InnoDB中 redo log 和 undo log、binlog

1. InnoDB中的Buffer Pool

Buffer Pool 的大小由系统变量 innodb_buffer_pool_size 控制,最小为 5MB 。

innodb_buffer_pool_size 的值小于 1GB 时,innodb_buffer_pool_instances强制设置为 1。

1.1 控制块

(1)每一个缓冲页都有一些控制信息,包含该页所属的表空间 ID、页号、链表节点信息等。每个缓冲页对应的控制信息占用内存大小是相同的,称为控制块

(2)控制块与缓冲页一一对应,存放在 Buffer Pool 前面,innodb_buffer_pool_size 并不包含控制块占用的内存空间大小。

1.2 链表

(1)链表的基节点包含头尾指针、当前链表节点数量,注意该基节点本身存储在另一块单独申请的内存空间中。

free 链表中实际内容为各控制块。

flush 链表存储的是被修改过的缓冲页。

LRU 链表优化:只有被访问的缓冲页位于 young 区域的后 1/4 页面时,才会被移动到 LRU 链表的头部。

(2)刷新脏页到磁盘:

  1. 后台进程定时从 LRU 链表尾部开始扫描一些页面,发现脏页后就刷新到磁盘中。
  2. 后台进程从 flush 链表中刷新一部分到磁盘
  3. 用户进程从 LRU 链表尾部刷新一个脏页到磁盘。(后台进程刷新较慢,用户加载一个页面时没有空间)

(3)以 chunk 为单位向操作系统申请内存空间。innodb_buffer_pool_chunk_size 只能在服务器启动时指定。

2. redo 日志

通用结构为:type | space ID | page number | data

2.1.1 简单的 redo 日志类型
类型type字段对应数字(十进制)描述
MLOG_1BYTE1表示在页面的某个偏移量处写入 1 个字节数据
MLOG_2BYTE2表示在页面的某个偏移量处写入 2 个字节数据
MLOG_4BYTE4表示在页面的某个偏移量处写入 4 个字节数据
MLOG_8BYTE8表示在页面的某个偏移量处写入 8 个字节数据
MLOG_WRITE_BYTE30表示在页面的某个偏移量处写入一个字节序列

type | space ID | page number | offset | data

其中 MLOG_WRITE_BYTE 的结构为:

type | space ID | page number | offset | len(具体占用多少字节) | data

(1)例子

服务器在内存中维护一个全局变量,每当向某个包含 row_id 隐藏列的表中插入一条数据时,就会将当前全局变量的值做为新纪录的 row_id 列的值,并将该全局变量自增 1;
每当这个全局变量的值为 256 的倍数时,就会写入到系统表空间页号为 7 的页面中一个名为 MAX ROW ID 的属性中(以 redo 日志方式写入);
当系统启动时,会将 MAX ROW ID 加载到内存中,并将该值加上 256 之后赋值给全局变量;

2.1.2 复杂一点的 redo 日志类型

在语句执行过程中,INSERT 对所有页面的修改都需要保存到 redo 日志中。

除了实际记录外,还可能更改 Page Header、Page Directory 等部分,甚至发生页的分裂。

类型type字段对应数字(十进制)描述
MLOG_REC_INSERT9插入一条使用非紧凑行格式( REDUNDANT ) 的记录
MLOG_COMP_REC_INSERT38插入一条使用紧凑行格式( COMPACT、DYNAMIC、COMPRESSED) 的记录
MLOG_COMP_PAGE_CREATE58创建一个存储紧凑行格式记录的页面
MLOG_COMP_REC_DELETE42删除一个存储紧凑行格式记录的页面
MLOG_COMP_LIST_START_DELETE44从某条给定记录开始删除页面中一系列使用紧凑行格式的记录
MLOG_COMP_LIST_END_DELETE43与 MLOG_COMP_LIST_START_DELETE 对应,表示删除一系列记录的末尾
MLOG_ZIP_PAGE_COMPRESS51压缩一个数据页

