分布式事务两阶段提交
在分布式事务中,需要协调所有分布式原子事务参与者,并决定提交或回滚分布式事务,因此采用两阶段提交协议:
第一阶段为请求阶段或表决阶段,事务协调者通知事务参与者准备提交或取消事务,然后进入表决过程,事务参与者将表决结果告知协调者是否同意提交事务;
第二阶段是提交阶段,协调者收集到所有参与者的表决结果,当且仅当所有表决者都同意提交事务,事务协调者才通知所有参与者提交事务,否则通知参与者回滚事务。
分布式事务首选需要确保各个参与者上面的事务都能进行提交,才能在所有参与者上提交,因此需要两个阶段提交。
MySQL事务两阶段提交
在MySQL事务中,由于事务涉及到存储引擎层和MySQL服务层以及binlog的写入,因此MySQL采用两阶段提交协议来处理事务。
先在事务涉及到的所有存储引擎上进行prepare成功后,调用方法将相关数据写到binlog,然后再调用存储引擎的commit完成事务提交。
Binlog在2PC中充当事务协调者的角色,由于Binlog来通知InnoDB引擎来执行Prepare,Commit或者Rollback操作。
在大部分关系性数据库中,为保证事务ACID特性,通过会要求事务在提交前,先将事务日志写入到磁盘固化即采用WAL预先写日志机制,但在MySQL Innodb事务引擎中,并没有遵循WAL预先写日志机制,因此可能存在数据不一致问题。
在MySQL Innodb处理事务的函数lock_trx_release_locks中,有以下注释:
/* The following assignment makes the transaction committed in memory
and makes its changes to data visible to other transactions.
NOTE that there is a small discrepancy from the strict formal
visibility rules here: a human user of the database can see
modifications made by another transaction T even before the necessary
log segment has been flushed to the disk. If the database happens to
crash before the flush, the user has seen modifications from T which
will never be a committed transaction. However, any transaction T2
which sees the modifications of the committing transaction T, and
which also itself makes modifications to the database, will get an lsn
larger than the committing transaction T. In the case where the log
flush fails, and T never gets committed, also T2 will never get
committed. */
事务T1先在内存中进行提交,此时事务日志尚未刷新到事务日志文件中,但事务所在修改对其他事务T2可见,由于T2访问T1数据,因此T2的LSN肯定大于T1的LSN,如果在T1的事务日志被刷新到日志文件之前发生宕机,在系统恢复时,由于事务T1因为没有预写日志而被回滚,当T2因为LSN大于T1且T1发生回滚,因此T1的事务也会发生回滚。
在刷新日志到日志文件时,T2的LSN大于T1的LSN,如果T1的事务日志未刷新到磁盘,那么T2的事务日志肯定不会被刷新到磁盘。