前言
InnoDB有两个非常重要的日志,undo log 和 redo log;通过undo log可以看到数据较早版本,实现MVCC,或回滚事务等功能;redo log用来保证事务持久性
本文以一条insert语句为线索介绍 mini transaction
mini transaction 简介
mini transation 主要用于innodb redo log 和 undo log写入,保证两种日志的ACID特性
mini-transaction遵循以下三个协议:
- The FIX Rules
- Write-Ahead Log
- Force-log-at-commit
The FIX Rules
修改一个页需要获得该页的x-latch
访问一个页是需要获得该页的s-latch或者x-latch
持有该页的latch直到修改或者访问该页的操作完成
Write-Ahead Log
持久化一个数据页之前,必须先将内存中相应的日志页持久化
每个页有一个LSN,每次页修改需要维护这个LSN,当一个页需要写入到持久化设备时,要求内存中小于该页LSN的日志先写入到持久化设备中
Force-log-at-commit
一个事务可以同时修改了多个页,Write-AheadLog单个数据页的一致性,无法保证事务的持久性
Force -log-at-commit要求当一个事务提交时,其产生所有的mini-transaction日志必须刷到持久设备中
这样即使在页数据刷盘的时候宕机,也可以通过日志进行redo恢复
代码简介
本文使用 MySQL 5.6.16 版本进行分析
mini transation 相关代码路径位于 storage/innobase/mtr/ 主要有 mtr0mtr.cc 和 mtr0log.cc 两个文件
另有部分代码在 storage/innobase/include/ 文件名以 mtr0 开头
mini transaction 的信息保存在结构体 mtr_t 中,结构体成员描述如下
| 成员属性 | 描述 |
|---|---|
| state | mini transaction所处状态 MTR_ACTIVE, MTR_COMMITTING, MTR_COMMITTED |
| memo | mtr 持有锁的栈 |
| log | mtr产生的日志 |
| inside_ibuf | insert buffer 是否修改 |
| modifications | 是否修改buffer pool pages |
| made_dirty | 是否产生buffer pool脏页 |
| n_log_recs | log 记录数 |
| n_freed_pages | 释放page数 |
| log_mode | 日志模式,默认MTR_LOG_ALL |
| start_lsn | lsn 起始值 |
| end_lsn | lsn 结束值 |
| magic_n | 魔术字 |
一个 mini transaction 从 mtr_start(mtr)开始,到 mtr_commit(mtr)结束
一条insert语句涉及的 mini transaction
下面涉及 mtr 的嵌套,在代码中,每个 mtr_t 对象变量名都叫 mtr,本文中为了区分不同 mtr,给不同的对象加编号
下面一般省略 mtr_t 以外的参数
第一个 mtr 从 row_ins_clust_index_entry_low 开始
1. mtr_start(mtr_1) // mtr_1 贯穿整条insert语句
2. row_ins_clust_index_entry_low
5. mtr_s_lock(dict_index_get_lock(index), mtr_1) // 对index加s锁
6. btr_cur_search_to_nth_level
7. row_ins_clust_index_entry_low
10. mtr_memo_push(mtr_1) // buffer RW_NO_LATCH 入栈
11. buf_page_get_gen
12. btr_cur_search_to_nth_level
13. row_ins_clust_index_entry_low
16. mtr_memo_push(mtr_1) // page RW_X_LATCH 入栈
17. buf_page_get_gen
18. btr_block_get_func
19. btr_cur_latch_leaves
20. btr_cur_search_to_nth_level
21. row_ins_clust_index_entry_low
24. mtr_start(mtr_2) // mtr_2 用于记录 undo log
25. trx_undo_report_row_operation
26. btr_cur_ins_lock_and_undo
27. btr_cur_optimistic_insert
28. row_ins_clust_index_entry_low
31. mtr_start(mtr_3) // mtr_3 分配或复用一个 undo log
32. trx_undo_assign_undo
33. trx_undo_report_row_operation
34. btr_cur_ins_lock_and_undo
35. btr_cur_optimistic_insert
36. row_ins_clust_index_entry_low
38. mtr_memo_push(mtr_3) // 对复用(也可能是分配)的 undo log page 加 RW_X_LATCH 入栈
39. buf_page_get_gen
40. trx_undo_page_get
41. trx_undo_reuse_cached // 这里先尝试复用,如果复用失败,则分配新的 undo log
42. trx_undo_assign_undo
43. trx_undo_report_row_operation
46. trx_undo_insert_header_reuse(mtr_3) // 写 undo log header
47. trx_undo_reuse_cached
48. trx_undo_assign_undo
49. trx_undo_report_row_operation
52. trx_undo_header_add_space_for_xid(mtr_3) // 在 undo header 中预留 XID 空间
53. trx_undo_reuse_cached
54. trx_undo_assign_undo
55. trx_undo_report_row_operation
58. mtr_commit(mtr_3) // 提交 mtr_3
59. trx_undo_assign_undo
60. trx_undo_report_row_operation
61. btr_cur_ins_lock_and_undo
62. btr_cur_optimistic_insert
63. row_ins_clust_index_entry_low
65. mtr_memo_push(mtr_2) // 即将写入的 undo log page 加 RW_X_LATCH 入栈
66. buf_page_get_gen
67. trx_undo_report_row_operation
68. btr_cur_ins_lock_and_undo
69. btr_cur_optimistic_insert
70. row_ins_clust_index_entry_low
73. trx_undo_page_report_insert(mtr_2) // undo log 记录 insert 操作
74. trx_undo_report_row_operation
75. btr_cur_ins_lock_and_undo
76. btr_cur_optimistic_insert
77. row_ins_clust_index_entry_low
80. mtr_commit(mtr_2) // 提交 mtr_2
81. trx_undo_report_row_operation
82. btr_cur_ins_lock_and_undo
83. btr_cur_optimistic_insert
84. row_ins_clust_index_entry_low
86. /*
87. mtr_2 提交后开始执行 insert 操作
88. page_cur_insert_rec_low 具体执行 insert 操作
89. 在该函数末尾调用 page_cur_insert_rec_write_log 写 redo log
90. */
93. page_cur_insert_rec_write_log(mtr_1) // insert 操作写 redo log
94. page_cur_insert_rec_lowpage_cur_tuple_insert
95. btr_cur_optimistic_insert
98. mtr_commit(mtr_1) // 提交 mtr_1
99. row_ins_clust_index_entry_low
至此 insert 语句执行结束后
一条 insert 是一个单语句事务,事务提交时也会涉及 mini transaction
提交事务时,第一个 mtr 从 trx_prepare 开始
1. mtr_start(mtr_4) // mtr_4 用于 prepare transaction
2. trx_prepare
3. trx_prepare_for_mysql
4. innobase_xa_prepare
5. ha_prepare_low
6. MYSQL_BIN_LOG::prepare
7. ha_commit_trans
8. trans_commit_stmt
9. mysql_execute_command
12. mtr_memo_push(mtr_4) // undo page 加 RW_X_LATCH 入栈
13. buf_page_get_gen
14. trx_undo_page_get
15. trx_undo_set_state_at_prepare
16. trx_prepare
19. mlog_write_ulint(seg_hdr + TRX_UNDO_STATE, undo->state, MLOG_2BYTES, mtr_4) 写入TRX_UNDO_STATE
20. trx_undo_set_state_at_prepare
21. trx_prepare
24. mlog_write_ulint(undo_header + TRX_UNDO_XID_EXISTS, TRUE, MLOG_1BYTE, mtr_4) 写入 TRX_UNDO_XID_EXISTS
25. trx_undo_set_state_at_prepare
26. trx_prepare
29. trx_undo_write_xid(undo_header, &undo->xid, mtr_4) undo 写入 xid
30. trx_undo_set_state_at_prepare
31. trx_prepare
34. mtr_commit(mtr_4) // 提交 mtr_4
35. trx_prepare
40. mtr_start(mtr_5) // mtr_5 用于 commit transaction
41. trx_commit
42. trx_commit_for_mysql
43. innobase_commit_low
44. innobase_commit
45. ha_commit_low
46. MYSQL_BIN_LOG::process_commit_stage_queue
47. MYSQL_BIN_LOG::ordered_commit
48. MYSQL_BIN_LOG::commit
49. ha_commit_trans
50. trans_commit_stmt
51. mysql_execute_command
55. mtr_memo_push(mtr_5) // undo page 加 RW_X_LATCH 入栈
56. buf_page_get_gen
57. trx_undo_page_get
58. trx_undo_set_state_at_finish
59. trx_write_serialisation_history
60. trx_commit_low
61. trx_commit
64. trx_undo_set_state_at_finish(mtr_5) // set undo state, 这里是 TRX_UNDO_CACHED
65. trx_write_serialisation_history
66. trx_commit_low
67. trx_commit
70. mtr_memo_push(mtr_5) // 系统表空间 transaction system header page 加 RW_X_LATCH 入栈
71. buf_page_get_gen
72. trx_sysf_get
73. trx_sys_update_mysql_binlog_offset
74. trx_write_serialisation_history
75. trx_commit_low
76. trx_commit
79. trx_sys_update_mysql_binlog_offset // 更新偏移量信息到系统表空间
80. trx_write_serialisation_history
81. trx_commit_low
82. trx_commit
84. mtr_commit(mtr_5) // 提交 mtr_5
85. trx_commit_low
86. trx_commit
至此 insert 语句涉及的 mini transaction 全部结束
总结
上面可以看到加锁、写日志到 mlog 等操作在 mini transaction 过程中进行
解锁、把日志刷盘等操作全部在 mtr_commit 中进行,和事务类似
mini transaction 没有回滚操作, 因为只有在 mtr_commit 才将修改落盘,如果宕机,内存丢失,无需回滚;如果落盘过程中宕机,崩溃恢复时可以看出落盘过程不完整,丢弃这部分修改
mtr_commit 主要包含以下步骤
- mlog 中日志刷盘
- 释放 mtr 持有的锁,锁信息保存在 memo 中,以栈形式保存,后加的锁先释放
- 清理 mtr 申请的内存空间,memo 和 log
- mtr—>state 设置为 MTR_COMMITTED
上面的步骤 1. 中,日志刷盘策略和 innodb_flush_log_at_trx_commit 有关
- 当设置该值为1时,每次事务提交都要做一次fsync,这是最安全的配置,即使宕机也不会丢失事务
- 当设置为2时,则在事务提交时只做write操作,只保证写到系统的page cache,因此实例crash不会丢失事务,但宕机则可能丢失事务
- 当设置为0时,事务提交不会触发redo写操作,而是留给后台线程每秒一次的刷盘操作,因此实例crash将最多丢失1秒钟内的事务
