其实所谓的两阶段就是把一个事物分成两个阶段来提交。 也可以回顾 :https://www.yuque.com/wangchao-volk4/whmpo0/zdeodg#L2hDt
两阶段提交的第一阶段 (prepare阶段):写rodo-log 并将其标记为prepare状态。
1、设置undo state=TRX_UNDO_PREPARED;
2、刷事务更新产生的redo日志;
两阶段提交的第二阶段(commit阶段):写bin-log 并将其标记为commit状态。
1、将事务产生的binlog写入文件,刷入磁盘;
2、设置undo页的状态,置为TRX_UNDO_TO_FREE或TRX_UNDO_TO_PURGE; //标记可以清理回滚段
3、记录事务对应的binlog偏移,写入系统表空间。
为什么需要两段式提交
因为MySQL有两个日志文件,我们需要两个日志都写入,我们需要保证两个日志的一致性。那么如果不使用两阶段提交的方式,直接写入 redo log 然后写入 binlog 有什么问题呢?
假设,写完redo log,系统挂了。那么重启后innoDB引擎会根据redo log日志来恢复数据库。这时候数据库里面的数据是正确的。但是binlog丢失了啊。如果你有从库,那么从库的数据就错误了。因为从库的数据是通过binlog同步的。
如果把这两个步骤反过来呢,先写入binlog 再写入redo log呢?那么就会redo log丢失,数据库实际上没有更新。但是从库通过binlog更新了。还是数据不一致。
所以需要 两阶段提交 来保证数据一致性。如果这时候写完redo log后挂掉了,因为redo log和binlog都没有数据,所以会回滚事务。 如果 binlog 和 redo log 都写入了,但是没有提交,那么重启后会提交事务。这样binlog 和数据库就都有数据了。
MYSQL目前的组提交方式解决了一致性和性能的问题。通过二阶段提交解决一致性,通过redo log和binlog的组提交解决磁盘IO的性能。
binlog 组提交
引入队列机制保证 innodb commit 顺序与 binlog 落盘顺序一致,并将事务分组,组内的 binlog 刷盘动作交给一个事务进行,实现组提交目的。binlog提交将提交分为了3个阶段,FLUSH阶段,SYNC阶段和COMMIT阶段。每个阶段都有一个队列,每个队列有一个 mutex 保护,约定进入队列第一个线程为 leader,其他线程为follower,所有事情交由leader去做,leader做完所有动作后,通知follower刷盘结束。binlog组提交基本流程如下:
FLUSH 阶段
(1) 持有Lock_log mutex [leader持有,follower等待]
(2) 获取队列中的一组binlog(队列中的所有事务)
(3) 将binlog buffer到I/O cache
(4) 通知dump线程dump binlog
SYNC阶段
(1) 释放Lock_log mutex,持有Lock_sync mutex[leader持有,follower等待
(2) 将一组binlog 落盘(sync动作,最耗时,假设sync_binlog为1
COMMIT阶段
(1) 释放Lock_sync mutex,持有Lock_commit mutex[leader持有,follower等待]
(2) 遍历队列中的事务,逐一进行innodb commit
(3) 释放Lock_commit mutex
(4) 唤醒队列中等待的线程
说明:由于有多个队列,每个队列各自有mutex保护,队列之间是顺序的,约定进入队列的一个线程为leader,因此FLUSH阶段的leader可能是SYNC阶段的follower,但是follower永远是follower。