这些记录只是记录了必要的信息并不完全,例如没有记录怎么修改 Page Directory 等,在进行恢复时,实际上是重播,即调用函数重新进行插入操作。

2.2 Mini-Transaction

Mini-Transaction(MTR):对底层页面进行一次原子访问的过程。

2.2.1 以组的形式写入 redo 日志

(1)将日志分为若干组,这些组都是不可分割的(具有原子性,要么全恢复,要么一条也不恢复)

更新 Max Row ID 属性时产生的 redo 日志为一组
向聚簇索引对应的 B+ 树的页面中插入一条记录时产生的 redo 日志为一组
向二级索引对应的 B+ 树的页面中插入一条记录时产生的 redo 日志为一组

(2)有些需要保证原子性的操作生成多条 redo 日志,在该组最后一条 redo 日志后面加上一个特殊类型的 redo 日志(MLOG_MULTI_REC_END,该类型只有一个 type 字段(十进制的 31));

有些需要保证原子性的操作只生成一个 redo 日志(在该日志的 type 字段的最高位标识产生单个/一系列的 redo 日志)

2.3 日志写入过程

图19-15
LOG_BLOCK_HDR_NO:每个 block 的编号;
LOG_BLOCK_HDR_DATA_LEN:表示该 block 中已写入的字节数,初始为 12;
LOG_BLOCK_FIRST_REC_GROUP:该 block 中第一个 MTR 生成的 redo log 偏移量;
LOG_BLOCK_CHECKPOINT_NO:表示 checkpoint 的序号;

LOG_BLOCK_CHECKSUM:校验和;

2.3.1 log buffer

在内存中有个 buf_free 的全局变量指向后续写入的 redo 日志应该写到 log buffer 中的哪个 block 的哪个位置。

并不是每生成一条 redo 日志就将其插入到 log buffer 中,而是将每个 MTR 运行过程中产生的日志先暂存到某个地方,当 MTR 运行结束后,再将该组 redo 日志全部复制到 log buffer 中。

不同事务可能是并发执行的,因此不同事务的 MTR 可能是交替写入 log buffer 中。

2.3.2 redo log 刷盘时机

log buffer 空间不足时
事务提交时
后台进程每个 1s 将 log buffer 中的 redo 日志刷新到磁盘
正常关闭服务器时
做 checkpoint 时

2.3.3 redo log 文件

redo 日志文件也是由若干个 512 字节的 block 组成的。
前 4 个 block 存放一些管理信息。
图19-23
LOG_HEADER_START_LSN:标记本 redo 文件偏移量为 2048 字节处对应的 lsn 值。

checkpoint1 和 2 的结构如下:
在这里插入图片描述
LOG_CHECKPOINT_NO:服务器执行 checkpoint 的编号;
LOG_CHECKPOINT_LSN:服务器在结束 checkpoint 时对应的 lsn 值,崩溃后恢复时从该值开始;
LOG_CHECKPOINT_OFFSET:上个属性中的 lsn 值在 redo 日志文件组中的偏移量;

2.4 lsn

(1)lsn(Log sequence number) 记录当前总共已经写入的日志量。初始值为 8704,其增长是随着字节数进行增长的(也包含log block header 和 trailer 的字节数,有点偏移量的感觉)

flushed_to_disk_lsn (Log flushed up to)表示刷新到磁盘中的 redo 日志量,初始时与 lsn 相同。

(2)buf_next_to_write 标记当前 log buffer 中已经有哪些日志被刷新到磁盘中。

(3)flush 链表中的脏页是按第一次修改发生的时间顺序进行排序的,即按照 oldest_modification 代表的 lsn 值进行排序,被多次更新的页面只需修改 newest_modification 属性值即可。

某个 MTR 执行结束后,会将修改的页对应的控制块加入 flush 链表的头部,若该控制块已经在 flush 链表中则只需修改 newest_modification。

在控制块中的两个属性:
oldest_modification :第一次修改该页面 MTR 开始时对应的 lsn 值。
newest_modification:修改该页面 MTR 结束时对应的 lsn 值。

