书接上一篇文章,MySQL通过不同的策略来保证事务的ACID:原子性、一致性、隔离性、持久性,通过锁机制实现隔离性,通过redo+undo+binlog三种日志实现事务的原子性、一致性和持久性。
本文主要讲MySQL的持久性的一个实现机制-两阶段提交策略。
两阶段提交定义
两阶段提交指的是MySQL在提交事务将脏数据刷盘的过程分为prepare和commit两个阶段,当客户端commit命令开始执行后,MySQL内部开启一个XA(分布式事务)事务,并为该事务分配一个XID,先将redo log缓存中的数据刷新到日志,redo log刷盘后,redo log对该事务标记为prepare,磁盘的redo日志中也就存在该XID。接着,将binlog缓存中的数据刷盘,binlog刷盘完成后,磁盘的binlog中会存在该XID,会将该redo log上的事务标记为commit。至此,客户端发起的commit命令执行完成。
那么问题来了,提交一个事务的时候直接写到binlog中不行么,为啥非得先对redo log刷盘,然后再对binlog刷盘?
两阶段提交策略的作用
这就引出了两阶段提交策略的作用:确保MySQL数据库崩溃后重启时能快速恢复。这个快速是建立在使用redo log恢复基础上的,因为redo log的数据页记录的是物理页的修改,并且大小和物理表空间中数据页相同,利用redo log可以快速将未提交的事务提交并完成刷盘,因此提交事务时要使用两阶段提交策略。
数据库崩溃时两阶段提交的不同情况
数据库崩溃时,提交的事务(假设对表user中id=1 name="张三"的记录执行 update user set name="李四" where id = 1 )可能遇到如下
1、redo log已经刷盘,事务被标记为prepare状态,binlog尚未刷盘,MySQL内部开启的XA事务会给redo log标记唯一的事务号,binlog中不存在该事务号。
2、redo log已经刷盘,事务被标记为prepare状态,binlog已经完成刷盘,MySQL内部开启的XA事务会给redo log和binlog分别标记相同的事务号。
3、redo log和binlog都已经完成刷盘,事务被标记为commit状态。
数据库崩溃恢复时,会检查redo log中的XA事务号是否在binlog中能找到。
针对第1种情况,binlog中不存在redo log 中的XA事务号,则直接将事务回滚。
针对第2种情况,binlog中找到了redo log中的XA事务号,但事务仍然是prepare状态,说明binlog将修改刷盘后还没有来得及标记该事务为commit状态数据库就已经崩溃了,这个时候直接提交该事务即可。
针对第3种情况,说明事务已经成功提交,崩溃恢复时事务无须处理。
到这里两阶段提交策略就没有问题了,但爱学习的小伙伴肯定就会问了,都说学以致用,那么理解两阶段提交策略原理到底有什么用呢?
哈哈哈哈,这个问题我也在思考,除了装逼之外,我暂时想不到其他在工作中能具体用到的地方,留作以后补充吧。