当前位置: 首页 > news >正文

MySQL-日志

UndoLog

为什么需要UndoLog?

我们在执行增删改的时候,MySQL默认开启事务,执行完就会自动提交事务;如果在还没有提交之前,MySQL发生崩溃,通过undolog回滚日志回滚到事务之前的数据;保证了事务ACID中的原子性;

undolog是一种撤销回退的日志,在事务没提交之前,MySQL会先记录更新前的数据到undolog文件里,当事务回滚时,可以利用undolog:

增删改时:

插入: undolog记录插入记录的主键值,这样之后回滚时只需要把这个主键对应的记录删掉就好了;

删除: 要把删除记录的内容都记录下来,这样之后回滚再把内容组成的记录插入到表中就好了;

更新: 要把更新前的旧值记录,回滚的时候再把这些列更新为旧值;

每一条记录的undolog格式都有一个 roll_pointer指针和一个trx_id 事务id:

  • 通过trx_id可以知道该记录是被哪个事务修改的
  • 通过roll_pointer指针可以将这些undo log串成一个链表,这个链表就被称为undolog版本链;
undolog实现MVCC机制:

对于「读提交」和「可重复读」隔离级别的事务来说,它们的快照读(普通 select 语句)是通过 Read View + undo log 来实现的,它们的区别在于创建 Read View 的时机不同:

  • 「读提交」隔离级别是在每个 select 都会生成一个新的 Read View,也意味着,事务期间的多次读取同一条数据,前后两次读的数据可能会出现不一致,因为可能这期间另外一个事务修改了该记录,并提交了事务。
  • 「可重复读」隔离级别是启动事务时生成一个 Read View,然后整个事务期间都在用这个 Read View,这样就保证了在事务期间读到的数据都是事务启动前的记录。

这两个隔离级别实现是通过「事务的 Read View 里的字段」和「记录中的两个隐藏列(trx_id 和 roll_pointer)」的比对,如果不满足可见行,就会顺着 undo log 版本链里找到满足其可见性的记录,从而控制并发事务访问同一个记录时的行为,这就叫 MVCC(多版本并发控制)。具体的实现可以看我这篇文章:事务隔离级别是怎么实现的?(opens new window)

因此,undo log 两大作用:

  • 实现事务回滚,保障事务的原子性。事务处理过程中,如果出现了错误或者用户执 行了 ROLLBACK 语句,MySQL 可以利用 undo log 中的历史数据将数据恢复到事务开始之前的状态。
  • 实现 MVCC(多版本并发控制)关键因素之一。MVCC 是通过 ReadView + undo log 实现的。undo log 为每条记录保存多份历史数据,MySQL 在执行快照读(普通 select 语句)的时候,会根据事务的 Read View 里的信息,顺着 undo log 的版本链找到满足其可见性的记录。
undoLog的持久化

undolog 和数据页的刷盘策略是一样的,都需要通过redolog保证持久化;

bufferpool中有undo页,对undo页的修改都会记录到redo log.

redo log每秒刷盘,事务提交刷盘,以此保证undolog的持久化.

RedoLog

什么是RedoLog

redolog是物理日志,记录了某个数据页做了什么修改.事务提交,先将redolog持久化到磁盘,可以不需要等到缓存在BufferPool里的脏数据持久化到磁盘.

当系统崩溃的时候,虽然脏数据页没有变化,但是redolog已经持久化了,MySQL重启后就会根据redoLog将数据恢复到最新状态.

redo 和 undo有什么区别?
  • redo记录的是事务完成后的数据状态,记录的是更新后的值;
  • undo记录的是事务开启前的数据状态,记录更新之前的值,
redolog的持久化:

redo log 也有自己的缓存—— redo log buffer,每当产生一条 redo log 时,会先写入到 redo log buffer,后续在持久化到磁盘如下图:

redoLog buffer什么时候刷盘?

  • MySQL正常关闭
  • 当redolog buffer 记录的写入量大于内存空间一半
  • InnoDB后台线程每隔1s
  • 每次事务提交