(4)执行 checkpoint 的步骤

计算 flush 链表中表尾控制块中的 oldest_modification(Page flushed up to) ,小于该值的 lsn,说明其代表的脏页被刷新到磁盘中,是可以被覆盖的。并将该值赋值给 checkpoint_lsn;(Last checkpoint at);
根据 checkpoint_lsn 计算对应的 redo 日志文件组的偏移量,并记录到 checkpoint1 block 或者 checkpoint2 block 中(根据 checkpoint_no 奇偶决定)。

注意:Page flushed up to 和 Last checkpoint at 不一定相同,在执行 checkpoint 时相同,但可能后台进程会刷新 redo 日志到磁盘,就会改变 Page flushed up to。

2.5 崩溃恢复

比较 checkpoint1 block 和 checkpoint2 block 中的 checkpoint_no 确定哪个是最近的一个 checkpoint,取出对应的 checkpoint_lsn。

凡是小于 checkpoint_lsn 的日志都已经被刷新到磁盘中不需要进行恢复。

从 checkpoint_lsn 开始恢复到日志结尾,但日志中的记录可能都是不同页面交替写入的,会造成大量随机 IO。

(1)可以建立哈希表,将 space ID 和 page number 做为 key,使用开链法处理冲突。这样修改同一个页面的记录就会按先后顺序放在一个槽中,可以避免大量随机 IO;

(2)跳过已经刷新到磁盘中页面。每个页面中的 File Header 部分的 FIL_PAGE_LSN 属性,记录了其最近一次修改页面的 lsn 值,若当前日志的 lsn 值小于 FIL_PAGE_LSN ,也不需要进行恢复。

2.6 相关参数

(1)innodb_flush_log_at_trx_commit 控制事务提交时,redo log 的行为:

  1. 设置为0的时候,表示每次事务提交时都只是把 redo log 留在 redo log buffer 中(什么也不调用);
  2. 设置为1的时候,表示每次事务提交时都将 redo log 直接持久化到磁盘(调用 write 和 fsync);
  3. 设置为2的时候,表示每次事务提交时都只是把 redo log 写到操作系统的 page cache(只调用 write)。

3. Undo log

对只读事务,只有它在第一次对某个用户创建的临时表执行增删改操作时,才会分配一个事务 ID。

对于读写事务,只有第一次对某个表执行增删改时,才会分配事务 ID。

事务 ID 的分配过程与隐藏列 row_id 大致相同。

3.1 Undo log 页面

页面类型为 FIL_PAGE_UNDO_LOG(0x0002),可以从系统表空间中分配,也可以从专门存放 undo log 的表空间中分配;

3.2 INSERT 操作对应的 undo log

图20-2
undo no:在一个事务中从 0 开始递增。

向表中插入记录时,实际上需要向聚簇索引和二级索引都插入相应记录,在回滚操作时,只需知道主键信息即可。(如何快速找到二级索引中主键对应记录?通过Change Buffer?)

在这里插入图片描述

roll_pointer 是指向记录对应的 undo log 的指针

3.3 DELETE 操作对应的 undo log(类型为 TRX_UNDO_DEL_MARK_REC)

PAGE_FREE 指向由被删除记录组成的垃圾链表中的头节点。

删除一条记录会经历两个阶段:

  1. delete mark:仅仅将记录的 deleted_flag 标志位设为 1,然后修改 trx_id、roll_pointer 这些隐藏列的值。

  2. purge:删除语句的事务提交后,由专门的线程把该记录从正常记录的链表中移动到垃圾链表中,然后调整 PAGE_N_RECS、PAGE_LAST_INSERT、PAGE_FREE 等信息,注意这里插入采用头插法,即插入到垃圾链表的头结点处。

(1)在事务提交前,只会经历阶段 1,因此只需考虑删除操作在阶段 1 所做的影响进行回滚即可;

