什么是两阶段提交
“两阶段提交”用在 redo log 与 binlog 同步更新时,用于保证主从同步数据时数据的一致性
简单来说,两阶段提交就是把 redo log 日志分为 prepare 和 commit 这两步写入。在一个事务完成后,日志的写入流程是:
redo log 写入处于 prepare 阶段的记录 ---> binlog 写记录 ---> redo log 写入 commit 阶段的记录
不使用两阶段提交可能出现的问题
先来看如果不使用“两阶段提交”,可能出现的问题:
不使用两阶段提交,即两个日志都只写一次,那就只有两种情况,要么先写 redo log 再写 binlog,要么先写 binlog 再写 redo log。
如果先写 redo log,假如在 redo log 写入成功后,写 binlog 时发生错误宕机了。数据库重启后,会根据 redo log 恢复数据(因为 redo log 写入成功就代表一个事务执行完成了,当然要恢复这部分数据),而 binlog 上却没有记录相应的语句。所以之后使用 binlog 归档的时候就会丢数据
如果先写 binlog,假如在 binlog 写入成功后,写入 redo log 时发生错误宕机了。数据库重启后,根据 redo log 恢复数据,因为 redo log 没有写成功,所以恢复后事务相当于没有执行,但是 binlog 中已经写入了这个事务相应的语句。这样使用 binlog 归档时会多出来一部分操作
两阶段提交是怎么解决问题的
再来看为什么引入“两阶段提交”之后,就能解决上面出现的问题:
引入两阶段提交后,日志写入流程为:
整个流程中,可能出现错误的时刻分别有:
时刻 1:写入 prepare 阶段的 redo log
时刻 2:prepare 阶段 redo log 写完,写入 binlog 前
时刻 3:prepare 阶段 redo log 写完,写入 binlog 时
时刻 4:binlog 写完,写入 commit 状态 redo log 前
时刻 5:binlog 写完,写入 commit 状态 redo log 时
逐个分析,
在时刻 1,redo log 还没有写完,系统就发生错误。那在故障前未提交的事务会回滚,redo log 和 binlog 都没有相应记录,没有什么影响
时刻 2 和时刻 1类似,就算 redo log 已经写完了,但是 binlog 没有开始写,恢复后也会回滚事务,此时 redo log 和 binlog 都没有相应记录
时刻 3 也类似,binlog 写一半发生故障了,恢复后同样回滚事务,此时 redo log 和 binlog 都没有相应记录
在时刻 4,redo log 已经完成了 prepare 阶段,binlog 也已经写完,在写 commit 状态的 redo log 前发生错误。恢复后,系统会进行判断,如果
redo log 中的事务只有完整的 prepare,且对应的事务在 binlog 中完整的存在,那么就会重新写一次commit阶段的 redo log,写入完成后提交这个事务。如果写入过程中又发生故障导致写入失败,那么在恢复后就会再重新判断一次
在时刻 5,如果 redo log 已经完成了 commit 阶段的写入,在之后无关紧要的时刻发生错误。系统恢复后查看 redo log 中的内容,redo log 中的事务是完整的,也就是已经有了 commit 标识,此时直接提交即可。如果 commit 阶段 redo log 没有写完,崩溃恢复后,会像时刻 4 一样,由系统判断是否应该重新写一次 commit 阶段的 redo log
前三个时刻没有什么问题,在时刻 4和时刻 5中,有几个问题需要考虑
1、系统是怎么知道 binlog 内容是完整的?
一个事务的 binlog 是有完整格式的,根据标识就可以判断一个事务是否完整:
-
statement 格式的 binlog,最后会有 COMMIT
-
row 格式的 binlog,最后会有一个 XID event
另外,在 MySQL 5.6.2 版本以后,还引入了 binlog-checksum 参数,用来验证 binlog 内容的正确性。binlog 日志由于磁盘原因,可能会在日志中间出错的情况,MySQL 可以通过校验 checksum 的结果来发现。所以,MySQL 还是有办法验证事务 binlog 的完整性的
2、redo log 和 binlog 是怎么关联起来的?
在 redo log 和 binlog 中,有一个共同的字段 XID,用于标识事务
崩溃恢复的时候,会按顺序扫描 redo log:
-
如果碰到既有 prepare、又有 commit 的 redo log,就直接提交
-
如果碰到只有 parepare、而没有 commit 的 redo log,就拿着 XID 去 binlog 找对应的事务,找到了就再写一遍 commit 阶段的 redo log,然后提交
所以,引入“两阶段提交”后,无论在哪一个时刻发生错误,都有恢复的手段
两阶段提交的缺点
两阶段提交虽然保证了事务提交后 redo log 和 binlog 这两个日志文件的数据一致性,但是牺牲了一些性能,如增加了磁盘 IO 次数、增加锁次数,降低并发度
对此 MySQL 引入了“组提交”的机制用于优化,提高了并发度。组提交需要了解 redo log 和 binlog 的具体写入过程,不再展开,后续有机会会在写文章