20 | 幻读是什么,幻读有什么问题?
20 | 幻读是什么,幻读有什么问题?
讲述:林晓斌
时长19:27大小17.77M
幻读是什么?
幻读有什么问题?
如何解决幻读?
小结
上期问题时间
赞 141
提建议
精选留言(289)
- 令狐少侠置顶2018-12-28老师,今天的文章对我影响很大,发现之前掌握的知识有些错误的地方,课后我用你的表结构根据以前不清楚的地方实践了一遍,现在有两个问题,麻烦您解答下 1.我在事务1中执行 begin;select * from t where c=5 for update;事务未提交,然后事务2中begin;update t set c=5 where id=0;执行阻塞,替换成update t set c=11 where id=0;执行不阻塞,我觉得原因是事务1执行时产生next-key lock范围是(0,5].(5,10]。我想问下update set操作c=xxx是会加锁吗?以及加锁的原理。 2.一直以为gap只会在二级索引上,看了你的死锁案例,发现主键索引上也会有gap锁?展开
作者回复: 1. 好问题。你可以理解为要在索引c上插入一个(c=5,id=0)这一行,是落在(0,5],(5,10]里面的,11可以对吧 2. 嗯,主键索引的间隙上也要有Gap lock保护的
共 88 条评论88 - xuery置顶2019-01-28老师之前的留言说错了,重新梳理下: 图8:间隙锁导致的死锁;我把innodb_locks_unsafe_for_binlog设置为1之后,session B并不会blocked,session A insert会阻塞住,但是不会提示死锁;然后session B提交执行成功,session A提示主键冲突 这个是因为将innodb_locks_unsafe_for_binlog设置为1之后,什么原因造成的?展开
作者回复: 对, innodb_locks_unsafe_for_binlog 这个参数就是这个意思 “不加gap lock”, 这个已经要被废弃了(8.0就没有了),所以不建议设置哈,容易造成误会。 如果真的要去掉gap lock,可以考虑改用RC隔离级别+binlog_format=row
共 8 条评论34 - 杜嘉嘉2018-12-28说真的,这一系列文章实用性真的很强,老师非常负责,想必牵扯到老师大量精力,希望老师再出好文章,谢谢您了,辛苦了
作者回复: 精力花了没事,睡一觉醒来还是一条好汉😄 主要还是得大家有收获,我就值了😄
共 7 条评论235 - 薛畅2018-12-29可重复读隔离级别下,经试验: SELECT * FROM t where c>=15 and c<=20 for update; 会加如下锁: next-key lock:(10, 15], (15, 20] gap lock:(20, 25) SELECT * FROM t where c>=15 and c<=20 order by c desc for update; 会加如下锁: next-key lock:(5, 10], (10, 15], (15, 20] gap lock:(20, 25) session C 被锁住的原因就是根据索引 c 逆序排序后多出的 next-key lock:(5, 10] 同时我有个疑问:加不加 next-key lock:(5, 10] 好像都不会影响到 session A 可重复读的语义,那么为什么要加这个锁呢?展开
作者回复: 是的,这个其实就是为啥总结规则有点麻烦,有时候只是因为代码是这么写的😓
共 23 条评论84 - Mr.Strive.Z.H.L2019-01-03看了@令狐少侠 提出的问题,对锁有了新的认识: 对于非索引字段进行update或select .. for update操作,代价极高。所有记录上锁,以及所有间隔的锁。 对于索引字段进行上述操作,代价一般。只有索引字段本身和附近的间隔会被加锁。 这次终于明白,为什么说update语句的代价高!展开
作者回复: 是的,update、delete语句用不上索引是很恐怖的😄
共 9 条评论68 - 沉浮2018-12-28通过打印锁日志帮助理解问题 锁信息见括号里的说明。 TABLE LOCK table `guo_test`.`t` trx id 105275 lock mode IX RECORD LOCKS space id 31 page no 4 n bits 80 index c of table `guo_test`.`t` trx id 105275 lock_mode X Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 ----(Next-Key Lock,索引锁c(5,10]) 0: len 4; hex 8000000a; asc ;; 1: len 4; hex 8000000a; asc ;; Record lock, heap no 5 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 ----(Next-Key Lock,索引锁c (10,15]) 0: len 4; hex 8000000f; asc ;; 1: len 4; hex 8000000f; asc ;; Record lock, heap no 6 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 ----(Next-Key Lock,索引锁c (15,20]) 0: len 4; hex 80000014; asc ;; 1: len 4; hex 80000014; asc ;; Record lock, heap no 7 PHYSICAL RECORD: n_fields 2; compact format; info bits 0 ----(Next-Key Lock,索引锁c (20,25]) 0: len 4; hex 80000019; asc ;; 1: len 4; hex 80000019; asc ;; RECORD LOCKS space id 31 page no 3 n bits 80 index PRIMARY of table `guo_test`.`t` trx id 105275 lock_mode X locks rec but not gap Record lock, heap no 5 PHYSICAL RECORD: n_fields 5; compact format; info bits 0 ----(记录锁 锁c=15对应的主键) 0: len 4; hex 8000000f; asc ;; 1: len 6; hex 0000000199e3; asc ;; 2: len 7; hex ca000001470134; asc G 4;; 3: len 4; hex 8000000f; asc ;; 4: len 4; hex 8000000f; asc ;; Record lock, heap no 6 PHYSICAL RECORD: n_fields 5; compact format; info bits 0 0: len 4; hex 80000014; asc ;; ----(记录锁 锁c=20对应的主键) 1: len 6; hex 0000000199e3; asc ;; 2: len 7; hex ca000001470140; asc G @;; 3: len 4; hex 80000014; asc ;; 4: len 4; hex 80000014; asc ;; 由于字数限制,正序及无排序的日志无法帖出,倒序日志比这两者,多了范围(Next-Key Lock,索引锁c(5,10]),个人理解是,加锁分两次,第一次,即正序的锁,第二次为倒序的锁,即多出的(5,10],在RR隔离级别, innodb在加锁的过程中会默认向后锁一个记录,加上Next-Key Lock,第一次加锁的时候10已经在范围,由于倒序,向后,即向5再加Next-key Lock,即多出的(5,10]范围展开
作者回复: 优秀
共 5 条评论65 - kabuka2019-03-11这样,当你执行 select * from t where d=5 for update 的时候,就不止是给数据库中已有的 6 个记录加上了行锁,还同时加了 还同时加了 7 个间隙锁 --------------------------------------------------------------- 老師這句話沒看太明白,數據庫只有一條d=5的記錄,為什麼會給6個記錄加上行鎖呢?
作者回复: 因为d上没有索引,这个语句要走全表扫描
共 10 条评论50 - 钱2019-08-01课前思考 1:幻读是什么?幻读有什么问题?如何避免? 幻读我的理解是,读出的数据出现了不一致的现象,在事务的读未提交和读已提交这两种事务的隔离级别下会出现幻读的现象,问题嘛?就是数据不一致了,对于数据严格要求一致的场景是不能够允许的。如何避免?在可重复读和串行化的事务隔离级别下应该不会出现 课后思考 1:学完此节后发现自己的认知,基本是错的 1-1:什么是幻读? 幻读是指在同一个事务中,存在前后两次查询同一个范围的数据,但是第二次查询却看到了第一次查询没看到的行。 注意,幻读出现的场景 第一:事务的隔离级别为可重复读,且是当前读 第二:幻读仅专指新插入的行 1-2:幻读带来的问题? 一是,对行锁语义的破坏 二是,破坏了数据一致性 1-3:怎么避免幻读? 存储引擎采用加间隙锁的方式来避免出现幻读 1-4:为啥会出现幻读? 行锁只能锁定存在的行,针对新插入的操作没有限定 1-5:间隙锁是啥?它怎么避免出现幻读的?它引入了什么新的问题? 间隙锁,是专门用于解决幻读这种问题的锁,它锁的了行与行之间的间隙,能够阻塞新插入的操作 间隙锁的引入也带来了一些新的问题,比如:降低并发度,可能导致死锁。 注意,读读不互斥,读写/写读/写写是互斥的,但是间隙锁之间是不冲突的,间隙锁会阻塞插入操作 另外,间隙锁在可重复读级别下才是有效的 感谢老师的分享,意料之外的认知很好玩,也纠正了自己的认知偏差。 感觉自己明白了,看完评论,感觉自己啥都不懂。展开共 10 条评论47
- 郭江伟2018-12-28insert into t values(0,0,0),(5,5,5), (10,10,10),(15,15,15),(20,20,20),(25,25,25); 运行mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> select * from t where c>=15 and c<=20 order by c desc for update; c 索引会在最右侧包含主键值,c索引的值为(0,0) (5,5) (10,10) (15,15) (20,20) (25,25) 此时c索引上锁的范围其实还要匹配主键值 。 思考题答案是,上限会扫到c索引(20,20) 上一个键,为了防止c为20 主键值小于25 的行插入,需要锁定(20,20) (25,25) 两者的间隙;开启另一会话(26,25,25)可以插入,而(24,25,25)会被堵塞。 下限会扫描到(15,15)的下一个键也就是(10,10),测试语句会继续扫描一个键就是(5,5) ,此时会锁定,(5,5) 到(15,15)的间隙,由于id是主键不可重复所以下限也是闭区间; 在本例的测试数据中添加(21,25,25)后就可以正常插入(24,25,25)展开
作者回复: 感觉你下一篇看起来会很轻松了哈👍🏿
共 9 条评论41 - 慧鑫coming2018-12-28这篇需要多读几遍,again31
- 发条橙子 。2019-01-01老师 , 看到幻读的定义是 : 幻读是一个事物在前后两次查询同一个范围的时候,后一次查询看到了前一次查询没有看到的行 。 那么我感觉 1. 读提交事务隔离级别 2.可重复读事务隔离级别的当前读 这两个都符合这个定义 。 那是不是说 在 1 、 2 条件下都会发生幻读 。 但是我看一些文章都说幻读是rr级别下的 , rc 是不可重复读 。请问是我理解有误还是文章写的不准确展开
作者回复: 其实读提交隔离级别下看到的,严格来说不算。 因为这个就是读提交隔离级别下“设计内”的问题😄 这种感觉就是,对于读提交隔离级别,这个算“feature”,(是不是恨熟悉) 对于可重复读,这个是”bug”, 所以要解决,称呼这个bug为幻读😄
共 4 条评论26 - hetiu2019-01-06mysql官方提到自增锁是个表级锁,老师能介绍下这个吗,以及实际项目中高并发insert是否需要避免自增主键?
作者回复: 好问题,innodb_auroinc_lock_mode设置为2,binlog_formate设置成row就行,没有表锁问题
共 4 条评论18 - 卡卡2019-03-26间歇锁 和 排他锁有关系吗?
作者回复: next-key lock = gap lock + record lock 间隙锁是一种锁的类型 排他是一种锁的行为
14 - 凡凡是谁爹2019-12-20老师,你有点没详细讲 1、那其实不是记录锁和间隙锁的冲突,是意向插入锁和间隙锁的冲突。 2、你没有讲什么条件下会gap锁,gap锁是非唯一索引下会加的共 7 条评论13
- 郭健2018-12-30老师,想请教您几个问题。1.在第六章MDL锁的时候,您说给大表增加字段和增加索引的时候要小心,之前做过测试,给一个一千万的数据增加索引有时需要40分钟,但是增加索引不会对表增加MDL锁吧。除了增加索引慢,还会对数据库有什么影响吗,我问我们dba,他说就开始和结束的时候上一下锁,没什么影响,我个人是持怀疑态度的。2,老师讲到表锁除了MDL锁,还有显示命令lock table的命令的表锁,老师我可以认为,在mysql中如果不显示使用lock table表锁的话,那么mysql是永远不会使用表锁的,如果锁的条件没有索引,使用的是锁住行锁+间隙控制并发。展开
作者回复: 1. 在锁方面你们dba说的基本是对的。一开始和结束有写锁,执行中间40分钟只有读锁 但是1000万的表要做40分钟,可能意味着系统压力大(或者配置偏小),这样可能不是没影响对,比较这个操作还是要吃IO和CPU的 2. 嗯,innodb引擎是这样的。
共 4 条评论11 - 乔纳森2019-05-04加锁过程的分析,这篇文章也是很棒的;供同学们参考 http://hedengcheng.com/?p=771共 9 条评论10
- Cv2019-03-07gap锁是否只会在可重复读的情况下才有? 在提交读和有唯一索引的情况下, 我也有遇到过因为gap死锁的情况 大致是这种sql session1 delete from t where id in (1,3,5); insert into t id(1,3,5); session2 delete from t where id in (2,4,6); insert into t id(2,4,6);展开
作者回复: 读提交隔离级别一般没有gap lock,不过也有例外情况, 比如insert 出现主键冲突的时候,也可能加间隙锁
共 3 条评论10 - do it2019-06-16什么是幻读? 幻读指的是一个事务在前后两次查询同一个范围的时候,后一次查询看到了前一次查询没有看到的行。(幻读在当前读下才会出现;幻读仅专指新插入的行) 如何解决幻读? 间隙锁(Gap lock):(两个值之间的锁)。 间隙锁和行锁合称 next-key lock,每个 next-key lock 是前开后闭区间。 间隙锁为开区间。 next-key-lock为前开后闭区间。 间隙锁引入什么问题? 可能会导致同样的语句锁住更大的范围,这其实是影响了并发度的。 间隙锁在RR级别下才有效,RC级别下无间隙锁。 不使用间隙锁方法: 使用读提交隔离级别+ binlog_format=row组合。展开8
- Geek_89bbab2019-01-07表结构 CREATE TABLE `t2` ( `id` int(11) DEFAULT NULL, `v` int(11) DEFAULT NULL ) ENGINE=InnoDB; 两个session, session1, | session2 step1: set session transaction isolation level repeatable read;(session1) | set session transaction isolation level repeatable read;(session2) step2: begin;(session1) step3: begin; (session2) step4: insert into t2 (id,v) values(1,1); (session1) step5: insert into t2 (id,v) select 2,2 from dual where not exists(select * from t2 where id=2); (session2) // 这里为什么会阻塞,直到session1提交呢? step6: commit; (session1) 该句执行完 session2不再阻塞 step7:commit;(session2) 我的疑惑就是为什么step5 那一步会阻塞?select * from t2 where id=2 不是快照读吗?也没有用for update,share lock 之类的语句,而且insert into 也没有什么唯一键约束,主键约束,怎么用数据库锁和隔离级别的知识来解释这个现象呢?请老师指点展开
作者回复: 好问题 Insert...select 是会给select部分加读锁的 这个也是为了保证一致性
共 5 条评论8 - 咸鱼2020-07-28有了间隙锁是不是就解决了幻读了?那这样的话,串行化的意义在哪呢?解决的又是什么问题呢?以前一直以为串行化是为了解决幻读的问题共 8 条评论7