关于分布式事务的思考
另外关于分布式事务的支持也是一个大家可能比较感兴趣的点,基于MySQL的方式来做分布式数据库的时候分布式事务是不可能满足严格的分布式事务语义的。
数据库事务有ACID四个属性,分别是原子性、一致性、隔离性、持久性。
原子性(Atomicity)的意思是整个事务最终只能是要么成功要么失败,不能存在中间状态,如果发生错误了就需要回滚回去,就像这个事务从来没有执行过一样。
一致性(Consistency)是指系统要处于一个一致的状态,不能因为并发事务的多少影响到系统的一致性,举个典型的例子就是转帐的情况,假设有ABC三个帐号各有100元,那么不管这三个帐号之间怎么转账,整个系统总的额度是300元这一点是应该是不变的。其实ACID里的一致性更多的是应用程序需要考虑的问题,和分布式系统里的CAP里的一致性完全不是一个概念。
隔离性(Isolation),本质上是解决并发执行的事务如何保证数据库状态是正确的,抽象描述叫可串行化,就是并发的事务在执行的时候效果要求达到看起来像是一个个事务串行执行的效果。有冲突的事务之间的隔离性如果保证不了会引起前面的一致性(consistency)也无法满足。
每个事务包含多个动作,这些动作如果按照事务本身的顺序依次执行就是所谓的串行执行,这些动作也可以重新排列,排列完以后的动作如果效果可以等价于事务串行执行的效果我们就叫做可串行化调度。
实际实现的时候往往采用的是冲突可串行化,这个条件比可串行化要求会更高一点,规定了一些读写顺序规定了一些访问冲突的情况规定了哪些情况两个事物的动作可以调换哪些是不可以的,可以理解为冲突可串行化是可串行化的充分条件。
持久性(Durability),在事务完成以后所有的修改可以持久的保存在数据库中,一般会采用WAL的方式,会把操作提前记录到日志中来保证即使操作还没有刷到磁盘就宕机的情况下有日志可以恢复。介绍完事务的ACID属性以后,我们再来分析为什么基于MySQL无法提供严格的分布式事务语义的支持。
如果客户端发送的SQL只涉及到一个节点,那自然是可以保证事务的,但是如果客户端发送的SQL涉及到两个及以上节点的SQL,那就无法保证事务语义了。
原因主要是两个,一是原子性无法保证,另一个是隔离性无法保证。在一个节点commit成功以后,在另外的节点commit失败了,这个事务就处在一个中间状态,此时原子性被打破。
引起的另一个问题就是隔离性,这个事务的一部分提交了,另一部分未提交,此时该事务正常是不该被读取到的,但是提交成功的部分会被其他事务读到,此时就无法保证隔离性了。
另外就算是涉及多个节点的操作都是成功的,理论上来说也是无法保证隔离性的。因为假设A事务的一个节点先commit成功,其他的节点后commit成功,而此时B事务在读取的时候可能会读取到了A事务最早commit成功的那部分内容,却没有读到后来commit成功的内容,此时依然无法保证隔离性。
更本质一点的原因是MySQL的事务都是每个实例维护自身的事务ID,而基于MySQL集群的分布式方案没有一个全局的事务ID来标识每个MySQL实例上的事务以及全局事务的元信息的管理,所以无法做到严格的分布式事务语义。
但实际上绝大多数业务对这个需求未必那么强烈,因为绝大多数的业务逻辑都是可以拆分的,拆成一个个只落在一个分库里的操作在绝大多数场景下是完全可行的,而且拆分完以后也会更可控,所以这个问题在我们支撑业务的过程中也不是一个特别大的问题。