分布式事务(一) 本地事务回顾

什么是分布式事务

要了解分布式事务,首先需要了解什么是本地事务。

本地事务

本地事务,是指传统的单机数据库事务,必须满足ACID原则。

  • 原子性(atomicity)
    整个事务中所有操作,要么一块完成,要么都不完成。
    对于事务发生错误,所有操作必须完成回滚。

  • 一致性(consistency)
    事务的执行必须保证系统的一致性,事务在开始之前和结束之后,数据库的完整性不能被破坏。
    比如在一个事务中,A(余额100)给B(余额100)转账10元,那么不管发生什么,最终A和B的账户总额必须是200元。

  • 隔离性(isolation)
    隔离性就是事务与事务之间不会互相影响,一个事务的中间状态不能被其他事务感知。数据库的隔离级别包括四种:

    1. Read UmCommitted 读取未提交内容
    2. Read Committed 读取提交内容
    3. Repeatable Read 可重复读
    4. Seriallzable 可串行化
  • 持久性(durability)
    事物一旦执行完毕,那么事务对数据进行的变更就完全保存在数据库硬盘中,即使断电、宕机也不会改变。

传统项目中,项目部署基本都是单点式,这种情况下,数据库本身的事务机制就能保证ACID的原则,这样的事务叫做本地事务。
其中原子性和持久性需要依靠undo和redo日志来实现。

undo和redo日志

数据库中数据从来不是最重要的,日志才是。

在数据库系统中,既有存放数据的文件,也有存放日志的文件。日志在缓存中也有log buffer,也有磁盘log file。
MySQL的日志文件,有两种与事务有关,undo log和redo log

undo日志

介绍

数据库事务具备原子性(Atomicity),如果事务执行失败,需要把数据回滚。
事务同时还具备持久性(durability),事务对数据做的更改完全保存在数据库,不能因为故障而丢失。
原子性可以使用undo log来实现。

原理

undo log的原理很简单,为了满足事务的原子性,在操作任何数据之前都会将数据备份到undo log中然后再对数据进行更改。如果执行出错或者手动回滚后,系统可以利用undo log中备份的数据恢复到事务开始之前的状态。
数据库写入数据到磁盘之前,会把数据先缓存在内存中。事务提交才写入到磁盘。

实例

用undo log实现原子性和持久性的简化过程:
假设有A=1, B=2两条数据。

实例.png
  • 如何保证持久性?

事务提交前,会把修改数据记录到磁盘中,只要事务提交了,数据肯定持久化了。

  • 如何保证原子性?

每次对数据库修改,都会把修改前记录保存在undo log中,需要回滚时可以直接读取undo log恢复数据。

缺陷

每个事务提交前都需要将数据和undo log写入到磁盘,导致大量磁盘IO,性能差。

redo日志

和undo log相反,redo log是对新数据的备份,在事务提交之前,只要将redo log持久化即可,不需要将数据持久化,减少IO操作。

实例

用undo log + redo log 实现原子性和持久性的简化过程:
假设有A=1, B=2两条数据。

  1. 事务开始
  2. 记录A=1到undo log buffer
  3. 修改A=3
  4. 记录A=3到redo log buffer
  5. 记录B=2到undo log buffer
  6. 修改B=4
  7. 记录B=4到redo log buffer
  8. 将undo log写入redo log
  9. 将redo log写入磁盘
  10. 事务提交
  • 如何保证持久性

整个过程数据并未持久化,因为数据已经写入到redo log中,而redo log已经写入到磁盘中,因此只要进行到(9)后,事务是可以提交的。

  • 如何保证原子性

如果在事务提交前故障,通过undo log恢复数据,如果undo log还未写入(8),那么数据尚未持久化,无需回滚。

  • 内存中数据何时持久化到硬盘

因为redo log已经持久化,因此是否写入硬盘已经不重要了。但是一般为了避免内存数据与数据库数据不一致,在事务提交后或者会固定频率刷新到数据库中。

问题

  • 之前是将数据库数据和undo log写入磁盘,现在是将undo log和redo log写入磁盘,IO次数并没有减少吗?
  1. 数据库数据写入是随机的,性能差
  2. redo log在初始化时会开辟一段连续空间,写入是顺序IO,性能高
  3. 实际上undo log并不是直接写入磁盘,而是写入到redo log buffer中,当redo log持久化时,undo log也顺便持久化了。
    因此事务在提交前只需要将redo log持久化即可。
    另外redo log并不是写入一次就持久化一次,redo log在内存中有redo log buffer缓冲池,在最终数据库事务提交时一次性持久化,减少IO次数。
  • redo log buffer已满(第8步)但事务未提交时会写入buffer数据到硬盘,而此时如果数据库宕机事务未提交已保存的部分redo log怎么处理。
  1. 恢复时,只重做已经提交了的事务。
  2. 恢复时,重做所有事务包括未提交的和回滚的事务,然后通过undo log回滚哪些未提交的事务。
    innodb采用了方案2,因此undo log要在redo log前持久化。

总结

undo log记录旧数据,redo log记录最新数据。

分布式事务

跨数据源

对数据库进行水平或垂直拆分,将原表数据拆分成数据库分片。于是就产生了跨数据库事务问题。
因为ACID是数据库内部的,不能解决多个数据库实例之间的问题。
multiDS

跨服务

对单体项目进行微服务拆分后,将原有的Spring Transaction Manager拆分到每个微服务之间都有自己的Spring事务管理器。导致出现分布式事务问题。

microService

分布式系统的数据一致性问题

当出现部分事务成功,部分事务失败时,业务数据就会不一致。
例如电商下单场景,包括以下几个行为:

  1. 创建订单
  2. 扣减库存
  3. 扣减余额
    完成上面三个动作需要三个微服务和三个不同的数据库,一旦其中任何一个失败,其它的服务之间都无法感知,就会造成数据的不一致问题。
    这正是分布式事务要解决的问题。
    microServiceSimple