MySQL Transaction--MySQL事务提交流程

MySQL事务提交流程

MySQL事务提交流程可拆分为下面几个阶段:

  • Prepare阶段
  • Flush阶段
  • Sync阶段
  • Commit阶段
  • Clean阶段

Prepare阶段

  1. 获取MDL_Key::COMMIT Metux。
  2. 获取last_committed值,该值为上一次COMMIT队里中最大的sequence_number。
  3. 修改事务状态,并将事务状态和XID写入Undo日志。
  4. 生成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阶段

  1. 形成Flush队列,将第一个进入Flush队列的线程作为Leader线程,后续进入Flush队列的线程作为非Leader线程,非Leader线程会被阻塞,直到Commit阶段后由Leader线程唤醒。
  2. 获取Flush阶段的LOCK
  3. 固化Flush队列,阻止新线程进入Flush队列
  4. 在InnoDB层做Redo日志持久化(Flush Redo Log)
  5. 生成GTID和sequence_number,基于Prepare阶段获取的last_committed值和参数binlog_transaction_dependency_tracking计算出最终last_committed值,生成GTID Event并直接写入Binary Log(OS CACHE层)。
  6. 将Binlog Cache中所有Event写入到Binary Log(OS CACHE层)。
  7. 如果参数Sync_binlog!=1,则唤醒Dump进程发生Binlog Evetn。
  8. 循环4、5、6步骤处理Flush队列中所有事务。
  9. 检查当前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阶段

  1. 将Flush阶段使用的Flush队列加入到Sync队列,第一个进入Sync队列的线程会作为Leader线程,其他线程作为非Leader线程,非Leader线程会被阻塞直到Commit阶段由Leader线程唤醒。
  2. 使用Flush阶段获取的LOCK
  3. 获取Sync阶段的锁
  4. 根据组提交参数binlog_group_commit_sync_delay和binlog_group_commit_sync_no_delay_count来决定当前步骤是否等到一段时间
  5. 固化Sync队列
  6. 根据参数sync_binlog参数决定是否执行Sync刷盘操作
  7. 如果参数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阶段

  1. 将Sync阶段产生的Sync队列加入到Commit队列,第一个加入到Commit队列的线程作为Leader线程,其余线程为非Leader线程,非Leader线程被阻塞直到Commit阶段后由Leader线程唤醒。
  2. 释放Sync阶段获取的LOCK
  3. 获取Commit阶段的LOCK
  4. 根据参数binlog_order_commits参数来决定是否按照队列的顺序进行InnoDB层的提交。当参数binlog_order_commits=0,则跳过后面步骤。
  5. 固化COMMIT队列,循环COMMIT队列中每个事务执行后面6、7、8步骤。
  6. 当参数rep_semi_sync_master_wait_point=AFTER_SYNC时,等待从库返回ACK,等待状态为Waiting for semi-sync ACK from slave。
  7. 修改全局last_committed值,在InnoDB层提交事务,如更新全局ReadView和Undo状态和释放事务锁资源等。
  8. 当参数rep_semi_sync_master_wait_point=AFTER_COMMIT时,等待从库返回ACK,等待状态为Waiting for semi-sync ACK from slave。
  9. 释放COMMIT阶段获取的LOCK

PS1: 由于Sync队列数据来源于Flush队列,而Commit队列事务来源于Sync队列,因此Commit队列中事务顺序和Sync队列及Flush队列中的事务顺序相同,但Sync队列可能包含多个Flush队列的数据,而Commit队列又可能包含多个Sync队列的数据,因此在事务数量上存在:Commit队列>=Sync队列>=Flush队列

Clean阶段

  1. Leader线程唤醒所有组内事务成员。
  2. 每个事务成员都清空Binlog Cache内存和临时文件,保留文件描述符但不释放,供后续事务使用。
  3. 当参数binlog_order_commits=0,每个事务分别在InnoDB层进行事务提交(不按照Binary Log的顺序处理)。
  4. 根据Sync阶段的Binary Log切换标记决定是否切换Binary Log
  5. 如果发生Binary Log切换,根据参数expire_logs_days来决定是否清理Binary Log。

参考资料

本文摘抄自<深入理解MySQL主从原理>中第3.3章节

MySQL Transaction--MySQL事务提交流程

上一篇:docker 安装mysql5.7


下一篇:Proj THUDBFuzz: AFLplusplus