事务的ACID特性

1. 絮絮叨叨

  • 重温Apache ORC时,发现ORC支持ACID
  • 想起自己之前一度不知道ACID是哪些单词的缩写,更别提面试中常提到的事物隔离级别等知识了
  • 因此,特地学习一下数据库中事务的ACID

2. ACID

2.1 What’s transaction?

  • 考虑一个真实场景,账户A向账户B转账2000元,将涉及两次数据库写操作:
    1. update账户A的记录,余额balanceA = balanceA - 2000
    2. update账户B的记录,余额balanceB = balanceB + 2000
  • 由于某些意外,转账操作结束时,有两种不被银行系统所允许的情况出现了:
    1. 账户A的余额已减少,账户B却没有增加2000元
    2. 账户B的余额已增加,账户A却没有减少2000元
  • 也就是说,转账操作中涉及的两次数据库写操作,要么都成功,要么都失败,不允许处于中间态(in-between state
  • 这两次数据库的写操作,就是一个事务(transaction)
  • 事务是一组数据库的读写操作,这组操作是一个不可分割的整体,要么全部完成,要么都不执行,以保证数据库的一致性

2.2 ACID

  • ACID是单词Atomicity(原子性)、Consistency(一致性)、Isolation(隔离性)、Durability(持久性)的首字母缩写,是数据库支持事务所必须遵守的4个特性
  • PS: 支持事务的数据库,又叫事务型数据库(transactional databse)

2.2.1 Atomicity

  • 原子性,可能是4个特性中最好理解的一个特性,可能原子操作已经深入程序员的心了吧
  • 原子性,将事务中的所有操作视为一个整体,要么都成功执行,要么都不执行。
  • 在事务的执行过程中,若某个操作失败或被中断,需要回滚(roll back)已执行的操作,使数据库恢复到事务执行之前的状态
  • 原子性可以保证数据库状态的确定性,即使在数据库崩溃或断电的情况下
  • 事务的原子性,一般都是使用日志预写技术(Write Ahead Log,WAL)中的undo日志实现

2.2.2 Consistency

  • 一致性,就是事务对数据库的更改,必须与数据库约束一致
  • 一旦破坏数据库约束,会使数据库进入非法状态,事务将被中止并进行回滚操作
  • 数据库约束,一般是为了保证数据的完整性所设置
  • 例如,转账前后,余额不能为负数。账户A原本只有2000元,却想向账户B转账2400元,这样的转账事务将被中止、回滚
    是指事务操作前和操作后,数据满足完整性约束,数据库保持一致性状态。

2.2.3 Isolation

  • 多个用户同时读写同一张表,这些事务必须在一个隔离的环境中运行,以保证互不干扰或影响
  • 隔离性,并不意味着事务不能并发执行,而是要对事务进行全局排序(global order),互不影响的事务可以同时执行,而相互影响的事务需要按序执行
  • 例如,余额为2000元的账户,同时发起两笔转账交易,每笔交易金额为1000元。转账交易完成后,账户余额为0元,而非1000元

2.2.4 Durability

  • 持久性,是指事务成功执行后,对数据所做的改动会被持久化存储,即使数据库崩溃或断电
  • 例如,账户A余额为2000元,向账户B成功转账1000元后,余额变成1000元。即使银行系统崩溃,账户A余额不允许回退到2000元,或者减少至0元
  • 事务的持久性,一般都是使用日志预写技术(Write Ahead Log,WAL)中的redo日志实现

3. 事务的隔离性

3.1 并发事务,将会带来哪些影响?

3.1.1 脏读(dirty read)

  • 事务执行完成,该事务对数据库所有操作都已提交
  • 事务B读取到了事务A未提交的数据
    • 若事务A由于某些原因发生了回滚,则事务B读取到的数据是无效的
      事务的ACID特性_第1张图片
    • 若事务A成功执行,则事务B读取的数据的是有效的
  • 此时,无法保证该数据是否有效,该数据被视为脏数据,对应的read操作被称为脏读

3.1.2 不可重复读(non-repeatable read)

  • 在一个事务内多次读取同一条数据,由于其他事务对该数据的更新,使得前后两次读取到的值不一致
  • 例如,事务A、事务B同时读写lucy的银行账户。事务B提交后,lucy的账户余额变成了200万,事务B前后两次查询余额得到结果时不一致的
    事务的ACID特性_第2张图片
  • 余额增加,对事务A对应的真实业务场景来说可能影响不大,但是余额减少影响就大了
    • 例如,购买一套房子要求账户余额不少于100万,第一次资格审核时,lucy可以买房
    • 签合同前,再次确认买房资格时,由于lucy的老公jack偷偷取了50万,发现账户余额为50万,lucy将丧失买房资格
    • 对lucy来说,这可是晴天霹雳啊
  • 这种前后两次读取同一条数据,值却不一致的现象,被称为不可重复读

3.1.2 幻读(phantom read)

  • 在一个事务内多次执行同一个查询,前后两次的查询结果却不一致,被称作幻读
  • 例如,由于事务A增加了一条余额大于100万的账户记录,导致事务B第二次查询余额大于100万的账户数量时,发现账户数从5变成了6
    • 事务B对应银行柜员,要求本月100万存款的客户至少6个
    • 第一次查询时,发现自己业绩不达标;第二次查询,简直以为自己眼睛花了,咋100万存款的客户变成了6个
    • 甚至揉了几下眼睛、还掐了自己的大腿,以确定这不是在做梦
      事务的ACID特性_第3张图片

3.1.4 不可重复读 VS 幻读

  • 不可重复读、幻读,都是两次查询的结果不一致,但二者的侧重有所不同
  • 不可重复读是查询同一条数据时,前后两次的值有差异

    A non-repeatable read occurs, when during the course of a transaction, a row is retrieved twice and the values within the row differ between reads.

  • 幻读是执行同一个查询时,前后两次返回的结果集有差异
  • 例如,第一次查询age > 10的学生,有108条记录;第二次查询时,由于有学生转校进来,变成109条记录

    A phantom read occurs when, in the course of a transaction, two identical queries are executed, and the collection of rows returned by the second query is different from the first.

  • 至于二者的差异,stackoverflow中的一个回答非常棒:What is the difference between Non-Repeatable Read and Phantom Read?

3.2 事务隔离级别

3.2.1 隔离级别的介绍

  • 并发事务引发的脏读、不可重复度、幻影读三大问题,严重性排序如下:

    脏读 > 不可重复读 > 幻读
    
  • 需要采取一定的隔离措施,来避免这些问题的出现

  • SQL标准中提出了四种隔离级别,隔离级别越高,隔离效果越好,事务的执行性能越低

    读未提交(read uncommitted) > 读已提交(read committed) > 可重复读(repeatable read) > 串行化(serializable )
    
  • 读未提交:

    • 一个事务还未提交,它对数据库所做的变更就能被其他事务读到
    • 这是最低的隔离级别,存在脏读、不可重复读、幻读的问题,不会使用该隔离级别
  • 读已提交:

    • 一个事务提交后,它对数据库所做的变更才能被其他事务读到
    • 读已提交解决了脏读的问题,有资料说:大多数的数据库默认级别就是读已提交,如Sql Server、Oracle (有待验证
  • 可重复读:

    • 若事务自身不对某条数据做更改,则在事务的整个执行过程中,即使其他事务更新了这条数据,但该事务读到的这条数据始终不变
    • 对于可重复读,最容易想到的方法是:让事务读取数据的快照(或者是临时视图),其他事务对数据的修改不会影响快照
    • 可重复读并不能解决幻读的问题,但有资料说:可重复读是Mysql InnoDB 引擎的默认隔离级别,在很大程度上还避免了幻读问题
  • 串行化:

    • 在该隔离级别下,事务顺序执行,后一个事物的执行必须等待前一个事务的执行,自然也就不存在脏读、不可重复读、幻读三大问题
    • 这是最强的隔离级别,一般需要对数据加锁,导致数据库性能变差,一般不推荐使用

  • 在并发事务下,这些隔离级别能解决()或仍然存在(X)的问题,总结如下:

    隔离级别 脏读 不可重复读 幻读
    读未提交 X X X
    读已提交 X X
    可重复读 X
    串行化

3.2.2 基于隔离级别,推导事务执行结果

  • 事务A和事务B都对lucy的账户余额表进行读写操作,在不同的隔离级别下,执行结果会有所差异
    事务的ACID特性_第4张图片
  • 读未提交:在事务B未提交前,事务A查询到余额V1为200万
  • 读已提交:在事务B未提交前,事务A查询到余额V1为100万;事务B提交后,事务A查询到的余额V2变200万,出现了不可重复读的问题
  • 可重复读:不管事务B是否提交,事务A查询到的余额V1和V2都是100万;事务A提交后,查询到的余额为200万
  • 串行化:事务B在事务A读取余额后,发起了update操作,存在读写冲突,事务B会暂停,直到事务A已提交

4. 后记

4.1 参考链接

  • 对ACID的介绍,mongdb的这篇文章,在笔者看来是最好的:What are ACID Properties in Database Management Systems?
  • 脏读与读未提交:Dirty Reads and the Read Uncommitted Isolation Level
  • 幻读的示意图:Phantom Read Problem in SQL Server
  • MySQL知识汇总:
    • 事务隔离级别是怎么实现的?(笔者虽然不是服务器开发方向的,但觉得这些文档应该是针对面试总结的)
    • MySQL事务隔离级别和实现原理

4.2 絮絮叨叨

  • 在学习的过程中,还发现了之前经常遇到,但未深入了解的WAL技术
  • 很多数据存储系统中,都是使用到了WAL技术,后续可以深入学习一下该技术

你可能感兴趣的:(数据库,数据库)