redo log 文件写满了怎么办?

默认情况下, InnoDB 存储引擎有 1 个重做日志文件组( redo log Group),「重做日志文件组」由有 2 个 redo log 文件组成,这两个 redo 日志的文件名叫 :ib_logfile0 和 ib_logfile1 。

在重做日志组中,每个 redo log File 的大小是固定且一致的,假设每个 redo log File 设置的上限是 1 GB,那么总共就可以记录 2GB 的操作。

重做日志文件组是以循环写的方式工作的,从头开始写,写到末尾就又回到开头,相当于一个环形。

所以 InnoDB 存储引擎会先写 ib_logfile0 文件,当 ib_logfile0 文件被写满的时候,会切换至 ib_logfile1 文件,当 ib_logfile1 文件也被写满时,会切换回 ib_logfile0 文件。

我们知道 redo log 是为了防止 Buffer Pool 中的脏页丢失而设计的,那么如果随着系统运行,Buffer Pool 的脏页刷新到了磁盘中,那么 redo log 对应的记录也就没用了,这时候我们擦除这些旧记录,以腾出空间记录新的更新操作。

redo log 是循环写的方式,相当于一个环形,InnoDB 用 write pos 表示 redo log 当前记录写到的位置,用 checkpoint 表示当前要擦除的位置,如下图:

图中的:

  • write pos 和 checkpoint 的移动都是顺时针方向;
  • write pos ~ checkpoint 之间的部分(图中的红色部分),用来记录新的更新操作;
  • check point ~ write pos 之间的部分(图中蓝色部分):待落盘的脏数据页记录;

如果 write pos 追上了 checkpoint,就意味着 redo log 文件满了,这时 MySQL 不能再执行新的更新操作,也就是说 MySQL 会被阻塞因此所以针对并发量大的系统,适当设置 redo log 的文件大小非常重要),此时会停下来将 Buffer Pool 中的脏页刷新到磁盘中,然后标记 redo log 哪些记录可以被擦除,接着对旧的 redo log 记录进行擦除,等擦除完旧记录腾出了空间,checkpoint 就会往后移动(图中顺时针),然后 MySQL 恢复正常运行,继续执行新的更新操作。

所以,一次 checkpoint 的过程就是脏页刷新到磁盘中变成干净页,然后标记 redo log 哪些记录可以被覆盖的过程。

WAL (Write-Ahead Logging)技术。

WAL 技术指的是, MySQL 的写操作并不是立刻写到磁盘上,而是先写日志,然后在合适的时间再写到磁盘上

redo log 要写到磁盘,数据也要写磁盘,为什么要多此一举?

写入 redo log 的方式使用了追加操作, 所以磁盘操作是顺序写,而写入数据需要先找到写入位置,然后才写到磁盘,所以磁盘操作是随机写

磁盘的「顺序写 」比「随机写」 高效的多,因此 redo log 写入磁盘的开销更小。

BinLog

undolog和redolog都是InnoDB存储引擎生成的,在MySQL完成一条更新操作之后,server层会生成一条binlog,等事务提交的时候会将事务内产生的binlog写入binlog文件.

binlog和redolog的区别

  • 适用对象不同
    • Binlog是server层实现的日志,所有存储引擎都可以使用
    • redolog是innnDB存储引擎实现的日志
  • 文件格式不同
    • binlog有三种格式: statement, row, mixed
      • statement是逻辑日志,每一条修改数据的sql都会被记录到binlog中,但是对于动态函数例如: uudi.now这些函数在使用binlog执行的时候与先前执行的结果不一致
      • row记录数据最终被修改成什么样,不会出现动态函数的问题,但是例如批量更新,会记录大量数据,而statement只会记录一条update语句,使得binlog文件过大
      • mixed包含statement和row模式,它会根据不同的情况自动使用row模式和statement模式
    • redolog是物理日志,它记录了在某个数据页做了什么修改: 对xxx表空间的xxx数据页xxx偏移量的地方做了xx修改
  • 写入方式不同
    • binlog是追加写,一个文件满了就新建文件继续写,不会覆盖以前的日志,保存的是全量日志
    • redolog是循环写,日志空间大小是固定的,全部写满就从头开始,保存未被刷入的磁盘的脏页日志.
  • 用途不同
    • binlog用于备份恢复,主从复制
    • redolog用于掉电等故障恢复

