在 MySQL 中, 使用 MVCC 来实现 REPEATABLE-READ 隔离级别, 由于 SELECT 操作不会对数据加锁, 其他回话可以修改当前回话所读取过的数据而不会被阻塞, 因此读写不冲突
在 MVCC 并发控制中, 读操作可以分成两类: 快照读 (snapshot read) 与当前读 (current read) 快照读, 读取的是记录的可见版本 (有可能是历史版本), 不用加锁当前读, 读取的是记录的最新版本, 并且, 当前读返回的记录, 都会加上锁, 保证其他事务不会再并发修改这条记录
当事务中进行查询时, MySQL 会把快照读和当前读的结果进行合并再返回给客户端, 而这个合并可能导致一些奇特的结果
生成测试数据:
- drop table tb002;
- create table tb002(id int primary key,c2 int,unique index uni_c2(c2));
- begin;
- insert into tb002(id,c2) select 1,1;
- insert into tb002(id,c2) select 2,2;
- insert into tb002(id,c2) select 4,4;
- commit;
假设有回话 A 和回话 B, 均使用 REPEATABLE-READ 隔离级别
##========================================================##
首先回话 A 执行 SQL:
- begin;
- select * from tb002;
返回结果如下:
##========================================================##
然后回话 B 执行 SQL:
- begin;
- delete from tb002 where id = 2;
- commit;
由于回话 A 没有加锁, 所以回话 B 能顺利完成删除并提交事务, 当前数据库中无 C2=2 的记录, 且会话 B 提交事务释放锁
##========================================================##
回到回话 A 执行 SQL:
insert into tb002(id,c2) select 3,2;
由于当前数据库中无 C2=2 的记录, 且其他回话没有在此 C2=2 的范围上加锁, 因此回话 A 可以完成 C2=2 的数据插入
在回话 A 上再次进行查询:
select * from tb002;
返回结果如:
C2 上有唯一索引, 但为什么查询结果中仍包含两条 C2=2 的记录呢? ID=2 的记录属于快照读的数据, ID=3 的记录数据当前读的数据, MySQL 将当前读和快照读的数据进行简单的合并后返回给客户端, 并不检查结果数据是否满足唯一索引的要求
##========================================================##
上面的测试针对唯一索引进行, 那如果针对主键会有啥区别呢?
将插入 SQL 修改为:
insert into tb002(id,c2) select 2,3;
即回话 B 删除的 ID 值和回话 A 新插入的 ID 值相同情况下, 最后的查询结果并不会包含两条相同 ID 的记录, 对于快照读和当前读两个结果集存在 " 主键冲突的情况, 最终返回客户端的结果会丢弃快照读中的老版本记录, 保留最新版本的记录
可见对于主键不存在上述问题
来源: http://www.linuxidc.com/Linux/2018-02/150769.htm