> For the complete documentation index, see [llms.txt](https://yangsx95.gitbook.io/notes/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://yangsx95.gitbook.io/notes/distributed/shi-wu-yi-zhi-xing.md).

# 事务一致性

## 数据库事务的概念

数据库事务：多个操作可以被视作一个完整的逻辑处理单元，要么全部执行，要么全部不执行，是不可分割的工作单元。

事务具有ACID的特性：

* 原子性：日志保证，undolog
* 一致性：锁保证
* 隔离性：锁保证
* 持久性：日志保证，redolog

> 事务是为了保证一致性产生的，AID是方式，C是目的。

## 分布式事务的概念

涉及到多个数据库、多个服务时，多个库、多个服务之间就会存在数据一致性的问题，这种事务就是分布式事务。

![](https://2351062869-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7b2CdwBN9liniVJpfEAc%2Fuploads%2Fgit-blob-e946bb8ec51c1e691aafceb6991a9e2b6764d297%2F20240310100404.png?alt=media)

## 如何解决分布式事务的问题？解决思路是什么呢？

分布式事务问题实际上就是分布式系统下的数据一致性问题。根据CAP和BASE理论解决一致性问题一般有强一致性与最终一致性方案。

强一致性方案比较适合对一致性的实时性要求高、性能要求较低的场景下；而最终一致性方案，则适合系统对性能的要求较高，实时性要求比较叫低的场景。

强一致性的场景下，通常会引入一个事务协调者，监控所有分支事务的运行状态。通常，所有分支事务会想进行”彩排“，彩排的目的是提高事务的成功概率，如果彩排成功，就会对所有分支事务提交，否则会通知所有的分支事务回滚。

而最终一致性方案因为对实时性要求比较低，解决方案就相对比较多。

| 方案名称          | 一致性   | 描述                                                   |
| ------------- | ----- | ---------------------------------------------------- |
| XA规范          | 强一致性  |                                                      |
| 2PC协议         | 强一致性  |                                                      |
| 3PC协议         | 强一致性  |                                                      |
| TCC           | 最终一致性 | 先进行资源预留，成功提交，失败取消资源预留                                |
| Saga          | 最终一致性 | 提交时从前往后提交，回滚时从后往前做补偿操作                               |
| 事件驱动          | 最终一致性 | 前一个事务提交成功后会发送事件给下一个事务，依次驱动到最后一个事务                    |
| 事件表+定时任务+消息队列 | 最终一致性 | 事件驱动的优化，存储分支事务事件到事件表，并通过定时任务发送事件到其他服务处理分支事务          |
| 本地消息表         | 最终一致性 | 在主事务中记录分支事务的消息状态，然后向分支事务发送消息，根据所有消息的状态可对事务进行完成或回滚的操作 |
| 最大努力通知方案      | 最终一致性 | 事务处理失败重复尝试知道成功为止、事务处理失败通过其他查询接口对其数据（支付对账）            |

## 分布式事务的XA规范

![](https://2351062869-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7b2CdwBN9liniVJpfEAc%2Fuploads%2Fgit-blob-df4fc305e5a767eb385c61edf73551a613a906df%2F20240310102056.png?alt=media)

根据分布式事务的解决思路，就衍生出了XA规范。XA规范定义了事务协调者与数据库之间的规范，事务协调者用于通知数据库的开始、结束以及提交、回滚等。

上图中，TM就是协调者，RM是数据库，AP是应用程序。XA接口函数由数据库厂商提供。

分布式集群情况下，一般增加代理层来充当TM的角色，实现对事物的支持，由XA规范，引申出了：

* 2PC，两阶段提交
* 3PC，三阶段提交（几乎没有人用）

## 什么是两阶段提交协议？

两阶段提交分为两个阶段：

1. 第一阶段为投票阶段，事务协调者开启事务，让参与事务的各方进行执行演练，并收集执行结果
2. 第二阶段为提交/回滚阶段，如果有所有方都投票成功，就会通知所有方进行提交。如果有任何一方投票失败，或者连接不通，就会通知所有方进行回滚。

两阶段提交可以尽可能的保证数据的一致性。

![](https://2351062869-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7b2CdwBN9liniVJpfEAc%2Fuploads%2Fgit-blob-d70283cd56a08e66b515784c712432cc0aefaf09%2F20240310103344.png?alt=media) ![](https://2351062869-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7b2CdwBN9liniVJpfEAc%2Fuploads%2Fgit-blob-1fedcb2f1f8d1076ed78d0fda6709a66fc5fe210%2F20240310103404.png?alt=media) ![](https://2351062869-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7b2CdwBN9liniVJpfEAc%2Fuploads%2Fgit-blob-149f2016a116a4ab949beec6523e00b2031f7d50%2F20240310103422.png?alt=media)

## 两阶段提交有什么缺点？如何解决这些缺点？

* 单点故障
  * 协调者发生故障
  * 解决方案：协调者改为集群部署
* 阻塞资源
  * 占用数据库连接，性能低
  * 解决方案：在一阶段的时候，直接提交事务，并记录提交前的数据状态。等到二阶段，如果成功，那就不用通知了。如果失败，将数据回滚到记录的上个状态中（Alibaba seata AT模式 就是这么做的，他通过undolog来完成的）。
* 数据不一致仍有可能发生
  * 当演练成功后，通知所有的服务，这个阶段有可能通讯失败，就造成了数据不一致的情况。
  * 解决方案：使用脚本检查异常情况，自动回滚

## 什么是三阶段提交协议？

有了两阶段提交，为什么还需要三阶段提交协议？

1. 事务参与者如果本身就不符合执行条件，如果继续开启事务执行sql，会占用数据库连接，造成性能损失。故在三阶段提交中，增加`can commit`阶段，用于校验参与者是否可以提交，如果不可以，直接不开启事务。
2. 为参与者提供超时机制，也就是在`do commit`阶段，做更好的容错处理，提高事务成功的概率。

![](https://2351062869-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7b2CdwBN9liniVJpfEAc%2Fuploads%2Fgit-blob-36d08232bcffdc9b53a9b57f6be7f046738eeb17%2F20240310105057.png?alt=media)

三阶段提交分为三个阶段，分别是：

1. `can commit`：校验所有参与者是否可进行提交操作
   1. 如果反馈结果为成功，进行`pre commit`阶段
   2. 如果有反馈结果为失败，执行 `abort commit`，事务直接会被取消。`can commit`返回失败的情况包含：
      1. 参与者返回no
      2. 协调者等待超时（包含参与者没有收到指令的情况）
2. `pre commit`：同二阶段第一个相同，执行sql
   1. 如果反馈结果为成功，进行`do commit`阶段
   2. 如果有反馈结果为失败，执行 `abort commit`，并会进行数据回滚
3. `do commit`：提交事务

参与者视角，可能发生超时的情况：

1. `can commit`指令阶段，参与者不存咋超时情况
2. 参与者在收到`can commit`指令后并回复，但是没有收到`pre commit`指令或`abort commit`指令，这时会执行回滚操作
3. 参与者在收到`pre commit`指令后并回复，但是没有收到`do commit`指令或`abort commit`指令，这时候会直接执行提交操作（这是一个概率问题，前两个阶段的校验已经通过，后续大概率是会成功的，所以直接提交事务较为合适）。

## 什么是TCC（Try Confirm/Cancel）解决方案？

TCC可以解决两个数据方的数据同步问题，比如Mysql和Redis的数据同步问题：

![](https://2351062869-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7b2CdwBN9liniVJpfEAc%2Fuploads%2Fgit-blob-d3db552acb29ad0e3264d286a70c28e67f0ef76c%2F20240310114250.png?alt=media) ![](https://2351062869-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7b2CdwBN9liniVJpfEAc%2Fuploads%2Fgit-blob-19f461733011a645049fbe1b023637ba24b904a0%2F20240310114647.png?alt=media) ![](https://2351062869-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7b2CdwBN9liniVJpfEAc%2Fuploads%2Fgit-blob-f9de2146c53cbdf3b81699089dc5fbe2db542e3a%2F20240310115000.png?alt=media)

1. Try阶段，张三转账100元，将余额字段减去100，但是冻结字段增加100。而李四的冻结字段会加上100。
2. 如果Try阶段成功，会执行`Confirm`，这时张三的冻结字段减100，李四的冻结字段减100，但是余额增加一百。
3. 如果Try阶段失败，会执行`Cancel`，这时张三的冻结字段减100，余额增加100，恢复到了之前的状态，而李四的冻结字段减去了100，也恢复到了之前的状态。

所谓TCC就是给整个流程执行的过程中，增加一个中间的冻结态，如果操作失败，都可以根据中间台进行逆操作，从而恢复之前的状态。

## 使用事件表+定时任务+消息队列来解决分布式事务的问题

![](https://2351062869-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7b2CdwBN9liniVJpfEAc%2Fuploads%2Fgit-blob-99710359a98ecd0b4a3c8ff48cd1539c9742e066%2F20240310115900.png?alt=media)

优点和缺点：

1. 系统的可用性高，异步处理，接口的响应速度快
2. 相应的，所有事件的处理是通过消息传递的，有可能发生数据一致性的问题，只能尽可能保证最终一致性

## 最大努力通知方案

支付回调，有可能发生回调失败的情况，可以增加重试次数。 并且，支付接口通常会提供查询接口，查询出已提交的支付信息的支付状态：

1. 重复通知机制
2. 消息校对机制