binlog什么时候刷盘

同样binlog也有自己的缓存,在server’层中binlog cache,事务提交的时候再把binlog cache写入到binlog文件中;这是为了防止多个事务被分段破坏了事务的原子性,

MySQL 给每个线程分配了一片内存用于缓冲 binlog ,该内存叫 binlog cache,参数 binlog_cache_size 用于控制单个线程内 binlog cache 所占内存的大小。如果超过了这个参数规定的大小,就要暂存到磁盘。

虽然每个线程有自己 binlog cache,但是最终都写到同一个 binlog 文件:

  • 图中的 write,指的就是指把日志写入到 binlog 文件,但是并没有把数据持久化到磁盘,因为数据还缓存在文件系统的 page cache 里,write 的写入速度还是比较快的,因为不涉及磁盘 I/O。
  • 图中的 fsync,才是将数据持久化到磁盘的操作,这里就会涉及磁盘 I/O,所以频繁的 fsync 会导致磁盘的 I/O 升高。

MySQL提供一个 sync_binlog 参数来控制数据库的 binlog 刷到磁盘上的频率:

  • sync_binlog = 0 的时候,表示每次提交事务都只 write,不 fsync,后续交由操作系统决定何时将数据持久化到磁盘;
  • sync_binlog = 1 的时候,表示每次提交事务都会 write,然后马上执行 fsync;
  • sync_binlog =N(N>1) 的时候,表示每次提交事务都 write,但累积 N 个事务后才 fsync。

在MySQL中系统默认的设置是 sync_binlog = 0,也就是不做任何强制性的磁盘刷新指令,这时候的性能是最好的,但是风险也是最大的。因为一旦主机发生异常重启,还没持久化到磁盘的数据就会丢失。

MySQL 主从复制还有哪些模型

主要有三种:

  • 同步复制:MySQL 主库提交事务的线程要等待所有从库的复制成功响应,才返回客户端结果。这种方式在实际项目中,基本上没法用,原因有两个:一是性能很差,因为要复制到所有节点才返回响应;二是可用性也很差,主库和所有从库任何一个数据库出问题,都会影响业务。
  • 异步复制(默认模型):MySQL 主库提交事务的线程并不会等待 binlog 同步到各从库,就返回客户端结果。这种模式一旦主库宕机,数据就会发生丢失。
  • 半同步复制:MySQL 5.7 版本之后增加的一种复制方式,介于两者之间,事务线程不用等待所有的从库复制成功响应,只要一部分复制成功响应回来就行,比如一主二从的集群,只要数据成功复制到任意一个从库上,主库的事务线程就可以返回给客户端。这种半同步复制的方式,兼顾了异步复制和同步复制的优点,即使出现主库宕机,至少还有一个从库有最新的数据,不存在数据丢失的风险

update 语句的执行过程

  1. 首先sql语句交给执行器,执行器调用存储引擎接口,通过索引树获取对应行记录:
    1. 如果该行所在的数据页在BufferPool中,就直接返回给执行器更新
    2. 如果不在,将数据页加载到BufferPool返回给执行器
  2. 执行器得到聚簇索引记录后,会查看更新前后数据是否一致
    1. 一致,就不进行后续流程
    2. 不一致,将更新前后记录当作参数交给InnoDB,让存储引擎真正执行更新操作
  3. InnoDB开启事务,更新之前先记录旧数据到undolog,undolog写入BufferPool中的undo页,然后记录对应undo的redo log
  4. InnoDB更新数据,将对应数据页标记为脏页,然后记录redolog,使用WAL技术,先将redo日志刷入磁盘,脏页等待合适的时机刷盘
  5. 更新语句完成后,记录的对应的binlog,保存到binlog cache,在事务提交的时候将binlog刷盘
  6. 剩下就是两阶段提交.

