0%

数据库事务 - ACID特性与隔离级别

什么是事务

事务是恢复和并发控制的基本单位。

ACID特性

  • Atomicity
    原子性:事务中的所有操作要么都成功执行,要么都取消执行,不存在部分执行,部分取消的情况。
  • Consistency
    一致性:事务前后数据的完整性必须保持一致。
    可以理解为,从一个一致的状态转换到另一个一致的状态
    事务的一致性决定了一个系统设计和实现的复杂度,也导致了事务的不同隔离级别。
    ACID就是说事务能够通过AID来保证这个C的过程,C是目的,AID都是为保证C而存在的。
  • Isolation
    隔离性:并发事务之间互相影响的程度,隔离性是分级别的,eg. 读未提交、读已提交、可重复读等。
  • Durability
    持久性:事务提交后对数据的更改是永久性的,不会丢失。

隔离级别

  1. 并发事务的几个问题

    • 脏读
      一个事务读取了另一个事务未提交的数据。
    • 不可重复读
      一个事务内,多次读取结果不一样。
    • 幻读
      按照官方的说法:

      The so-called phantom problem occurs within a transaction when the same query produces different sets of rows at different times. For example, if a SELECT is executed twice, but returns a row the second time that was not returned the first time, the row is a “phantom” row…>>
      这样的定义很容易跟“不可重复读”混淆,但实际这个定义下面又给出了一个示例,意指如果 SELECT…FOR UPDATE 不加Gap Lock(间隙锁),则会查出其他事务在此范围内提交的 INSERT 行,则事务中再次 SELECT 返回的“新”行就是“幻像”。
      所以,幻读的定义更偏向于 INSERT,即:在同一个事务中,查询结果集包含之前没有的记录。

  2. 事务的隔离级别
    隔离级别主要影响的是修改数据在事务提交前后,另一个进行中的事务读取到的是修改前的数据还是修改后的数据。
    比如有两个并发事务,如下用 A 和 B 来表示:

    级别 名称(直译) 解释 示例 脏读 不可重复读 幻读
    Read Uncommitted 读-未提交 一个事务可以读到另一个事务未提交的结果 B读取了A还未提交的数据
    Read Committed 读-已提交 只有在事务提交后,其更新结果才会被其他事务看见 B在A提交的前后读取的数据不一样 x
    Repeatable Read 可重复读 在一个事务中,对于同一份数据的读取结果总是相同的,无论是否有其他事务对这份数据进行操作,以及这个事务是否提交 B中多次查询(普通SELECT),结果是一样的 x x
    Serializable 可序列化 事务串行化执行,隔离级别最高,牺牲了系统的并发性 - x x x

    MySQL默认RR,SqlServer、Oracle默认RC。

实现原理简述

  1. 原子性、一致性、持久性通过数据库的 Redo LogUndo Log 来完成。
    Redo Log:重做日志,用来保证事务的原子性和持久性。
    Undo Log:回滚日志,用来保证数据库的一致性。
  2. 隔离性是通过锁机制来实现的。
    • 悲观锁
      按粒度分:Table Lock / Row Lock [ 又分 Gap Lock / Record Lock / Next-Key Lock ] /… 。
      按类型分:X锁 [ 排他锁,又称写锁 ] / S锁 [ 共享锁,又称读锁 ] / IX锁 / IS锁 /…。

      Intention Lock,意向锁,为 表级锁,作用是:当一个事务在需要获取资源(行 或 表)的锁定时,若事务成功申请到该资源的排他锁,则会自动给该事务申请一个该资源所属表的意向排他锁。若资源加的是共享锁,就申请表的意向共享锁。比如,SELECT…LOCK IN SHARE MODE加的是IS锁,SELECT…FOR UPDATE加的是IX锁…>>

    • 乐观锁
      不同事务可以看到资源的不同历史版本。

      MySQL的InnoDB通过MVCC(Multi-Version Concurrency Control,多版本并发控制)一致性视图(read-view)来实现;
      MVCC的核心是 Undo Log。
      通常说的“快照读”就是MySQL使用 MVCC 机制,读取之前版本数据,这些旧版本记录不会且也无法再去修改,就像快照一样,所以将这种查询称为快照读