图20-9
这里的索引列信息主要在事务提交后使用(也包含聚簇索引),用来对中间状态的记录真正的删除(阶段 2);

pos 代表列的位置,表名是第几列,注意这里是包含隐藏列的。

3.4 UPDATE 操作对应的 undo log

(1)不更新主键(TRX_UNDO_UPD_EXIST_REC 类型)

  1. 就地更新(对每一个更新后的列与更新前占用存储空间一样大)
    直接在原纪录的基础上修改对应列的值

  2. 先删除旧记录,再插入新记录
    这里的删除操作是真正的删除操作,即阶段 1 和 2,特殊的是由用户线程同步执行真正的删除操作。

图20-15
如果有被更新的索引列,也会记录主键索引的信息。

(2)更新主键

将旧记录进行 delete mark 操作(会记录 TRX_UNDO_DEL_MARK_REC 类型的 undo log)

根据更新后各列的值创建一条新记录,并将其插入到聚簇索引中。(会记录 TRX_UNDO_INSERT_REC 类型的 undo log)

3.5 增删改操作对二级索引的影响

INSERT、DELETE 操作与聚簇索引类似

UPDATE 操作中若不涉及二级索引的列,则不用执行任何操作。否则,也需要对旧的二级索引记录执行 delete mark 操作,再根据更新后的值创建一条新的二级索引记录,然后在二级索引对应的 B+ 树中插入。

二级索引记录没有 trx_id、roll_point 等属性,每当增删改二级索引记录时,都会影响 Page Header 部分的 PAGE_MAX_TRX_ID 属性。

3.6 Undo 页面

(1)页面类型为 FIL_PAGE_UNDO_LOG,除了页面通用的 File Header \ Trailer 外,其中 Undo Page Header 如下
图 20-21

  1. TYPE:undo log 的类型,分为两类:insert undo log 和 update undo log,分别使用十进制的 1、2 表示;类型为 TRX_UNDO_INSERT_REC 的 undo log 属于 insert undo log,其余类型都属于 update undo log;
    注意,不同大类的 undo log 不能混着存储,之所以这样划分是因为插入操作的 undo log 在事务提交后可以直接删除。

  2. START:第一条 undo log 在本页面的偏移量

  3. FREE:最后一条日志结束时的偏移量

  4. NODE:链表节点结构(前后指针,包含页号和偏移量)

(2)Undo 页面组成双向链表,链表中的第一个页面为 first undo page,其余为 normal undo page;

在一个事务执行过程中,可能需要 2 个 Undo 页面链表,一个为 insert undo 链表,另一个为 update undo 链表。

对普通表和临时表的记录进行改动时所产生的 undo log 要分别记录,因此一个事务最多需要 4 个链表。并非事务一开始就分配,而是使用到时才会进行分配。

(3)每个 Undo 页面链表都对应一个段,称为 Undo Log Segment。链表中的页面都是从这个段中申请的。

first undo page 中还包含一个 Undo Log Segment Header 部分,此部分包含了该链表对应段的 Segment Header 信息,具体如下:

图20-29

  1. STATE:本 Undo 页面链表的状态;
    TRX_UNDO_ACTIVE:有活跃事务正在向这个链表中写入 Undo 日志;
    TRX_UNDO_CACHED:被缓存,等待之后被其他事务重用;
    TRX_UNDO_TO_FREE:等待被释放,对 insert undo 链表,事务提交后就处于这种状态,不能被重用。
    TRX_UNDO_TO_PURGE:等待被 purge,对 update undo 链表,事务提交后就处于这种状态,不能被重用。
    TRX_UNDO_PREPARED:用于存储处于 PREPARE 阶段的事务产生的日志;
  2. LAST_LOG:本 Undo 页面链表中最后一个 Undo Log Header 的位置;
  3. FSEG_HEADER:本 Undo 页面链表对应段的 Segment Header 信息;
  4. PAGE_LIST:Undo 页面链表的基节点。

(4)同一个事务向一个 Undo 页面链表中写入的 undo 日志算一个组,存储这些组属性地方为 Undo Log Header;

