传统的两阶段提交方式,所谓的两个阶段是指:准备阶段和提交阶段,包括管理者与参与者两个角色。
1.准备阶段
事务管理器给所有事务参与者发送准备提交(Prepare)消息,参与者即开始进行检查,
写本地的 Undo(取消)日志和 Redo(重做)日志,但不提交,执行完成上述步骤后返回成功
响应,若这个过程中参与者有任何问题(如写重做日志失败)就直接返回失败响应。
准备阶段详细描述如下:
(1)事务管理器向所有参与者询问是否做好提交事务的准备。
(2)参与者执行提交前的准备工作,将 Undo 信息和 Redo 信息写入日志,发送响应
给事务管理器。如果参与者的准备工作执行完成,则发送 “OK”消息;如果参与者的准备工作执行失败,则发送“Failed”消息。
2.提交阶段
如果事务管理器收到任何一个参与者的“Failed”消息或者因超时未收到响应,认为本事务无法提交成功,给每个参与者发送回滚(Rollback)消息;否则发送提交(Commit)消息;参与者根据事务管理器的指令执行提交或者回滚操作,释放所有事务处理过程中使用的锁资源。
根据响应信息的不同,提交阶段接分两种情况描述如下:
(1)当事务管理器从所有参与者节点获得的响应消息都为“OK”时:

1)事务管理器向所有参与者节点发送“提交”请求。
2)参与者执行提交操作,并释放在整个事务期间内占用的资源,向事务管理器发送“OK消息。
3)事务管理器收到所有参与者反馈的“OK”消息后,记录全局事务状态为已提交。
(2)如果任何一个参与者在第一阶段返回的响应消息为“Failed”,或者在超时之前无法获取所有参与者节点的响应消息时:

1)事务管理器向所有参与者发出“回滚”的请求。
2)参与者利用之前写入的 Undo 信息执行回滚,并释放在整个事务期间内占用的资源,
向事务管理器发送“OK”消息。
3)事务管理器收到所有参与者反馈的“OK”消息后,记录全局事务状态为终止状态。
从上述两阶段提交的过程可以看出,两阶段提交的原理非常简单,实现起来非常方便,在实践中很多厂商的组件都提供了满足两阶段提交协议的接口。但两阶段提交存在如下缺点:
(1)数据不一致。在提交阶段中,事务管理器向所有参与者发送提交请求后,由于网络延迟不同或者服务器处理速度不同,会出现部分参与者已经提交,部分参与者尚未提交的情况,在多个参与者提交的间隙会出现数据不一致的情况,出现脏读。
事务 T1 执行转账操作,从账户 1 转账 50 元到账户 2,在事务 T1 的
两阶段提交期间,若有事务 T2 读取账户 1 和账户 2 的余额之和,结果是 50+20=70,产生数据不一致问题。虽然这个时间间隙很短,发生的概率非常低,但在对数据一致性要求极高的金融交易中,任何不一致的情况都不允许发生。若在提交时发生了网络异常或者协调者发生了故障,也会导致整个分布式系统出现数据不一致性的现象,在系统故障恢复前,任何操作都会读取到不一致的数据。
(2)同步阻塞。在二阶段提交模型中,事务管理器记录了所有本地事务和全局事务的状态,它的角色至关重要,一旦它发生故障,参与者会一直阻塞下去。尤其在第二阶段开始前,若事务管理器发生故障,那么所有的参与者都处于阻塞状态。若在事务管理器发出提交消息之后宕机,且唯一接收到这条消息的参与者也同时宕机,那么这条事务的状态进入“悬挂”状态,无法确定是该提交还是回滚。在高并发的场景下,一旦出现同步阻塞,数据库会出现性能陡降。
(3)已提交数据无法回滚。在第二阶段中,如果个别参与者发生故障,事务管理器将尝试在此提交失败的数据库上重新提交,如果此节点一直不成功,则需要对于所有节点中已经提交数据要进行回滚,让数据回到交易前的状态,而单节点的数据库本身不能回滚已经提交的数据,只能由业务介入,通过补偿操作实现。在此种数据库上,每个业务都要准备相应的补偿业务来保证业务失败带来的数据不一致,工作量巨大,业务逻辑复杂起来,改造过于痛苦。