两阶段提交

事务提交后,redo log 和 binlog 都要持久化到磁盘,但是这两个是独立的逻辑,可能出现半成功的状态,这样就造成两份日志之间的逻辑不一致。

  • 如果redo刷入磁盘,MySQL断电,binlog没有来得及刷盘.MySQL重启之后,根据redolog将对应数据重做恢复;在主从架构中,binlog丢失了这条数据,导致主从数据不一致.
  • 如果binlog刷入到磁盘中,redolog没有刷盘MySQL就宕机;MySQL重启之后,在主从架构中,binlog会被复制到从库,从库执行更新语句,redolog没有写入,崩溃恢复后这个事务数据没有恢复,那么主从数据不一致.

两阶段提交的过程是怎样的?

为了保证这两个日志的一致性,MySQL 使用了内部 XA 事务

事务的提交过程有两个阶段,就是将 redo log 的写入拆成了两个步骤:prepare 和 commit,中间再穿插写入binlog,具体如下:

  • prepare 阶段:将 XID(内部 XA 事务的 ID) 写入到 redo log,同时将 redo log 对应的事务状态设置为 prepare,然后将 redo log 持久化到磁盘(innodb_flush_log_at_trx_commit = 1 的作用);
  • commit 阶段:把 XID 写入到 binlog,然后将 binlog 持久化到磁盘(sync_binlog = 1 的作用),接着调用引擎的提交事务接口,将 redo log 状态设置为 commit,此时该状态并不需要持久化到磁盘,只需要 write 到文件系统的 page cache 中就够了,因为只要 binlog 写磁盘成功,就算 redo log 的状态还是 prepare 也没有关系,一样会被认为事务已经执行成功;

两阶段提交有什么问题?

两阶段提交虽然保证了两个日志文件的数据一致性,但是性能很差,主要有两个方面的影响:

  • 磁盘 I/O 次数高:对于“双1”配置,每个事务提交都会进行两次 fsync(刷盘),一次是 redo log 刷盘,另一次是 binlog 刷盘。
  • 锁竞争激烈:两阶段提交虽然能够保证「单事务」两个日志的内容一致,但在「多事务」的情况下,却不能保证两者的提交顺序一致,因此,在两阶段提交的流程基础上,还需要加一个锁来保证提交的原子性,从而保证多事务的情况下,两个日志的提交顺序一致。

http://www.mrgr.cn/news/57102.html

相关文章:

  • 如何在浏览器中使用JavaScript进行屏幕截图
  • Java | Leetcode Java题解之第493题翻转对
  • Spring配置/管理bean-IOC(控制反转) 非常详细!基于XML及其注解!案例分析! 建议复习收藏!
  • CDL数据传输工具
  • 学习threejs,通过THREE.Raycaster给模型绑定点击事件
  • C++ 模版和继承
  • Android Studio Gradle版本、插件以及Android API对应关系(持续更新)
  • faiss向量数据库实现rag
  • 搭建知识库:快消行业中知识管理的重要性
  • 奖金居然高达十几万美金!最大素数到底有啥用?
  • RTOS之队列
  • 看完这篇,轻松搞定JavaScript复杂的问题
  • 基于Word2Vec和LSTM实现微博评论情感分析
  • 给c++小白的教程11:优化(1)
  • Django自定义过滤器
  • ffmpeg环境
  • 拍摄照片(鸿蒙系统01)
  • D46【python 接口自动化学习】- python基础之类
  • stl(1)pair
  • JVM、字节码文件介绍
  • 四、多线程带来的的⻛险-线程安全
  • webpack4 - 动态导入文件 dynamic-import 报错的解决方法
  • 安装Python及pip使用方法详解
  • 重生之“我打数据结构,真的假的?”--1.单链表(无习题)
  • React写关键字高亮的三个方案
  • 第二期:第15节,beep 大海