first undo page 结构如下所示:
图20-30
Undo Log Header 结构如下所示:
图20-31

  1. TRX_ID:生成本组 undo log 的事务 ID;
  2. TRX_NO:事务提交后生成的序号;
  3. DEL_MARKS:标记本组 undo log 是否包含由 delete mark 操作产生的 undo 日志;
  4. LOG_START:本组 undo log 中第一条 undo log 在页面中的偏移量;
  5. XID_EXISTS:是否包含 XID 信息;
  6. DICT_TRANS:是不是由 DDL 语句产生的;
  7. TABLE_ID:如果 DICT_TRANS 为真,表示 DDL 操作的表的 ID;
  8. NEXT_LOG:下一组 undo log 在页面中开始的偏移量;(一般一个 undo 页面链表只有一个组,这里主要是考虑重用 undo log 链表的情况)
  9. PREV_LOG:上一组 undo log 在页面中开始的偏移量;
  10. HISTORY_NODE:表示 History 链表的节点;

3.7 重用 Undo 页面

一个 Undo 页面链表可被重用,需要满足条件:

该链表中只包含一个 Undo 页面;
该 Undo 页面已经使用的空间小于整个页面空间的 3/4;

(1)对 insert undo 链表
如果可重用,直接覆盖掉之前的 undo log,从头开始写入新事务的一组 undo log;需要适当修改 Undo Page Header 等中的信息。

(2)update undo

以 append 方式追加,注意,对每个新事务都需要一个单独的 Undo Log Header,也是以 append 方式追加,这也是 Undo Log Header 中 NEXT_LOG/PREV_LOG 属性的意义;

3.8 回滚段

每个事务最多可以拥有 4 个 Undo 页面链表,系统中可能有许多事务,为了更好的管理这些链表。

设计了一个 Rollback Segment Header 的页面类型,里面存放个跟 Undo 页面链表的 first undo page 的页号,这些页号称为 undo slot;

图20-35
每个 Rollback Segment Header 对应一个段,这个段称为回滚段,该段中只有一个页面;

  1. TRX_RSEG_MAX_SIZE:这个回滚段中所有 Undo 页面链表中的 Undo 页面数量之和的最大值,默认为 0xFFFFFFFE;
  2. HISTORY_SIZE:History 链表的页面数量;
  3. HISTORY:History 链表的基节点;
  4. FSEG_HEADER:Segment Header 结构,通过它可以找到该回滚段对应的 INODE Entry;
  5. UNDO_SLOTS:记录各链表的 first undo page 的页号;一个页号占用 4 字节,可以存储 1024 个 Undo 页面链表。
3.8.1 从回滚段申请 Undo 页面链表

(1)初始情况下,各 undo slot 被设置一个特殊值:FIL_NULL,表示不指向任何页面。
(2)有事务需要分配 Undo 页面链表时,会从回滚段的第一个 undo slot 开始检查:

  1. 若是 FIL_NULL,在表空间新建一个段(即 Undo Log Segment),从该段申请一个页面作为 Undo 页面链表的 first undo page (需要填写 Undo Log Segment Header,之后可以找到该段),把该页页号记录在这个 undo slot 处。
  2. 若不是 FIL_NULL,接着向下遍历;

(3)若这 1024 个 undo slot 都被占用,会停止执行该事务并返回错误;

(4)一个回滚段对应两个 cached 链表。如果有新事务需要分配 undo slot,都先从对应的 cached 链表中找,如果没有缓存,才会到回滚段的 Rollback Segment Header 页面中找。

当一个事务提交时,

  1. 如果其占用的 undo slot 指向的 Undo 页面链表符合被重用的条件,设置该链表为 TRX_UNDO_CACHED 状态,如果对应的 Undo 页面链表为 insert undo 链表,会被加入 insert undo cached 链表中,否则,加入 update undo cached 链表中。
  2. 不符合被重用的条件
    对 insert undo 链表,设置其状态为 TRX_UNDO_TO_FREE,会将 Undo 页面链表对应的段释放掉,把该 undo slot 设置为 FIL_NULL;
    对 update undo 链表,设置其状态为 TRX_UNDO_TO_PRUGE,把该 undo slot 设置为 FIL_NULL,将本事务写入的一组 undo log 放入 History 链表中;
