写在前面:
数据库是面试的一大重点,在上一篇博客中,我们已经总结了数据库的三大范式,在这篇博客中,将会总结另一个总会被问到的问题--事务的ACID和事务隔离级别。
事务(Transaction):
在计算机语言中,一般指访问并更新数据库中的一个程序执行单元。 事务具有的四个特点,称为ACID。在数据库的并发访问中,为了保证数据的读取正确性,设置的事务的隔离级别。
事务的ACID:
- A:(Atomicity) 原子性,一个事务是一个不可分割的单位,事务中的所有操作,要么全完成,要么全不完成,任何一个操作的失败,都会回滚到事务执行之前的状态。
- C:Consistentcy 事务结束后,系统状态是一致的。即,在并发操作时,系统的状态也要和串行执行事务时一样。
例如: ABCDE五个账户,每个账户有100元,共计500元,A给B转账10元,B给C转账5元,C给D转账5元,D给E转账10元,E给A转账10元,当转账同时进行,模拟并发操作数据库,最后5个账户的总额度仍旧应该是500元。
- I:Isolation 隔离性,并发执行的事务之间,无法看到彼此的系统状态。
- D:Durability 持久性,在事务完成后,事务对数据库的操作会被持久保存在数据库中,不会被回滚。
多事务访问产生的问题:
(1)脏读(Dirty Read):A读取B事务尚未提交的事务,并修改了B的数据,然后B对事务执行回滚,那么A读取到的数据为脏读。
时间 | 事务A | 事务B |
T1 | 事务开始 | |
T2 | 事务开始 | |
T3 | 查询账户余额=500元 | |
T4 | 账户+500元 | |
T5 | A查询到账户数据为1000元(脏读) | |
T6 | A取出200元 | |
T7 | 回滚事务 | |
T8 | 提交事务 |
(2)不可重复读(Unrepeatable Read):事务A读取数据,然后事务B对事务A的数据进行了修改,此后,事务A再次读取数据,这时为不可重复读,最后A提交事务。所以在A的一次事务中,两次读取到的数据不一致。
时间 | 事务A | 事务B |
T1 | 事务开始 | |
T2 | 事务开始 | |
T3 | 读取账户余额=1000元 | |
T4 | 读取账户余额=1000元 | |
T5 | 账户+500元 | |
T6 | 提交事务 | |
T7 | 读取账户余额=1500元(不可重复读) |
(3)幻读(Phantom Read):事务A查询数据,查询一切符合查询条件的行,这时B新增加一个行数据,A事务再次查询时,发现与第一次查询相比,新增加了一行。
时间 | 事务A | 事务B |
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 查询所有符合条件账户=100的行 | |
T4 | 新增加一个账户行,并且账户=100 | |
T5 | 提交事务 | |
T6 | 再次查询所有符合条件账户=100的行 |
在看过了上面数据并发访问时可能产生的问题,在有些场景下可能是允许的,但是有些场景下可能就是致命的,数据库通常会通过锁机制来解决数据并发访问问题,按锁定对象不同可以分为表级锁和行级锁;按并发事务锁定关系可以分为共享锁和独占锁。
直接使用锁是非常麻烦的,为此数据库为用户提供了自动锁机制,只要用户指定会话的事务隔离级别,数据库就会通过分析SQL语句然后为事务访问的资源加上合适的锁。
隔离级别
✔️表示允许,❌表示不允许
隔离级别 | 脏读 | 不可重复读 | 幻读 |
Read uncommited | ✔️ | ✔️ | ✔️ |
Read commited | ❌ | ✔️ | ✔️ |
Reapeatable Read | ❌ | ❌ | ✔️ |
Serializable | ❌ | ❌ | ❌ |