1. 数据库事务的四大特性(ACID)

  1. 原子性(Atomicity): 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用。
  2. 一致性(Consistency): 执行事务前后,数据保持一致,数据库总是从一个一致性的状态转换到另外一个一致性的状态。比如A转账给B100块钱,假设中间sql执行过程中系统崩溃A也不会损失100块,因为事务没有提交,修改也就不会保存到数据库。
  3. 隔离性(Isolation): 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的。
  4. 持久性(Durability): 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。

那么 ACID 靠什么保证的呢?

  • A原子性由 undo log 日志保证,它记录了需要回滚的日志信息,事务回滚时撤销已经执行成功的sql。
  • C一致性一般由代码层面来保证。
  • I隔离性由MVCC来保证。
  • D持久性由内存+redo log来保证,mysql修改数据同时在内存和redo log记录这次操作,事务提交的时候通过redo log刷盘,宕机的时候可以从redo log恢复。

    2. 事务的隔离级别

  1. READ-UNCOMMITTED(读取未提交): 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、不可重复读、幻读。项目中没有人用,但性能最高。
  2. READ-COMMITTED(读取已提交): 只允许读取并发事务已经提交的数据,可以阻止脏读,但是不可重复读或幻读仍有可能发生。它保证了事务不出现中间状态的数据,所有数据都是已提交且更新的。
  3. REPEATABLE-READ(可重复读):MySQL InnoDB 引擎的默认隔离级别,对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生
  4. SERIALIZABLE(可串行化): 最高的隔离级别,完全服从 ACID 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。选择“可串行化”意味着读取数据时,需要获取共享读锁;更新数据时,需要获取排他写锁;如果 SQL 使用 WHERE 语句,还会获取区间锁。换句话说,事务 A 操作数据库时,事务 B 只能排队等待,因此性能也最低。

数据库事务 - 图1
MySQL InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重复读)。InnoDB 存储引擎在 REPEATABLE-READ(可重复读) 事务隔离级别下使用 Next-Key Lock 锁算法,因此可以避免幻读的产生,达到了 SERIALIZABLE(可串行化) 隔离级别。

3. 事务并发可能引起的问题

  • 更新丢失(Lost Update):指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。 例如:事务1读取某表中的数据A=20,事务2也读取A=20,事务1修改A=A-1,事务2也修改A=A-1,最终结果A=19,事务1的修改被丢失。
  • 脏读(Dirty Reads):表示一个事务能够读取另一个事务中还未提交的数据。这个数据是脏数据,依据脏数据所做的操作可能是不正确的。
  • 不可重复读(Non-Repeatable Reads):指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
  • 幻读(Phantom Reads):幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入或删除了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了或少了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。

不可重复读的重点是修改比如多次读取一条记录发现其中某些列的值被修改,幻读的重点在于新增或者删除比如多次读取一条记录发现记录增多或减少了。

4. 分布式事务产生的场景

随着业务的快速发展,网站系统往往由单体架构逐渐演变为分布式、微服务架构,而对于数据库则由单机数据库架构向分布式数据库架构转变。此时,我们会将一个大的应用系统拆分为多个可以独立部署的应用服务,需要各个服务之间进行远程协作才能完成事务操作。这种分布式场景下就会产生分布式事务问题。

4.1 跨 JVM 进程

当我们将单体项目拆分为分布式、微服务项目之后,各个服务之间通过远程REST或者RPC调用来协同完成业务操作。典型的场景就是:商城系统中的订单微服务和库存微服务,用户在下单时会访问订单微服务,订单微服务在生成订单记录时,会调用库存微服务来扣减库存。各个微服务是部署在不同的JVM进程中的,此时,就会产生因跨JVM进程而导致的分布式事务问题。数据库事务 - 图2

4.2 跨数据库实例

单体系统访问多个数据库实例,也就是跨数据源访问时会产生分布式事务。例如,我们的系统中的订单数据库和交易数据库是放在不同的数据库实例中,当用户发起退款时,会同时操作用户的订单数据库和交易数据库,在交易数据库中执行退款操作,在订单数据库中将订单的状态变更为已退款。由于数据分布在不同的数据库实例,需要通过不同的数据库连接会话来操作数据库中的数据,此时,就产生了分布式事务。
数据库事务 - 图3

4.3 多服务单数据库

多个微服务访问同一个数据库。例如,订单微服务和库存微服务访问同一个数据库也会产生分布式事务,原因是:多个微服务访问同一个数据库,本质上也是通过不同的数据库会话来操作数据库,此时就会产生分布式事务。
数据库事务 - 图4