3.8.2 多个回滚段

指向这些回滚段的指针(表 ID + 页号)存放在系统表空间中第 5 号页面中,即 TRX_SYS。共设计了 128 个回滚段。

3.8.3 回滚段的分类

(1)第 0 号回滚段必须在系统表空间中。33 ~ 127 号回滚段既可以在系统表空间中,也可以在自己配置的 undo 表空间中。

对普通表记录的修改,必须从此类段中分配相应的 undo slot;

(2)1 ~ 32 号回滚段必须在临时表空间中。

对临时表记录的修改,必须从此类段中分配相应的 undo slot;

(3)为什么区分开来?

向 undo 页面写入 undo 日志的本身也是一个写页面的过程,需要记录相应的 redo log,而针对临时表,不需要记录对应的 redo log;

3.8.4 roll_pointer

图20-39
is_insert:指向的日志是否为 TRX_UNDO_INSERT 的 undo log;
rseg_id:回滚段的编号(最多为 128 个回滚段,7 位足够表示);

3.8.5 事务分配 Undo 页面链表的详细过程
  1. 事务在执行时对普通表记录修改前,首先到系统表空间的第 5 号页面中分配一个回滚段
    采用循环使用方式分配回滚段,当前事务分配到第 0 号回滚段,下一个事务就分配 33 号回滚段,依次类推。
  2. 分配到回滚段后,首先查看该回滚段的 2 个 cached 链表是否有相应缓存。
  3. 如果没有缓存,需要在 Rollback Segment Header 页面中找一个可用的 undo slot 分配给当前事务;

(1)这里的问题是 cached 链表的基节点存储在哪里?

这里是在内存中维护了一组数据结构,并不在磁盘上。

参考下述网址:
http://mysql.taobao.org/monthly/2021/10/01/

4. binlog(归档)

(1)binlog 是二进制文件,记录数据库发生更改的各种事件,是 Server 层的日志(redo log 是 InnoDB 中的日志)

binlog 的索引文件 binlog.index,只是将各 binlog 文件的路径保存了一下

记录数据库发生更改的各种事件(增删改),而且是以事务提交顺序记录的

(2)查看工具,mysqlbinlog binlog_filename

(3)逻辑日志,两种记录模式

  1. statement 格式的话是记 sql 语句 ;结束标志为 COMMIT;
  2. row格式会记录行的内容,记两条,更新前和更新后都有(一般情况下采用,statement 格式可能会遇到时间上的问题);结束标志为 XID event;
  3. mixed格式,前两种格式的混合;

结束标志用来判断 binlog 是完整的;

(4)不像 redo log 一样是循环写入的,而是以追加形式,会不断增大;好处是可以恢复到一段时间内的某个节点;

4.1 binlog 写入过程

(1)事务执行过程中,先把日志写到 binlog cache,事务提交时,再将 binlog cache 写入 binlog 文件中;

一个事务的 binlog 不能被拆开,必须确保一次性写入

(2)每个线程都有自己单独的 binlog cache, binlog cache 如果超过了 binlog_cache_size,就要暂存到磁盘(tmp_file),因此 binlog cache 可能包含 binlog cache memory + tmp_file;

事务提交时,就要清空 binlog cache,这些线程共用一份 binlog 文件

(3)sync_binlog 控制 fsync 和 write 的时机
sync_binlog = 0,每次事务提交时,只 write ,而不 fsync ;
sync_binlog = 1,每次事务提交时,都进行 fsync ;
sync_binlog = N(N > 1),每次事务提交时都 write ,累积 N 个事务后才 fsync ;(一般设置为 100 ~ 1000)主机异常重启可能会丢失最近的 N 个事务的 binlog 日志;

