目录
▎事务隔离级别定义
了解:三种读现象(Read phenomena)
▎幻读:在可重复读级别下的 “特殊场景”
所以:MySQL的 InnoDB引擎在RR级别下到底能否解决幻读p>
RR级别下InnoDB解决常规幻读的原理
▎总结
前言:在解决这个问题之前, 我们先复习一下关于事务隔离的定义。
▎事务隔离级别定义
数据库事务的ACID四个属性中,隔离性是一个限制最宽松的。为了获取更高的隔离等级,DBMS通常使用 锁机制 或者 多版本并发控制 机制。 应用软件也需要额外的逻辑来使其正常工作。
很多数据库管理系统(DBMS)定义了不同的 “事务隔离等级” 来控制锁的程度。大多数据库系统中,都不会选择最高等级: 可串行化 隔离级别,从而减少锁的开销。
? 低一级的隔离级别要求更多的限制,高一级的级别提供更强的隔离性。
更高的隔离级别会增加死锁发生的几率,需要编程过程中去避免。
SQL定义的标准隔离级别如下
隔离级别 | 脏读 | 不可重复读 | 幻读 |
Read Uncommitted (读未提交) |
存在 | 存在 | 存在 |
Read Committed (读已提交) |
– | 存在 | 存在 |
Repeatable Read (可重复读)默认 |
– | – | 存在 |
Serializable (可串行化) |
– | – | – |
1. 读未提交(Read Uncommitted)
最低的隔离级别。允许“脏读”(dirty reads),事务可以看到其他事务“尚未提交”的修改。
2. 读已提交(Read Committed)
基于锁机制并发控制的DBMS,会对选定对象的写锁一直保持到事务结束,但是读锁在select操作完成后马上释放(因此“不可重复读”现象可能会发生)。
3. 可重复读(Repeatable Read)
基于锁机制并发控制的DBMS,会对选定对象的读锁 和 写锁一直保持到事务结束,但不要求“范围锁”,因此可能会发生“幻影读” 。【重点】
4. 可串行化(Serializable)
最高的隔离级别。基于锁机制并发控制的DBMS上,同样会对选定对象上的读锁 和 写锁直到事务结束后才能释放。在select的查询中使用一个“where”子句来描述一个范围时应该获得一个“范围锁”。这种机制可避免 “幻影读” 现象。
了解:三种读现象(Read phenomena)
SQL 92标准描述了三种不同的一个事务读取另外一个事务可能修改的数据的“读现象”。
以下数据表user为例,模拟两个事务之间交替执行sql
id | name | age |
---|---|---|
1 | 张三 | 20 |
2 | 李四 | 25 |
● 脏读
一个事务读取到另外一个事务还未提交的数据,简称为脏读。(此处不做过多演示,自行查阅)
● 不可重复读
在一次事务中,当一行数据查询两遍得到不同的结果表示发生了不可重复读。
执行顺序 |
事务A | 事务B |
① |
begin; |
|
② |
select * from user where id =1; (查询的结果 age = 20) |
|
③ |
begin; |
|
④ |
update user set age=21 where id = 1; ( 修改age为21) |
|
⑤ |
commit ; |
|
⑥ |
select * from user where id =1; (结果age = 21,同一个事务中两次查询结果不一致) |
? 解析:在基于锁的并发控制中 “不可重复读” 现象,发生在当执行 select 操作时未获的读锁(隔离级别:读未提交),或者 select 操作执行完后马上释放了读锁(隔离级别:读已提交)
● 幻读
在一个事务中,在不同的时间执行相同的查询语句返回的结果集不同,则称为幻读(phantom)。
注:两次相同的查询,返回的行数不同即为「幻读」,而不是相同的行中某个字段的变化。
执行顺序 |
事务A | 事务B |
① |
begin; |
|
② |
select * from user where age between 20 and 30; (查询年龄在20-30岁之间的记录,结果集2条) |
|
③ |
begin; |
|
④ |
insert into user(id,age) values(3,27); ( 插入一条age为27岁的记录) |
|
⑤ |
commit ; |
|
⑥ |
select * from user where age between 20 and 30; (结果集有3条) |
? 解析:“幻影读”是不可重复读的一种特殊场景,当事务A 执行2次 select … where 查询一定范围内的数据中间,事务B在表中插入了一行新数据,这条新数据正好满足事务1的 “where”子句。
注:这种正常的 “幻影读” 场景,由于MVCC版本快照的存在,因此在RR级别下是不存在的。
▎幻读:在可重复读级别下的 “特殊场景”
RR级别下,保证了当前事务不会读取到其他事务已提交的 update 操作。但同时,也会导致当前事务无法感知到来自其他事务中的 insert 或 delete 操作。
? 例如下图:实验A
? 流程解析:开启2个事务
- 在事务A中进行查询操作,此时user表没有数据。 step1
- 在事务B中插入id=1数据,并提交事务。step2
- 然后事务A尝试插入同样id=1的记录,发现 主键冲突错误。step3
- 事务A再次查询,发现和step1的结果一致 (RR下的 MVCC机制),彷佛出现了“幻觉”。 step4
A:“见鬼!表里不是没有记录吗/strong>” ,由于RR下的 MVCC机制,因此A无论读取多少次,都查不到 id=1的记录,但它的确无法插入,这条通过读取而认定id=1不存在的记录。
注:A读取的数据状态并不能支持他的下一步的业务,见鬼了一样。
所以:MySQL的 InnoDB引擎在RR级别下到底能否解决幻读h3>
也可以说能够解决 “幻读”,但可能存在幻写读……
也可以说能够解决 “幻读”,但可能存在幻写读……
如果面试时遇到这个问题, 可能需要详细给他扯一扯. 需要讲清楚 MySQL InnoDB RR 隔离级别 的快照,快照读 & 当前读
- 在只读时是可以避免幻读的
- 在读写时可能会因为update操作使得不可见的行变得可见, 从而出现幻影行!
? 例如下图:实验B
1. demo表原数据如下。注:表中不存在id=2的记录,接下来模拟事务插入数据展示幻读效果。
2. 开启事务,执行顺序依次按照序 执行
时间顺序 |
事务A | 事务B |
① |
begin; (开始事务) |
|
② |
select * from demo where age between 16 and 21; (第一次查询:age为16-21之间的记录) |
|
③ |
begin; (开始事务) |
|
④ |
insert into demo(id, age, d) values (2,17,0); (插入一条age=17的记录,满足事务A条件) |
|
⑤ |
commit ; (提交事务) |
|
⑥ |
update demo set age=18 where id=2; (修改age=17的记录,在②的查询中是不存在的) |
|
⑦ |
select * from demo where age between 16 and 21; (第二次查询:age为16-21岁之间的记录) |
|
⑧ |
commit ; (提交事务) |
? 解析:
由于使用一致性非锁定读时是不加锁的,即②,虽然RR级别能使用Next-Key lock, 但在这里也是没有开启的,这就允许事务B进行④插入操作了。【快照读不加锁】
另外事务A执行⑥ update操作时,update 属于当前读(即锁定读),因此不受read-view限制,它会去竞争要修改的数据行的锁。【快照读变更为当前读】
最终会对 trx_id 处于read-view中的数据行(快照读不可见) 进行了修改,id=2的数据行的trx_id 被修改为当前事务A的trx_id,最后select时,就变得可见了 【trx_id 发生修改】
如果前后两次select 都不加锁,基于MySQL的read-view机制,能够保证前后读的数据一致,但如果两次select之间存在update,由于当前读不受read-view限制,最终可能会影响trx_id的修改,从而产生幻影读。但这种是否应该算作幻读呢p>
RR级别下InnoDB解决常规幻读的原理
注:MVCC 和 Next-key Locks,能够解决在可重复读的事务隔离级别下出现的幻读
- 快照读:不加锁,通过mvcc来避免幻读。
- 当前读:加锁读,通过next-key来避免幻读。对读取到的记录加锁 (记录锁),同时保证对读取的范围加锁(间隙锁),新的满足查询条件的记录不能够插入 。不存在幻读现象。
? 解析:加锁读例如select .. for update / select .. lock in share mode 显式加读锁/写锁;对于insert/update/delete 语句, 它们只有当前读, 且会自动对相应的行加写锁。
▎通俗理解
- 对于快照读来说:你随便读我不管你,反正你读的是历史数据,绝对不会出现幻读。
- 对于当前读来说:需要使用 Next-Key Lock,以实现对写数据进行阻止,防止出现幻读。
扩展:MVCC只会在这两种事务级别工作:
- 读已提交 Read Committed (每次select会生成新的快照)
- 可重复读 Repeatable Read (后续的select都会参照第一次select生成的快照)
因为 读未提交 总是读取最新的数据,不符合当前事务版本的数据行,而 可串行化 则会对所有的行加锁,这两种都不需要MVCC。
▎总结
最后重温一下MySQL官方对幻读(phantom)的定义:“ 同一个事务中,在不同的时间执行查询返回的结果集不同,则称为幻读。”
综上所述,在 实验A、实验B 中我们认为的 “幻读” ,对MySQL来说,并非 “幻读”,而是快照读。
根据官方文档的说法,在RR事务隔离级别:当前读的场景下,在搜索和扫描索引的时候使用next-key locks 可以避免幻读。而快照读则通过mvcc版本控制避免幻读。
文章知识点与官方知识档案匹配,可进一步学习相关知识Java技能树首页概览93557 人正在系统学习中
声明:本站部分文章及图片源自用户投稿,如本站任何资料有侵权请您尽早请联系jinwei@zod.com.cn进行处理,非常感谢!