如何保障-2PC
MySQL是通过2PC来保证写数据一致性的,具体步骤如下。
- 执行器先向存储引擎取指定行。如果这些行数据所在的数据页本来就在内存中,就直接返回给执行器,否需要先从磁盘读入内存,然后再返回。
- 执行器拿到数据后更新,得到新的数据,再调用存储引擎接口写入新数据。
- 存储引擎将新数据更新到内存,同时将这个更新操作记录到
redo-log
里面,此时redo-log
是prepare
状态。然后告知执行器执行完成了,可以commit
事务。 - 执行器生成这个操作的
binlog
,并把binlog
写入磁盘。 - 执行器调用存储引擎的提交事务接口,存储引擎把
redo-log
改成commit
状态,更新完成。
为什么2PC可以保障
- 如果在
redolog_prepare → binlog
发生崩溃,因为事务没有提交,所以直接回滚就行,不会产生任何影响。 - 如果在
binlog → redolog_commit
发生崩溃,MySQL的判断逻辑如下:
- 判断
redo-log
是否完整(是否处于commit状态),如果是就直接提交。 - 如果
redo-log
处于prepare
状态,这时候会判断binlog
是否完整(如果格式是statement,完整日志最后会有COMMIT
标识,如果格式是row,完整日志最后会有一个XID event
),如果完整就提交redo-log
,不完整就回滚事务。
为什么其他方式不行
这里的其他方式指除2PC(
redolog_prepare → binlog→ redolog_commit
)外的redolog→ binlog
和binlog→ redolog
两种方式。
我们可以简单用分布式事务来理解,MySQL的执行器和执行引擎是两个系统,必须保证这两个系统写数据的一致性。
或者采用反证法,一共就3种可能性,除了redolog_prepare → binlog → redolog_commit
之外还有另外两种:
redolog_full(redolog_prepare → redolog_commit) → binlog
- 如果
redolog
没写完就挂了,则一点影响都没有(redolog没成功写入意味着事务没有提交,就当这件事没发生过,redolog的写入机制决定了其一旦写入成功就不允许回滚了,如果回滚就可能会覆盖其他的事务 → 不同事务共用一块redolog buffer
,在落盘时还会顺带提交)。 - 如果写完
redo-log
,机器挂了,binlog
日志没有写入,那么机器重启后,这台机器会通过redo-log
恢复数据,但是因为binlog
并没有写入,因此后续进行备份或主从复制的时候,就会丢失相关记录 → 相当于少执行了一个事务。
binlog → redolog_full
- 如果写完了
binlog
,机器异常重启了,由于没有redo-log
,这个事务是无法恢复的,但是binlog
又有记录,那么和上面同样的道理,会产生数据不一致 → 相当于多了一个事务。