MySQL事务提交流程
MySQL事务提交流程可拆分为下面几个阶段:
- Prepare阶段
- Flush阶段
- Sync阶段
- Commit阶段
- Clean阶段
Prepare阶段
- 获取MDL_Key::COMMIT Metux。
- 获取last_committed值,该值为上一次COMMIT队里中最大的sequence_number。
- 修改事务状态,并将事务状态和XID写入Undo日志。
- 生成XID_EVENT并写入Binlog Cache。
MySQL实例会在内存中会维护一个全局递增变量global_query_id,在每次语句执行时获取当前最新的global_query_id分配给当前语句作为query_id,如果当前语句是事务中的第一个语句,则将该语句的query_id作为事务的XID。
XID值会写入到Undo日志和Binlog日志中,而Undo日志的修改会产生Redo日志,在MySQL实例崩溃恢复时,会根据Redo日志来恢复Undo日志,再根据Undo日志中的XID值和BINLOG中的XID来判定事务是否需要回滚。
Flush阶段
- 形成Flush队列,将第一个进入Flush队列的线程作为Leader线程,后续进入Flush队列的线程作为非Leader线程,非Leader线程会被阻塞,直到Commit阶段后由Leader线程唤醒。
- 获取Flush阶段的LOCK
- 固化Flush队列,阻止新线程进入Flush队列
- 在InnoDB层做Redo日志持久化(Flush Redo Log)
- 生成GTID和sequence_number,基于Prepare阶段获取的last_committed值和参数binlog_transaction_dependency_tracking计算出最终last_committed值,生成GTID Event并直接写入Binary Log(OS CACHE层)。
- 将Binlog Cache中所有Event写入到Binary Log(OS CACHE层)。
- 如果参数Sync_binlog!=1,则唤醒Dump进程发生Binlog Evetn。
- 循环4、5、6步骤处理Flush队列中所有事务。
- 检查当前Binary Log大小是否超过参数max_binlog_size限制,判断是否需要进行binary log切换。
PS1:由于GTID_EVENT在第5步生成并直接写入到Binary Log,而XID_EVENT在Prepare阶段写入到Binlog Cache,在第6步将binlog Cache中包含的QUERY_EVENT/MAP_EVENT/DML_EVENT/XID_EVENT写入到Binary Log,因此事务的Binary Log以GTID_EVENT开始和XID_EVENT结束。
Sync阶段
- 将Flush阶段使用的Flush队列加入到Sync队列,第一个进入Sync队列的线程会作为Leader线程,其他线程作为非Leader线程,非Leader线程会被阻塞直到Commit阶段由Leader线程唤醒。
- 使用Flush阶段获取的LOCK
- 获取Sync阶段的锁
- 根据组提交参数binlog_group_commit_sync_delay和binlog_group_commit_sync_no_delay_count来决定当前步骤是否等到一段时间
- 固化Sync队列
- 根据参数sync_binlog参数决定是否执行Sync刷盘操作
- 如果参数Sync_binlog=1,则唤醒Dump进程发生Binlog Evetn。
PS1: 当参数sync_binlog=N(N>1)时,是按照binlog groupt提交次数来计算(where N is a value other than 0 or 1: The binary log is synchronized to disk after N binary log commit groups have been collected.)
Commit阶段
- 将Sync阶段产生的Sync队列加入到Commit队列,第一个加入到Commit队列的线程作为Leader线程,其余线程为非Leader线程,非Leader线程被阻塞直到Commit阶段后由Leader线程唤醒。
- 释放Sync阶段获取的LOCK
- 获取Commit阶段的LOCK
- 根据参数binlog_order_commits参数来决定是否按照队列的顺序进行InnoDB层的提交。当参数binlog_order_commits=0,则跳过后面步骤。
- 固化COMMIT队列,循环COMMIT队列中每个事务执行后面6、7、8步骤。
- 当参数rep_semi_sync_master_wait_point=AFTER_SYNC时,等待从库返回ACK,等待状态为Waiting for semi-sync ACK from slave。
- 修改全局last_committed值,在InnoDB层提交事务,如更新全局ReadView和Undo状态和释放事务锁资源等。
- 当参数rep_semi_sync_master_wait_point=AFTER_COMMIT时,等待从库返回ACK,等待状态为Waiting for semi-sync ACK from slave。
- 释放COMMIT阶段获取的LOCK
PS1: 由于Sync队列数据来源于Flush队列,而Commit队列事务来源于Sync队列,因此Commit队列中事务顺序和Sync队列及Flush队列中的事务顺序相同,但Sync队列可能包含多个Flush队列的数据,而Commit队列又可能包含多个Sync队列的数据,因此在事务数量上存在:Commit队列>=Sync队列>=Flush队列
Clean阶段
- Leader线程唤醒所有组内事务成员。
- 每个事务成员都清空Binlog Cache内存和临时文件,保留文件描述符但不释放,供后续事务使用。
- 当参数binlog_order_commits=0,每个事务分别在InnoDB层进行事务提交(不按照Binary Log的顺序处理)。
- 根据Sync阶段的Binary Log切换标记决定是否切换Binary Log
- 如果发生Binary Log切换,根据参数expire_logs_days来决定是否清理Binary Log。
参考资料
本文摘抄自<深入理解MySQL主从原理>中第3.3章节