前几天在系统中开发一个活动, 使用了行级锁,但是却在并发测试下出现了重大的Bug,话不多说, 直接开始.
事务的隔离级别是Mysql默认的REPEATABLE_READ.
Time1:事务A(Ta)锁定表t_1中id=3的记录
Time2: 事务A查询表t_3中的数据.
Time3: 事务B(Tb)也去尝试锁定表t_1中id=3的记录, 没有获得锁, 线程等待.
Time4:事务B查询表t_3中的数据.
Time5: Ta查询表t_2中status=0的前一条记录, id=10,并修改该条记录的stauts=1.
Time6: Ta提交事务.
Time7: Tb获得记录锁,
Time8: Tb查询表t_2中status=0的前一条记录, id=10.(这是个问题,为什么在Tb事务中还能获得到这条记录?)
Time9: Tb修改表t_2中id=10这条记录的status=1时, 没有修改到.因为update语句执行返回的条数为0.抛出异常.
Time10: Tb回滚.
t_2表的查询语句:select id, num fromt_2 where status=0 and id=10
t_2表的更新语句:update t_2 set status=1 where status=0 and id=10
整个测试出现的问题就是Ta已经修改了t_2表中id=10的这个记录的status=1,并Commit,但是在Time7时,但是Tb查询t_2.id=10这条记录时,该条记录的status字段的值仍然是0,这是在Tb事务第一次查询该t_2表.
后来我将事务隔离级别换成READ_COMMITED,测试正常了. 问题看似是解决了,但是在REPEATABLE_READ级别中出现这种情况的原因还没有找到,这样也无法避免以后再次踩坑.就在网上找资料,发现Mysql的多版本并发控制(MVCC),如果有道友不了解的,可以打开链接查看.这里仅简单的说明一下:
通过保存数据在某个时间点的快照来实现的。在每个事务开启时,都会有一个事务版本号,SELECT时只查找版本早于当前事务版本的数据行(也就是,行的系统版本号小于或等于事务的系统版本号),这样可以确保事务读取的行,只么是在事务开始前已经存在的,要么是事务自身插入或者修改过的。
测试很久遍之后, 我把两个事务内执行的顺序改变了一下:
Time1:事务A(Ta)锁定表t_1中id=3的记录
Time2: 事务A查询表t_3中的数据
Time3: 事务B(Tb)也去尝试锁定表t_1中id=3的记录, 没有获得锁, 线程等待.
Time5: Ta查询表t_2中status=0的前一条记录, id=10,并修改该条记录的stauts=1.
Time6: Ta提交事务.
Time7: Tb获得记录锁
Time4:事务B查询表t_3中的数据.
Time8:Tb查询表t_2中status=0的前一条记录, id=10.(这是个问题,为什么在Tb事务中还能获得到这条记录?)
Time9: Tb修改表t_2中id=10这条记录的status=1时, 没有修改到.因为update语句执行返回的条数为0.抛出异常.
Time10: Tb回滚.
发现只在事务开启后,第一次跟数据库的交互去获取记录的锁,这时就可以正常.
出现并发问题的:
不会出现并发问题的:
猜测:
Mysql数据库的事务隔离级别REPEATABLE_READ中,事务开始后,跟数据库第一次交互后,SELECT语句就只能查找到这个事务之前并且不在该事务内改变的数据.
如果有不同见解,欢迎指出.