事务 A (版本号为 2 )
1、START TRANSACTION;
2、SELECT * FROM test_mvcc WHERE id = 1;
6、UPDATE test_mvcc SET `value` = 'b' WHERE id = 1;
7、COMMIT;
事务 B (版本号为 3 )
3、START TRANSACTION;
4、UPDATE test_mvcc SET `value` = 'c' WHERE id = 1;
5、COMMIT;
SQL 语句前面的序号为执行顺序,事务 A 开始做了一个快照读。然后事务 B 开启,修改数据并提交,此时数据的版本号应该是 3。这时候事务 A 也修改数据,然后提交的时候发现数据库中数据的版本号比自己的大,所以修改失败回滚操作。
以上是最近通过学习 MVCC 我认为理论上应该出现的情况,然后我自己测试发现事务 A 还是能提交成功,这究竟是什么原因?难道是哪里的理解有偏差吗。(当前数据库隔离级别为 Read Repeatable )
1
sun1991 2019-11-28 10:43:48 +08:00
因为事务 B 已经 commit 了呀, 你试试去掉 5, 看看执行是不是卡在那里了. 如果你期望事物 A 报错回滚, 那么需要更高一级的隔离级别.
|
2
zhenhuaYang 2019-11-28 10:44:45 +08:00 1
首先是 repeatable read 不是 read repeatable
事务 A 为什么不能提交成功? mvcc 多版本并发控制和 next-key locks 间隙锁搭配实现了事务的可重复读而已,只不过是让 mysql 可以重复读取数据而已,为什么不能提交成功呢? 你说的事务 B 提交数据后,事务 A 发现此时的数据库提交版本要高于自己,就不会提交成功会回滚,你说的是乐观锁吗? 乐观锁是提交版本大于当前数据库提交版本才会去更新数据。 |
3
ShutTheFu2kUP OP @zhenhuaYang 首先感谢大佬指正。其次,按照你的意思就是 MVCC 中并没有实现这种乐观锁的机制是吗?因为最近学 MVCC 相关知识,国内博客资料和 MySQL 官方文档在这块上都描述的模糊不清,一人一种说法,搞得我学的真的是一头雾水。
|
4
zhenhuaYang 2019-11-28 11:03:07 +08:00
@ShutTheFu2kUP 你可以看看这篇文章,或许看完你就恍然大悟了。https://tech.meituan.com/2014/08/20/innodb-lock.html
|
5
ShutTheFu2kUP OP @zhenhuaYang 好的,谢谢
|
6
newtype0092 2019-11-28 11:11:30 +08:00
事务是没有版本号的吧,事务 A 和事务 B 里这条数据的版本号都是 v2,B 提交后数据版本变为'c'(v3),但在 A 中,只要不修改,重复读取数据,看到的还是'b'(v2),这就是“可重复读”的意思吧。
|
7
banks0913 2019-11-28 12:12:25 +08:00
因为 UPDATE 操作属于当前读,即基于最新数据去进行更新,不像 SELECT 操作是基于 mvcc 读当前事务版本的数据快照。
|
8
peyppicp 2019-11-28 13:05:46 +08:00
1 楼+7 楼组合起来就是正解了。
|
9
phantomzz 2019-11-28 20:33:27 +08:00
@zhenhuaYang 今天看了一下午你发的这篇文章,我还是不能认同你的回复,按照我的理解,A 事务先发起,B 事务后发起,A 事务先 select 以后,会给对应的 rows 以及区间加上行锁以及 gap 锁,这种情况下,B 事务的 update 应该是无法执行的。我看文中对 Next-Key 锁介绍的部分是这么说的。还是我的理解有误?请指教!
|
10
phantomzz 2019-11-28 20:38:32 +08:00
重新尝试着理解了一下,是不是事务 A 的 select 获取的只是 s 锁,事务 B 的 update 获取的是 x 锁,事务 B 在 commit 以后释放了 x 锁,所以事务 A 可以 update ?我来调换一下 5 和 6 的步骤验证下看看
@zhenhuaYang |
11
phantomzz 2019-11-28 21:01:24 +08:00
再次挖掘了一下,理解到:事务 A 第 2 步,是快照读,不会加锁,事务 B 第 4 步是当前读,会加 X 锁,紧接着的第 5 步释放了 X 锁。
|
12
zhenhuaYang 2019-11-29 09:34:23 +08:00
@phantomzz 嗯嗯,对,没错。
|