SQL 多个事务并发时可能遇到的问题
Reference
事务并发的可能问题与其解决方案
脏读、幻读、不可重复读和丢失更新
数据库并发事务存在的问题(脏读、不可重复读、幻读等)
对于脏读,不可重复读,幻读的一点理解,看懂红字很关键
MySQL 四种事务隔离级的说明
15.7.2.1 Transaction Isolation Levels
MySQL Innodb 事务隔离级别
【漫画】如何给新来的师妹解释什么是数据库的脏读、不可重复读和幻读
多个事务并发时可能遇到的问题
- Lost Update 更新丢失 (两类)
a. 第一类更新丢失,回滚覆盖
b. 第二类更新丢失,提交覆盖 - Read Phenomena 读现象 (三种)
a. Dirty Reads 脏读
b. Non-Repeatable Reads 不可重复读
c. Phantom Reads 幻读
更新丢失
第一类更新丢失,回滚覆盖
定义
撤消一个事务时,在该事务内的写操作要回滚,把其它已提交的事务写入的数据覆盖了。
解释
实例
时间 | 取款事务 A | 转账事务 B |
---|---|---|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 读余额为 1000 | |
T4 | 取出 100, 余额改为 900 | |
T5 | 读余额为 1000 | |
T6 | 汇入 100, 余额改为 1100 | |
T7 | 提交事务, 余额定位 1100 | |
T8 | 撤销事务, 余额改回 1000 | - |
T9 | 最终余额为 1000,更新丢失 | - |
写操作没加 “持续 - X 锁”,没能阻止事务 B 写,发生了回滚覆盖。
第二类更新丢失,提交覆盖
定义
提交一个事务时,写操作依赖于事务内读到的数据,读发生在其他事务提交前,写发生在其他事务提交后,把其他已提交的事务写入的数据覆盖了。这是不可重复读
的特例。
解释
实例
时间 | 取款事务 A | 转账事务 B |
---|---|---|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 读余额为 1000 | |
T4 | 读余额为 1000 | |
T5 | 取出 100, 余额改为 900 | |
T6 | 提交事务, 余额定位 900 | |
T7 | 汇入 100,余额改为 1100 | - |
T8 | 提交事务,余额定为 1100 | - |
T9 | 最终余额为 1100,更新丢失 | - |
写操作加了 “持续 - X 锁”, 读操作加了 “临时 - S 锁”,没能阻止事务 B 写,发生了提交覆盖。
Read Phenomena 读现象
Dirty Reads 脏读
定义
一个事务读到了另一个未提交的事务写的数据。
针对未提交数据
解释
当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交(commit)到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是脏数据,依据脏数据所做的操作可能是不正确的。
实例
时间 | 取款事务 A | 转账事务 B |
---|---|---|
T1 | 开始事务 | |
T2 | 开始事务 | |
T3 | 读余额为 1000 | 查询账户余额为 1000 |
T4 | 取出 500, 余额改为 500 | |
T5 | 读余额为 500 (脏读) | |
T6 | 撤销事务,余额恢复为 1000 | |
T7 | 汇入 100,余额改为 600 | - |
T8 | 提交事务 | - |
T9 | 最终余额为 600,丢失 400 | - |
解决
修改时加排他锁,直到事务提交后才释放,读取时加共享锁,读取完释放事务 A 读取数据时加上共享锁后(这样在事务 A 读取数据的过程中,其他事务就不会修改该数据),不允许任何事务操作该数据,只能读取,之后 A 如果有更新操作,那么会转换为排他锁,其他事务更无权参与进来读写,这样就防止了脏读问题。但是当事务 A 读取数据过程中,有可能其他事务也读取了该数据,读取完毕后共享锁释放,此时事务 A 修改数据,修改完毕提交事务,其他事务再次读取数据时候发现数据不一致,就会出现不可重复读问题,所以这样不能够避免不可重复读问题。
Non-Repeatable Reads 不可重复读
定义
一个事务中两次读同一行数据,可是这两次读到的数据不一样。
针对其他提交前后,读取数据本身的对比
不可重复读重点在修改
解释
在数据库访问中,一个事务范围内两个相同的查询却返回了不同数据。这是由于查询时系统中其他事务修改的提交而引起的。比如事务T1读取某一数据,事务T2读取并修改了该数据,T1为了对读取值进行检验而再次读取该数据,便得到了不同的结果。
实例
时间 | 取款事务 A | 转账事务 B |
---|---|---|
T1 | 开始事务 | |
T2 | 读取余额为 1000 | |
T3 | 开始事务 | |
T4 | 取出 500, 余额改为 500 | |
T5 | 提交事务 | |
T6 | 再次读取余额为 500 (不可重复读取) |
解决
读取数据时加共享锁,写数据时加排他锁,都是事务提交才释放锁。读取时候不允许其他事物修改该数据,不管数据在事务过程中读取多少次,数据都是一致的,避免了不可重复读问题。
Phantom Reads 幻读
定义
同一个事务内多次查询返回的结果集不一样(比如增加了或者减少了行记录)。
针对其他提交前后,读取数据条数的对比
幻读是不可重复读的一种特殊场景。
幻读重点在新增或删除
解释
比如同一个事务A内第一次查询时候有n条记录,但是第二次同等条件下查询却又n+1条记录,这就好像产生了幻觉。
没有范围锁
实例
时间 | 取款事务 A | 转账事务 B |
---|---|---|
T1 | 开始事务 | |
T2 | 查询交易记录次数 | |
T3 | 开始事务 | |
T4 | 新增交易记录 | |
T5 | 提交事务 | |
T6 | 再次查询交易记录次数 (幻读) | - |
解决
实行 Serializable 隔离模式: 采用范围锁 RangeS - RangeS-S Mode,锁定检索范围为只读,这样就避免了幻读问题。
数据库通过锁机制解决并发访问的问题。根据锁定对象不同:分为行级锁和表级锁;根据并发事务锁定的关系上看:分为共享锁定和独占锁定,共享锁定会防止独占锁定但允许其他的共享锁定。而独占锁定既防止共享锁定也防止其他独占锁定。为了更改数据,数据库必须在进行更改的行上施加行独占锁定,insert、update、delete和selsct for update语句都会隐式采用必要的行锁定。
但是直接使用锁机制管理是很复杂的,基于锁机制,数据库给用户提供了不同的事务隔离级别,只要设置了事务隔离级别,数据库就会分析事务中的sql语句然后自动选择合适的锁。
事务的隔离级别和数据库并发性是成反比的,隔离级别越高,并发性越低。