跨数据库实例场景和多服务单数据库场景,本质上都是因为会产生不同的数据库会话来操作数据库中的数据,进而产生分布式事务。这两种场景是大家比较容易忽略的。

5. 分布式事务技术理论

5.1 CAP 理论

CAP 理论是指一个分布式系统最多只能同时满足一致性(Consistency)、可用性(Availability)和分区容错性(Partition tolerance)这三项中的两项。

C:(Consistency)数据一致性

假设我们的分布式存储系统有两个节点,每个节点都包含了一部分需要被变化的数据。如果经过一次写请求后,两个节点都发生了数据变化。然后,读请求把这些变化后的数据都读取到了,我们就把这次数据修改称为数据发生了一致性变化。
数据库事务 - 图5

A:(Availability)可用性

可用性要求系统内的节点们接收到了无论是写请求还是读请求,都要能处理并给回响应结果。只是它有两点必须满足的条件:

  1. 返回结果必须在合理的时间以内,这个合理的时间是根据业务来定的。业务说必须 100 毫秒内返回,合理的时间就是 100 毫秒,需要 1 秒内返回,那就是 1 秒,如果业务定的 100 毫秒,结果却在 1 秒才返回,那么这个系统就不满足可用性。
  2. 需要系统内能正常接收请求的所有节点都返回结果。这包含了两重含义:
    1. 如果节点不能正常接收请求了,比如宕机了,系统崩溃了,而其他节点依然能正常接收请求,那么,我们说系统依然是可用的,也就是说,部分宕机没事儿,不影响可用性指标。
    2. 如果节点能正常接收请求,但是发现节点内部数据有问题,那么也必须返回结果,哪怕返回的结果是有问题的。比如,系统有两个节点,其中有一个节点数据是三天前的,另一个节点是两分钟前的,如果,一个读请求跑到了包含了三天前数据的那个节点上,抱歉,这个节点不能拒绝,必须返回这个三天前的数据,即使它可能不太合理。

      P:(Partition tolerance)分区容忍性

      分布式的存储系统会有很多的节点,这些节点都是通过网络进行通信。而网络是不可靠的,当节点和节点之间的通信出现了问题,此时,就称当前的分布式存储系统出现了分区。但是,值得一提的是,分区并不一定是由网络故障引起的,也可能是因为机器故障。

      选择 CP 还是 AP?

      虽然 CAP 理论定义是三个要素中只能取两个,但放到分布式环境下来思考,我们会发现必须选择 P(分区容忍)要素,因为网络本身无法做到 100% 可靠,有可能出故障,所以分区是一个必然的现象。如果我们选择了 CA 而放弃了 P,那么当发生分区现象时,为了保证 C,系统需要禁止写入,当有写入请求时,系统返回 error(例如,当前系统不允许写入),这又和 A 冲突了,因为 A 要求返回 no error 和 no timeout。因此,分布式系统理论上不可能选择 CA 架构,只能选择 CP 或者 AP 架构。

当一套系统在发生分区故障后,客户端的任何请求都被卡死或者超时,但是,系统的每个节点总是会返回一致的数据,则这套系统就是 CP 系统,经典的比如 Zookeeper。

如果一套系统发生分区故障后,客户端依然可以访问系统,但是获取的数据有的是新的数据,有的还是老数据,那么这套系统就是 AP 系统,经典的比如 Eureka。

5.2 BASE 理论

BASE 理论,分别是以下三个单词的缩写:

  • Basically Available(基本可用):分布式系统在出现故障时,允许损失部分可用功能,保证核心功能可用。
  • Soft state(软状态):允许系统中存在中间状态,这个状态不影响系统可用性,这里指的是 CAP 中的不一致。
  • Eventually consistent(最终一致性):最终一致是指经过一段时间后,所有节点数据都将会达到一致。

BASE 是对CAP 中 AP 方案的一种补充。在 BASE 中用软状态和最终一致,保证了延迟后的一致性。BASE 和 ACID 是相反的,ACID 是一种强一致性模型,而 BASE 却是牺牲这种强一致性,允许数据短时间内不一致,最终一致性。

6. 分布式事务解决方案

6.1 2PC 方案

TODO

6.2 可靠消息最终一致性方案

TODO

6.3 TCC 方案

TODO

6.4 最大努力通知型方案

TODO


参考

  1. 12张图带你彻底理解分布式事务产生的场景和解决方案!!
  2. 什么是CAP?
  3. 面试官问了我分布式事务,我感觉他有想给我40k的冲动
  4. 如何优雅地回答 MySQL 的事务隔离级别和锁的机制?