(4)双 1 设置,sync_binloginnodb_flush_log_at_trx_commit 都设置为 1;

事务完整提交前,需要进行两次刷盘,一次是 redo log(prepare阶段),一次是 binlog;

4.2 组提交(group commit)

(1)并发场景下,redo log buffer 中未落盘的第一个事务的 redo log 被选为 Leader,等其开始写盘时,会把其他事务的 redo log 一起落盘;

第一个事务写完 redo log buffer 后,调用 fsync 越晚,组中的组员越多;

(2)redo log 和 binlog 两阶段提交的细化顺序为:

  1. redo log prepare write
  2. binlog write
  3. redo log prepare fsync
  4. binlog fsync
  5. redo log commit write

binlog_group_commit_sync_delay 表示延迟多少微妙后才调用 fsync;
binlog_group_commit_sync_no_delay_count 表示累加多少次后才调用 fsync;
二者是或的关系,只要有一个满足就会调用 fsync,当 binlog_group_commit_sync_delay 设置为 0 时, binlog_group_commit_sync_no_delay_count 也无效;
注意:这两个参数是影响第 4 步之前的等待过程,即使 sync_binlog 设置为 0,也会等待这段时间,即使不需要调用 binlog fsync;

4.3 statement 格式的问题

(1)statement 格式下会记录执行的 SQL 语句,但这些语句在主库和备库上执行可能会有不同的效果;例如主库可能选择索引 A,而在备库上可能选择索引 B,尤其是搭配 LIMIT 等一起时,可能会造成主备库不一致的问题;

(2)row 格式,则会记录要改变的记录的主键,因此不会造成不一致的问题;

优点:恢复数据方便;
记录 UPDATE 时会把更新前后的记录都保存下来;
会记录新插入的行的所有字段值

(3)mixed 格式是折中方案;row 格式占用空间较大,如果不会引起主从不一致问题就会使用 statement 格式记录;

调用 now() 函数会使用 statement 格式,因为在记录执行语句前,会先执行 SET TIMESTAMP=xxx

4.4 Xid

(1)在内存中维护 global_query_id 全局变量,每次执行语句的时候将它赋值给 Query_id,然后给这个变量加1。如果当前语句是这个事务执行的第一条语句,那么 MySQL 还会同时把 Query_id 赋值给这个事务的 Xid;

(2)MySQL重启之后会重新生成新的 binlog 文件,这保证了每个 binlog 文件中 Xid 是唯一的;

5. 各 log 的顺序

(1)以更新操作为例,前三个部分都分别是一个 MTR;

  1. 记录 undo log;(记录这个过程的 redo log);
  2. 修改聚簇索引记录(先更新 trx_idroll_pointer),记录这个过程的 redo log;
  3. 更新二级索引记录(此部分不会有 undo log),记录这个过程的 redo log;
  4. 记录 binlog;
  5. 事务提交,将相应 log 刷新到文件中;

(2)redo log 和 binlog 采用两阶段提交方式(两阶段提交是跨系统维持数据逻辑一致性时常用的一个方案)

  1. 写入 redo log,处于 prepared 状态
  2. 写入 binlog
  3. 将 redo log 改为 commit 状态(提交时的一个步骤)

redo log 是 InnoDB 中的日志,binlog 是 Server 层的日志,若不采用这种方式,那么不管 redo log 还是 binlog 谁先谁后,在写入一个后系统崩溃,都会造成不一致的问题;

(3)不同时刻崩溃的情况

  1. 顺利执行完的情况,redo log 中有 commit 标志,则直接提交

  2. redo log 中只有完整的 prepared
    会去查看对应事务的 binlog 中(XID字段相同)是否完整存在,如是,则提交事务,否则回滚事务;
    在 1、2 之间数据库崩溃时,binlog 没有写入,redo log 没有提交会进行回滚,不会造成主从不一致问题
    在 2、3 之间数据库崩溃时,binlog 写入,redo log 中只有完整的 prepared

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值