事务是用户定义的一系列的数据库操作,这些操作可以视为一个完整的逻辑处理工作单元,要么全部成功(全部执行),要么全部失败(全都不执行),是不可分割的工作单元
分布式事务是指会涉及到操作多个数据库(服务)的事务
其实就是将对同一数据库(服务)事务的概念扩大到了对多个数据库(服务)的事务。
目的是为了保证分布式系统中的数据一致性。
事务的特性ACID:
A 原子性
C 一致性:通过锁实现
I 隔离性
D 持久性
分布式事务场景
分布式事务处理的关键是必须有一种方法可以知道事务在任何地方的所有动作,提交或回滚事务的决定必须产生统一的结果(全部提交或者全部回滚)
引入协调者
XA规范
AP应用管理器:Web服务器
TM事务管理者(协调者)
RM 简单可以理解为数据库
XA规范: 定义了事务协调者和数据库之间的接口规范(接口函数),事务协调者用它来通知数据库事务的开始、结束以及提交、回滚等。
XA接口函数由数据库厂商提供
二阶段提交协议(2PC)和三阶段提交协议(3PC)(实际不会用)就是根据这一思想衍生出来的,保证了分布式事务的原子性:所有节点要么全做,要么全不做。
2PC生活中的例子
乐观的(SQL全部执行成功):
第一阶段:
牧师:你愿意娶这个女人吗?爱她、忠诚于她,无论她贫穷、疾病或者残疾,直到死亡,你愿意吗?
男:yes, I do
牧师:你愿意嫁给这个男人吗?爱他、忠诚于他,无论他贫穷、疾病或者残疾,直到死亡,你愿意吗?
女:我愿意
第二阶段
牧师:既然如此,请你们面向对方,握住对方的双手,作为妻子和丈夫向对方宣告誓言
男:我西门*,全心全意娶潘**作为我的妻子,无论....作为平等的忠实伴侣,度过今后的一生。
女:我潘**,全心全意嫁给西门*为妻,无论....作为平等的忠实伴侣,度过今后的一生。
皆大欢喜
悲观的(某个SQL报错):
第一阶段:
牧师:你愿意娶这个女人吗?爱她、忠诚于她,无论她贫穷、疾病或者残疾,直到死亡,你愿意吗?
男:yes, I do
牧师:你愿意嫁给这个男人吗?爱他、忠诚于他,无论他贫穷、疾病或者残疾,直到死亡,你愿意吗?
女:我不愿意
第二阶段
牧师:既然如此,请你们各自回家好好考虑考虑吧,亲友们也都散了吧
男:好的。
女:好的。
亲友不欢而散
2PC的设计
第一阶段(准备阶段)
各数据库执行SQL,执行完之后给协调者一个反馈(成功或者失败),等待协调者发第二次指令(第二阶段执行之前保留数据库连接资源,第二阶段执行完成之后释放)。
协调者发第二次指令:第一阶段所有参与者给的反馈都是成功(同意),协调者会发出提交的指令。
如果一个参与者有一个返回的是失败或者超过指定时间没有收到反馈,则协调者发出回滚的指令。
第二阶段(提交或者回滚阶段)
如果协调者发出的第二次指令是提交,数据库进行提交落库、释放资源(占有的数据库连接资源)。
如果协调者发出的第二次指令是回滚,则数据进行回滚并释放资源,这是一个靠谱的选择,1.如果第一阶段某个参与者给出了失败的反馈,那毫无疑问需要回滚。2.如果指定时间内没有收到反馈,那他可能成功也可能失败,回滚是一个比较靠谱的选择。
2PC协议的缺点:
1.单点故障:协调者如果挂了,则事务会失败无法保证,解决方案是搭建协调者集群。
2.阻塞资源:占用数据库连接、性能低,第一阶段执行完成后到协调者发第二条指令之前,占有数据库的资源,不能提交或者回滚,数据库的资源是宝贵的,这样会导致性能低。
解决方案:第一阶段直接提交并记录提交之前的数据库状态(释放资源),如果协调者发送的第二条指令是提交,删除记录的状态即可。如果协调者发出的第二条指令是回滚,回滚到我们记录的回滚之前的状态即可。这也是Seata中对于2PC的优化
3.数据不一致:第二阶段出错,数据不一致
假设第一阶段大家返回的都是成功,但是当协调者发出第二条指令(提交)的时候,某个数据库的网络断了(收不到第二次指令),则其他数据库都执行了提交的操作,但是这个断了连接的数据库没有执行,导致数据不一致。
发生网络连接故障的数据库的状态预期应该从1变成2,但是因为故障导致没有接受到协调者的提交指令,现在状态还是1.
其他某个数据库的预期状态从A变成B,因为一切正常,提交了,现在的状态是B。
解决方案:用脚本检查异常,如果出现了异常,则要么前滚(把故障库从1变成2),要么回滚(把正常库从B回滚成A)