08 | 事务到底是隔离的还是不隔离的?
该思维导图由 AI 生成,仅供参考
- 深入了解
- 翻译
- 解释
- 总结
本文深入探讨了事务隔离级别对于事务可见性的影响,并重点介绍了可重复读隔离级别下的事务视图和行锁的概念。通过具体的例子和技术概念,帮助读者理解了事务隔离级别对于数据可见性的影响,以及MVCC在实现中的作用。文章详细解释了在MySQL中MVCC实现时使用的一致性读视图的概念,以及InnoDB如何利用多版本数据实现“秒级创建快照”的能力。通过分析事务A的查询逻辑,阐述了数据版本的可见性规则,帮助读者更深入地理解事务的查询和更新的区别。整体而言,本文通过深入的技术分析和具体案例,为读者提供了对事务隔离级别和MVCC实现的深入理解。 文章还涉及了一致性读、当前读和行锁的概念,以及可重复读和读提交隔离级别下的查询结果差异。此外,还提到了InnoDB的行数据版本管理和一致性视图的创建,以及对表结构支持可重复读的可能性。最后,作者提出了一个思考题,引导读者思考在实际业务开发中可能遇到的问题,并鼓励读者分享观点和解决方案。 总的来说,本文通过深入的技术分析和具体案例,为读者提供了对事务隔离级别和MVCC实现的深入理解,同时引发了读者对实际问题的思考和讨论。
《MySQL 实战 45 讲》,新⼈⾸单¥68
全部留言(706)
- 最新
- 精选
- ithunter置顶请教一个问题,业务上有这样的需求,A、B两个用户,如果互相喜欢,则成为好友。设计上是有两张表,一个是like表,一个是friend表,like表有user_id、liker_id两个字段,我设置为复合唯一索引即uk_user_id_liker_id。语句执行顺序是这样的: 以A喜欢B为例: 1、先查询对方有没有喜欢自己(B有没有喜欢A) select * from like where user_id = B and liker_id = A 2、如果有,则成为好友 insert into friend 3、没有,则只是喜欢关系 insert into like 如果A、B同时喜欢对方,会出现不会成为好友的问题。因为上面第1步,双方都没喜欢对方。第1步即使使用了排他锁也不行,因为记录不存在,行锁无法生效。请问这种情况,在mysql锁层面有没有办法处理
作者回复: 你这个问题很有趣。我想到一个不错的解法。不过我先置顶。让别的同学来回答看看。 好问题,谁有想法po出来。
2018-12-056480 - 心雨鑫晴置顶老师,我有一个问题。当开启事务时,需要保存活跃事务的数组(A),然后获取高水位(B)。我的疑问就是,在这两个动作之间(A和B之间)会不会产生新的事务?如果产生了新的事务,那么这个新的事务相对于当前事务就是可见的,不管有没有提交。
作者回复: 好问题,有很深入的思考哈 代码实现上,获取视图数组和高水位是在事务系统的锁保护下做的,可以认为是原子操作,期间不能创建事务。
2018-12-0322145 - 包包up置顶以下是一个错误的理解,在编写评论的过程中用前面刚学到的知识把自己的结论推翻,有一种快感,所以还是决定发出来。 哈哈~ 事务A(100) | 事务B(101) ------------------- | select(1) ------------------- | update ------------------- update | ------------------- | select(2) ------------------- 事务A B在事务启动时的up_limit_id为99 事务B update 之后表格的每一行的row_trx_id变为101 事务A 再update 之后每一行的row_trx_id变为100 事务B的select(2)时因为隔离级别是RR,所以去遍历的时候找row_trx_id<=101的版本返回,优先找到版本为100的,就会导致select(2)并没有取到自己的更新。 对于对于自己的修改也认这句话和undo-log的介绍,我觉的这种情况下会获取不到自己更新的最新的数据。不知道我理解的对不对。 不对!因为事务A的update是会被行锁锁住的,而且锁是要在事务B结束之后才释放,所以不存在在事务B的update之后还在事务中被事务A给更新,导致上面的问题。
作者回复: 👍🏿 我在学习过程中也是最喜欢这种“自己推翻自己结论”的快感
2018-11-301063 - 夏日雨置顶老师你好,有个问题不太理解,对于文中的例子假设transaction id为98的事务在事务A执行select(Q2)之前更新了字段,那么事务A发现这个字段的row trx_id是98,比自己的up_limit_id要小,那此时事务A不就获取到了transaction id为98的事务更新后的值了吗? 换句话说对于文中"之后的更新,产生的新的数据版本的 row trx_id 都会大于 up_limit_id"这句话不太理解, up_limit_id是已经提交事务id的最大值,那也可能存在一个没有提交的id小于up_limit_id的事务对数据进行更新?还是说transaction id比up_limit_id小的事务都是保证已经提交的?
作者回复: 你的问题被引用最多,我回复你哈,其它同学看过来😄 好吧,今天的课后问题其实比较简单,本来是隐藏在思考题里的彩蛋,被你问出来了哈。 Innodb 要保证这个规则:事务启动以前所有还没提交的事务,它都不可见。 但是只存一个已经提交事务的最大值是不够的。 因为存在一个问题,那些比最大值小的事务,之后也可能更新(就是你说的98这个事务) 所以事务启动的时候还要保存“现在正在执行的所有事物ID列表”,如果一个row trx_id在这列表中,也要不可见。 虽然踩破了彩蛋,还是赞你的思考哈,置顶让大家学习😄
2018-11-3041362 - 约书亚置顶早。 思考题,RR下,用另外一个事物在update执行之前,先把所有c值修改,应该就可以。比如update t set c = id + 1。 这个实际场景还挺常见——所谓的“乐观锁”。时常我们会基于version字段对row进行cas式的更新,类似update ...set ... where id = xxx and version = xxx。如果version被其他事务抢先更新,则在自己事务中更新失败,trx_id没有变成自身事务的id,同一个事务中再次select还是旧值,就会出现“明明值没变可就是更新不了”的“异象”(anomaly)。解决方案就是每次cas更新不管成功失败,结束当前事务。如果失败则重新起一个事务进行查询更新。 记得某期给老师留言提到了,似乎只有MySQL是在一致性视图下采用这种宽松的update机制。也许是考虑易用性吧。其他数据库大多在内部实现cas,只是失败后下一步动作有区别。
作者回复: 早 赞 置顶了 明天课后问题时间直接指针引用了哈😄 补充一下:上面说的“如果失败就重新起一个事务”,里面判断是否成功的标准是 affected_rows 是不是等于预期值。 比如我们这个例子里面预期值本来是4,当然实际业务中这种语句一般是匹配唯一主键,所以预期住值一般是1。
2018-11-3046240 - 墨萧置顶可重复读情况下,事务c的102早于事务b的101,如果事务c再get k,那不是就取得101的值了?不太明白。
作者回复: 咱们例子里面,事务C是直接提交的,再执行一个GET 就是另外一个事务了… 如果你说的是用begin 来启动一个多语句事务,那么事务c在更新后查询,还是看到row trx_id是102的。 【注意:如果它还没提交,101根本生成不出来,因为事务B被行锁挡着呢】
2018-11-30217 - Leo置顶老师在文中说: "所以,在执行事务 B 的 Q1 语句的时候,一看自己的版本号是 101,最新数据的版本号也是 101,可以用,所以 Q1 得到的 k 的值是 3。", 1. 这里不参考up_limit_id了吗? 2. 如果参考,事务B的up_limit_id是在执行update语句前重新计算的,还是在执行Q1语句前重新计算的?
作者回复: 1. 判断可见性两个规则:一个是up_limit_id ,另一个是“自己修改的”;这里用到第二个规则 2. 这时候事务Bup_limit_id还是99
2018-11-30313 - 某、人这篇理论知识很丰富,需要先总结下 1.innodb支持RC和RR隔离级别实现是用的一致性视图(consistent read view) 2.事务在启动时会拍一个快照,这个快照是基于整个库的. 基于整个库的意思就是说一个事务内,整个库的修改对于该事务都是不可见的(对于快照读的情况) 如果在事务内select t表,另外的事务执行了DDL t表,根据发生时间,要嘛锁住要嘛报错(参考第六章) 3.事务是如何实现的MVCC呢? (1)每个事务都有一个事务ID,叫做transaction id(严格递增) (2)事务在启动时,找到已提交的最大事务ID记为up_limit_id。 (3)事务在更新一条语句时,比如id=1改为了id=2.会把id=1和该行之前的row trx_id写到undo log里, 并且在数据页上把id的值改为2,并且把修改这条语句的transaction id记在该行行头 (4)再定一个规矩,一个事务要查看一条数据时,必须先用该事务的up_limit_id与该行的transaction id做比对, 如果up_limit_id>=transaction id,那么可以看.如果up_limit_id<transaction id,则只能去undo log里去取。去undo log查找数据的时候,也需要做比对,必须up_limit_id>transaction id,才返回数据 4.什么是当前读,由于当前读都是先读后写,只能读当前的值,所以为当前读.会更新事务内的up_limit_id为该事务的transaction id 5.为什么rr能实现可重复读而rc不能,分两种情况 (1)快照读的情况下,rr不能更新事务内的up_limit_id, 而rc每次会把up_limit_id更新为快照读之前最新已提交事务的transaction id,则rc不能可重复读 (2)当前读的情况下,rr是利用record lock+gap lock来实现的,而rc没有gap,所以rc不能可重复读
作者回复: 👍🏿 本篇知识点全get
2018-11-3021219 - lucky star答案: 分析: 假设有两个事务A和B, 且A事务是更新c=0的事务; 给定条件: 1, 事务A update 语句已经执行成功, 说明没有另外一个活动中的事务在执行修改条件为id in 1,2,3,4或c in 1,2,3,4, 否则update会被锁阻塞; 2,事务A再次执行查询结果却是一样, 说明什么?说明事务B把id或者c给修改了, 而且已经提交了, 导致事务A“当前读”没有匹配到对应的条件; 事务A的查询语句说明了事务B执行更新后,提交事务B一定是在事务A第一条查询语句之后执行的; 所以执行顺序应该是: 1, 事务A select * from t; 2, 事务B update t set c = c + 4; // 只要c或者id大于等于5就行; 当然这行也可以和1调换, 不影响 3, 事务B commit; 4, 事务A update t set c = 0 where id = c; // 当前读; 此时已经没有匹配的行 5, 事务A select * from t; 读完第三篇后就陷入了事务执行原理的泥潭中了, 也找了不少相关资料, 但总感觉还不是特别明白, 今天看完这篇终于茅塞顿开呀, 仿佛打通了任督二脉了。。。。
作者回复: 嗯嗯,分析得很对。 茅塞顿开的感觉很好,恭喜🎉🎈
2018-12-1516101 - 沙亮亮买了很多专栏,丁奇老师绝对是为读者考虑最为细致的,不管是从回复大家的提问,还是从学习者角度考虑优化文章内容,最后到思考题的讲解,都是最细致的
作者回复: 谢谢你,我倍受鼓舞呀😄
2018-12-07391