图解MySQL【日志】——两阶段提交
两阶段提交为什么需要两阶段提交事务提交后Redo Log 和 Binlog 都要持久化到磁盘中但是这两个是独立的逻辑可能会出现半成功状态造成两种日志之间的逻辑不一致。1.1. 半成功状态Redo Log 成功持久化而 Binlog 失败如果在将 Redo Log 刷入到磁盘后MySQL 突然宕机了而 Binlog 还未来得及写入。MySQL 异常重启后通过 Redo Log 恢复而 Binlog 在被复制到从库时丢失了这条语句导致主从数据不一致。Binlog 成功持久化而 Redo Log 失败如果在将 Binlog 刷入到磁盘后MySQL 突然宕机了而 Redo Log 还未来得及写入崩溃后该事务无效重置为事务执行前的值而 Binlog 被复制到从库从库执行了该语句导致主从数据不一致。1.2. 两阶段提交MySQL 为了解决 Redo Log 与 Binlog 的半成功问题使用了【两阶段提交】来解决。两阶段提交一种分布式事务一致性协议可以保证多个逻辑操作要么全部成功要么全部失败不会出现这种半成功的状态。它将单个事务的提交拆分成 2 个阶段【准备阶段】和【提交阶段。两阶段提交的参与者协调者Coordinator负责管理整个事务的提交或回滚过程向所有参与者发送给请求并收集响应。参与者Participant执行事务的具体操作根据协调者的指令提交或回滚事务。准备阶段协调者向所有参与者发送准备请求Prepare Request询问是否可以提交事务。参与者执行事务操作并将结果写入日志确保故障后可以恢复。参与者根据执行结果回复协调者如果事务可以提交回复同意Yes。如果事务无法提交如发生错误回复中止No。提交阶段如果所有参与者都回复“同意”协调者向所有参与者发送提交请求Commit Request参与者正式提交事务。如果任一参与者回复“中止”协调者向所有参与者发送回滚请求Rollback Request参与者回滚事务。参与者完成提交或回滚后向协调者发送确认消息。两阶段提交过程prepare 阶段将 XID内部 XA 事务的 ID写入到 Redo Log。将 Redo Log 对应的事务状态设置为 prepare 阶段然后将 Redo Log 持久化到磁盘受innodb_flush_log_at_trx_commit参数控制commit 阶段将 XID 写入到 Binlog将 Binlog 持久化到磁盘中受sync_binlog参数控制调用引擎的提交接口将 Redo Log 设置为 commit 状态此时该状态并需要持久化到磁盘中只需 write 到 OS 的 page cache 中即可因为只要 Binlog 刷盘成功Redo Log 的即使是 prepare 状态也无所谓一样会被认为事务执行成功。两阶段异常重启及解决不管是时刻 A(redo log 已经写入磁盘 binlog 还没写入磁盘)还是时刻B(redo log 和 binlog 都已经写入磁盘还没写入 commit 标识)崩溃此时的 redo log 都处于 prepare 状态。在 MySQL重启后会按顺序扫描 redo log 文件碰到处于 prepare 状态的 redo log就拿着 redo log 中的XID 去 binlog 查看是否存在此 XID:如果 binlog 中没有当前内部 XA 事务的 XID说明 redolog 完成刷盘但是 binlog 还没有刷盘则回滚事务。对应时刻 A 崩溃恢复的情况。如果 binlog 中有当前内部 XA 事务的 XID说明 redolog 和 binlog 都已经完成了刷盘则提交事务。对应时刻 B 崩溃恢复的情况。对于处于 prepare 阶段的 redolog即可以提交事务也可以回滚事务这取决于是否能在binlog 中查找到与 redo log 相同的 XID如果有就提交事务如果没有就回滚事务。这样就可以保证redo log 和 binlog 这两份日志的一致性了。两阶段提交是以 binlog 写成功为事务提交成功的标识因为 binlog 写成功了就意味着能在binlog 中查找到与 redo log 相同的 XID。事务没提交的时候redo log 会被持久化到磁盘吗会的。事务执行中间过程的 redo log 也是直接写在 redo log buffer 中的这些缓存在 redo log buffer 里的 redolog 也会被「后台线程」每隔一秒一起持久化到磁盘。如果 mysql崩溃了还没提交事务的 redo log 已经被持久化磁盘了mysql 重启后数据不就不一致了?不会这种情况 mysql重启会进行回滚操作因为事务没提交的时候binlog 是还没持久化到磁盘的。所以redo log 可以在事务没提交之前持久化到磁盘但是 binlog,必须在事务提交之后才可以持久化到磁盘。两阶段提交的问题磁盘 I/O 次数高对于innodb_flush_log_at_trx_commit 1并且sync_binlog 1的双“1”配置每个事务提交都会进行两次刷盘Redo Log Binlog。锁竞争激烈两阶段提交虽然能保证【单事务】两个日志的内容一致但在【多事务】时却无法保证两者提交顺序一致因此需要锁来保证在多事务情况下两个日志提交顺序的一致。解决⭐组提交Binlog 组提交当有多个事务提交时会将多个 Binlog 刷盘操作合并成一个以减少磁盘 I/O 次数。过程flush多个事务按进入的顺序将 Binlog 从 cache 写入文件不刷盘。sync对 Binlog 文件做 sync 操作多个事务的 Binlog 合并一次刷盘。commit各个事务按顺序做 InnoDB commit 操作。队列 leader组提交的各个阶段都有一个队列每个阶段都有锁进行保护因此保证了事务写入顺序第一个进入队列的事务会成为 leaderleader 领导所在队列的所有事务全权负责整队的操作完成后通知队内其他事务操作结束。锁粒度减小用每个阶段引入了队列代替锁住事务的整个过程锁粒度减小使得多个阶段可以并发执行提升了效率。Redo Log 组提交MySQL 5.7 及以后版本引入了 Redo Log 组提交改进点prepare 阶段时不再让事务独自执行 Redo Log 的刷盘操作而是推迟到组提交的 flush 阶段即 prepare 阶段融合在了 flush 阶段。过程双“1”配置flush第一个事务成为 flush 阶段的 Leader之后的事务都是 Follower。获取队列的事务组由绿色事务组的 Leader 对 Redo Log 做一次writefsync即一次将同组事务的 Redo Log 刷盘。完成 prepare 阶段后将绿色这一事务组执行过程中产生的 Binlog 写入 Binlog 文件调用wirte而不fsync综上得知flush 阶段队列的作用是用于支撑 Redo Log 的组提交。如果在这个阶段数据库发生崩溃由于 Binlog 中没有该组事务记录MySQL 在重启后回滚该组事务。sync事务不会写入 Binlog 文件后立即刷盘而是会等待一段时间由参数Binlog_group_commit_sync_delay控制目的是为了组合更多事务的 Binlog然后一起刷盘。但在等待时间内如果事务数量达到了Binlog_group_commit_sync_no_delay_count参数设置的值就会立即刷盘。综上得知sync 阶段队列是用于支持 Binlog 的组提交。如果该阶段发生崩溃由于 Binlog 已经有了事务记录MySQL 重启后通过 Redo Log 刷盘的数据进行事务提交。commit调用引擎的提交接口将 Redo Log 状态设置为 commit。commit 阶段是为了承接 sync 阶段的事务完成最后的引擎提交使得 sync 可以尽早地处理下一组事务最大化组提交的效率。