1. 絮絮叨叨
- 重温Apache ORC时,发现ORC支持ACID
- 想起自己之前一度不知道ACID是哪些单词的缩写,更别提面试中常提到的事物隔离级别等知识了
- 因此,特地学习一下数据库中事务的ACID
2. ACID
2.1 What’s transaction?
- 考虑一个真实场景,账户A向账户B转账2000元,将涉及两次数据库写操作:
- update账户A的记录,余额balanceA = balanceA - 2000
- update账户B的记录,余额balanceB = balanceB + 2000
- 由于某些意外,转账操作结束时,有两种不被银行系统所允许的情况出现了:
- 账户A的余额已减少,账户B却没有增加2000元
- 账户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读取到的数据是无效的
- 若事务A成功执行,则事务B读取的数据的是有效的
- 若事务A由于某些原因发生了回滚,则事务B读取到的数据是无效的
- 此时,无法保证该数据是否有效,该数据被视为脏数据,对应的read操作被称为脏读
3.1.2 不可重复读(non-repeatable read)
- 在一个事务内多次读取同一条数据,由于其他事务对该数据的更新,使得前后两次读取到的值不一致
- 例如,事务A、事务B同时读写lucy的银行账户。事务B提交后,lucy的账户余额变成了200万,事务B前后两次查询余额得到结果时不一致的
- 余额增加,对事务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个
- 甚至揉了几下眼睛、还掐了自己的大腿,以确定这不是在做梦😂
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的账户余额表进行读写操作,在不同的隔离级别下,执行结果会有所差异
- 读未提交:在事务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技术,后续可以深入学习一下该技术