注:对于 MVCC / 一致性视图 / 快照读 / 当前读 / Undo Log / Redo Log 等概念,我们后面再整一篇来讲。

几个问题

  • “不可重复读”有什么问题吗,为什么需要“可重复读”
    有同学会问:及时读取其他事务已提交的生效数据不是更好吗,为什么需要重复读?
    比如,小明的账号里有100元,刚好准备支付80元的订单,此时,他家人从账户上转走了50元,小明支付订单时提示“余额不足”。
    虽然小明会显得很惊讶,但这样的逻辑不是更符合当前的账户余额吗。
    确实,从逻辑上看,没毛病,但从并发事务的角度看,当前事务受到了影响(小明无法完成订单)。
    而隔离级别本身是为了解决并发事务产生的问题,所以,为了保障当前事务,在事务中需要可重复读,同时对并发资源进行加锁(S锁或X锁,防止被其他事务修改)。
    参考:https://segmentfault.com/q/1010000018612645

  • RR / 幻读 的探讨
    RR下,当前事务 UPDATE 其他事务提交的 INSERT 记录后,再次 SELECT 会查出“幻像”记录,eg:

    事务A 事务B
    1
    mysql> begin;
    1
    2
    3
    4
    5
    6
    7
    mysql> select * from tb;
    +----+---+
    | id | b |
    +----+---+
    | 1 | 3 |
    +----+---+
    1 row in set
    1
    mysql> begin;
    1
    mysql> insert into tb values (2, 4);
    1
    2
    3
    4
    5
    6
    7
    mysql> select * from tb;
    +----+---+
    | id | b |
    +----+---+
    | 1 | 3 |
    +----+---+
    1 row in set
    1
    mysql> commit;
    1
    2
    3
    mysql> update tb set b = 5 where id = 2;
    Query OK, 1 row affected
    Rows matched: 1 Changed: 1 Warnings: 0
    1
    2
    3
    4
    5
    6
    7
    8
    mysql> select * from tb;
    +----+---+
    | id | b |
    +----+---+
    | 1 | 3 |
    | 2 | 5 |
    +----+---+
    2 rows in set
    理解:UPDATE 是当前读,所以可以更新到事务B已提交的INSERT记录,但事务A中的SELECT是快照读,UPDATE之后的SELECT是否生成了新的快照? 上面提到,“快照读”就是MySQL使用 MVCC 机制,读取之前版本数据。

    每行数据都可以有多个版本,每个版本都有一个字段 trx_id,记录生成这个版本的事务的ID。
    事务中,UPDATE先当前读,再更新后生成新版本的数据,因为在事务A环境下执行的UPDATE,所以这个新版本的 trx_id 指向事务A,而接下来的SELECT依旧是读取 trx_id 指向事务A的记录,所以可以读取到 id=2 的记录。

  • RR下如何解决幻读?
    MySQL提供了 Next-Key Lock


参考资料

理解事务的4种隔离级别,https://blog.csdn.net/qq_33290787/article/details/51924963
数据库隔离级别,https://www.cnblogs.com/zhuyeshen/p/11005226.html
事务隔离级别,https://www.cnblogs.com/schaepher/p/12798577.html
幻读到底是什么?https://zhuanlan.zhihu.com/p/103580034
如何理解数据库事务中的一致性,https://www.zhihu.com/question/31346392
Mysql可重复读(1)- 快照何时创建,https://zhuanlan.zhihu.com/p/55819387
Mysql可重复读(2)- 快照真的就是快照吗,https://zhuanlan.zhihu.com/p/55872397
MySQL 到底是怎么解决幻读的?https://bbs.csdn.net/topics/392669639
MySQL的RR隔离级别与幻读问题,https://www.jianshu.com/p/4c02a3a2e9d2
MySQL可重复读,https://mp.weixin.qq.com/s/efWDxdqO4TV2IY7x8LBx3w
MVCC中查询和更新的区别,https://blog.csdn.net/u010825931/article/details/103899235
数据库事务的概念及其实现原理,https://www.cnblogs.com/takumicx/p/9998844.html