阿里面试官:MySQL的InnoDB引擎是如何解决幻读问题的?(下)

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS PostgreSQL,集群系列 2核4GB
简介: 阿里面试官:MySQL的InnoDB引擎是如何解决幻读问题的?

为何会数据不一致?

这是我们假设“select * from t where d=5 for update这条语句只给d=5这一行,也就是id=5的这一行加锁”导致的。

所以我们认为,上面的设定不合理,要改。

那怎么改?

把扫描过程中碰到的行,也都加上写锁,再来看看执行效果。

  • 假设扫描到的行都被加上了行锁
  • image.png
  • 由于session A把所有行都加了写锁,所以session B在执行第一个update语句时就被锁住。需要等到T6时session A提交后,session B才能继续执行。

这样对于id=0这行,在DB的最终结果还是 (0,5,5)。在binlog里执行序列是这样:

insert into t values(1,1,5); /*(1,1,5)*/
update t set c=5 where id=1; /*(1,5,5)*/
update t set d=100 where d=5;/*所有d=5的行,d改成100*/
update t set d=5 where id=0; /*(0,0,5)*/
update t set c=5 where id=0; /*(0,5,5)*/

可见按日志顺序执行,id=0这行的最终结果也是(0,5,5)。所以,id=0这行问题解决了。


但id=1这一行,在DB里结果是(1,5,5),而根据binlog的执行结果是(1,5,100),即幻读依旧。


为何把所有记录都上锁了,还是阻止不了id=1这行的插入和更新?

在T3时,我们给所有行加锁时,id=1这行还不存在,不存在也就加不上锁。

即使把所有记录都加锁,还是阻止不了新插入的记录,这也是为什么“幻读”会被单独拿出来解决。

InnoDB解决幻读

  • 幻读的原因
    行锁只能锁行,但是新插入记录这个动作,要更新的是记录之间的“间隙”。因此,为了解决幻读,InnoDB只好引入间隙锁(Gap Lock),两个值之间的空隙。比如文章开头的表t,初始化插入了6个记录,这就产生了7个间隙。
  • 表t主键索引上的行锁和间隙锁
  • image.png
  • 当执行 select * from t where d=5 for update时,就不止是给数据库中已有的6个记录加上了行锁,还同时加了7个间隙锁。这样就确保了无法再插入新记录。

即在一行行扫描过程中,不仅给行加上了行锁,还给行两边的空隙加上了间隙锁。


数据行是可以加上锁的实体,数据行之间的间隙,也是可以加上锁的实体。


两种行锁间的冲突关系


image.png

  • 跟行锁有冲突关系的是“另外一个行锁”。
    但间隙锁不一样,跟间隙锁存在冲突关系的,是“往这个间隙中插入一个记录”这个操作。
    间隙锁之间不存在冲突关系。
    举个例子:
  • 间隙锁之间不互锁
  • image.png
  • session B不会被堵住。因为表t里并没c=7记录,因此session A加的间隙锁(5,10)。而session B也是在这个间隙加的间隙锁。它们有共同的目标,即:保护这个间隙,不允许插入值。但它们之间不冲突。


间隙锁和行锁合称next-key lock,每个next-key lock是前开后闭区间。即我们的表t初始化以后,如果用select * from t for update要把整个表所有记录锁起来,就形成了7个next-key lock,分别是 (-∞,0]、(0,5]、(5,10]、(10,15]、(15,20]、(20, 25]、(25, +supremum]。


本文没有特别说明,就把间隙锁记为开区间,把next-key lock记为前开后闭区间。

  • supremum是啥玩意?
    因为+∞是开区间,代码实现上,InnoDB给每个索引加了不存在的最大值supremum,就符合后闭区间了。

间隙锁和next-key lock解决了幻读,但也带来“困扰”。

案例

需求

任意锁住一行,若:

  • 该行不存在,就插入
  • 存在,就更新数据

实现

begin;
select * from t where id=N for update;
/*如果行不存在*/
insert into t values(N,N,N);
/*如果行存在*/
update t set d=N set id=N;
commit;

就这?

insert … on duplicate key update

不就能解决。但在有多个唯一键时,该方法无法满足该需求。

逻辑分析

该逻辑一旦有并发,就可能死锁。可这个逻辑每次操作前用for update锁了,已经是最严格模式了,怎么还有死锁?

模拟

两个session并发,假设N=9。

  • 间隙锁导致的死锁
  • image.png
  • 不需要用到后面的update语句,就已经死锁。

session A 执行select … for update,由于id=9这行不存在,因此会加间隙锁(5,10)

session B 执行select … for update,同样加间隙锁(5,10),间隙锁之间不冲突,因此可执行成功

session B 试图插入(9,9,9),被session A的间隙锁挡住,进入等待

session A试图插入(9,9,9),被session B的间隙锁挡住

session互相等待形成死锁。当然,InnoDB的死锁检测马上就发现了这对死锁关系,让session A的insert语句报错返回了。


所以间隙锁的引入可能导致同样语句锁住更大范围。


为解决幻读,引入这么多内容,有更简单方法吗?

没有特别说明,本文分析都是可重复读,间隙锁在可重复读隔离级别下才生效。

所以,你如果把隔离级别设置为读提交,就没间隙锁。

但同时,要解决可能出现的数据和日志不一致问题,要把binlog格式设为row。这也是很多公司使用的配置。

如果读提交隔离级别够用,即业务无需保证可重复读,考虑到读提交下操作数据的锁范围更小(无间隙锁),这个选择就是合适的。


如果大家都用读提交,可是逻辑备份时,mysqldump为什么要把备份线程设置成可重复读?

然后,在备份期间,备份线程用的是可重复读,而业务线程用的是读提交。同时存在两种事务隔离级别,会不会有问题?

进一步地,这两个不同的隔离级别现象有什么不一样的,关于我们的业务,“用读提交就够了”这个结论是怎么得到的?

如果业务开发和运维团队这些问题都没有弄清楚,那么“没问题”这个结论,本身就是有问题的。

总结

即使给所有行加上行锁,仍无法解决幻读,因此引入间隙锁。

行锁确实比较直观,判断规则也相对简单,间隙锁的引入会影响系统的并发度,也增加了锁分析的复杂度,但也有章可循。

相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
15天前
|
SQL 关系型数据库 MySQL
大厂面试官:聊下 MySQL 慢查询优化、索引优化?
MySQL慢查询优化、索引优化,是必知必备,大厂面试高频,本文深入详解,建议收藏。关注【mikechen的互联网架构】,10年+BAT架构经验分享。
大厂面试官:聊下 MySQL 慢查询优化、索引优化?
|
2月前
|
存储 关系型数据库 MySQL
阿里面试:为什么要索引?什么是MySQL索引?底层结构是什么?
尼恩是一位资深架构师,他在自己的读者交流群中分享了关于MySQL索引的重要知识点。索引是帮助MySQL高效获取数据的数据结构,主要作用包括显著提升查询速度、降低磁盘I/O次数、优化排序与分组操作以及提升复杂查询的性能。MySQL支持多种索引类型,如主键索引、唯一索引、普通索引、全文索引和空间数据索引。索引的底层数据结构主要是B+树,它能够有效支持范围查询和顺序遍历,同时保持高效的插入、删除和查找性能。尼恩还强调了索引的优缺点,并提供了多个面试题及其解答,帮助读者在面试中脱颖而出。相关资料可在公众号【技术自由圈】获取。
|
1月前
|
SQL 缓存 关系型数据库
美团面试:Mysql 有几级缓存? 每一级缓存,具体是什么?
在40岁老架构师尼恩的读者交流群中,近期有小伙伴因未能系统梳理MySQL缓存机制而在美团面试中失利。为此,尼恩对MySQL的缓存机制进行了系统化梳理,包括一级缓存(InnoDB缓存)和二级缓存(查询缓存)。同时,他还将这些知识点整理进《尼恩Java面试宝典PDF》V175版本,帮助大家提升技术水平,顺利通过面试。更多技术资料请关注公号【技术自由圈】。
美团面试:Mysql 有几级缓存? 每一级缓存,具体是什么?
|
18天前
|
存储 安全 关系型数据库
InnoDB引擎特性
InnoDB事务型数据库的首选引擎,支持事务安全表(ACID),支持行锁定和外键。MySQL5.5.5之后,InnoDB作为默认存储引擎,InnoDB主要特性有: InnoDB给MySQL提供了具有提交,回滚和崩溃恢复能力的事务安全(ACID兼容)存储引擎。InnoDB锁定在行级并且也在SELECT语句中提供了一个类似Oracle的非锁定读。 InnoDB是为处理巨大数据量的最大性能设计。它的CPU效率可能是任何其他基于磁盘关系的数据库引擎所不能匹敌的。 InnoDB存储引擎完全与MySQL服务器整合,InnoDB存储引擎为在主内存中缓存数据和索引而维持它自己的缓冲池
|
17天前
|
SQL 关系型数据库 MySQL
阿里面试:1000万级大表, 如何 加索引?
45岁老架构师尼恩在其读者交流群中分享了如何在生产环境中给大表加索引的方法。文章详细介绍了两种索引构建方式:在线模式(Online DDL)和离线模式(Offline DDL),并深入探讨了 MySQL 5.6.7 之前的“影子策略”和 pt-online-schema-change 方案,以及 MySQL 5.6.7 之后的内部 Online DDL 特性。通过这些方法,可以有效地减少 DDL 操作对业务的影响,确保数据的一致性和完整性。尼恩还提供了大量面试题和解决方案,帮助读者在面试中充分展示技术实力。
|
24天前
|
SQL 算法 关系型数据库
面试:什么是死锁,如何避免或解决死锁;MySQL中的死锁现象,MySQL死锁如何解决
面试:什么是死锁,死锁产生的四个必要条件,如何避免或解决死锁;数据库锁,锁分类,控制事务;MySQL中的死锁现象,MySQL死锁如何解决
|
1月前
|
SQL 关系型数据库 MySQL
美团面试:Mysql如何选择最优 执行计划,为什么?
在40岁老架构师尼恩的读者交流群中,近期有小伙伴面试美团时遇到了关于MySQL执行计划的面试题:“MySQL如何选择最优执行计划,为什么?”由于缺乏系统化的准备,小伙伴未能给出满意的答案,面试失败。为此,尼恩为大家系统化地梳理了MySQL执行计划的相关知识,帮助大家提升技术水平,展示“技术肌肉”,让面试官“爱到不能自已”。相关内容已收录进《尼恩Java面试宝典PDF》V175版本,供大家参考学习。
|
2月前
|
存储 关系型数据库 MySQL
mysql 引擎概述
MySQL存储引擎是处理不同类型表操作的组件,InnoDB是最常用的默认引擎,支持事务、行级锁定和外键。MySQL采用插件式存储引擎架构,支持多种引擎,如MyISAM、Memory、CSV等,每种引擎适用于不同的应用场景。通过`SHOW ENGINES`命令可查看当前MySQL实例支持的存储引擎及其状态。选择合适的存储引擎需根据具体业务需求和引擎特性来决定。
|
2月前
|
消息中间件 架构师 Java
阿里面试:秒杀的分布式事务, 是如何设计的?
在40岁老架构师尼恩的读者交流群中,近期有小伙伴在面试阿里、滴滴、极兔等一线互联网企业时,遇到了许多关于分布式事务的重要面试题。为了帮助大家更好地应对这些面试题,尼恩进行了系统化的梳理,详细介绍了Seata和RocketMQ事务消息的结合,以及如何实现强弱结合型事务。文章还提供了分布式事务的标准面试答案,并推荐了《尼恩Java面试宝典PDF》等资源,帮助大家在面试中脱颖而出。
|
2月前
|
SQL 关系型数据库 MySQL
阿里面试:MYSQL 事务ACID,底层原理是什么? 具体是如何实现的?
尼恩,一位40岁的资深架构师,通过其丰富的经验和深厚的技術功底,为众多读者提供了宝贵的面试指导和技术分享。在他的读者交流群中,许多小伙伴获得了来自一线互联网企业的面试机会,并成功应对了诸如事务ACID特性实现、MVCC等相关面试题。尼恩特别整理了这些常见面试题的系统化解答,形成了《MVCC 学习圣经:一次穿透MYSQL MVCC》PDF文档,旨在帮助大家在面试中展示出扎实的技术功底,提高面试成功率。此外,他还编写了《尼恩Java面试宝典》等资料,涵盖了大量面试题和答案,帮助读者全面提升技术面试的表现。这些资料不仅内容详实,而且持续更新,是求职者备战技术面试的宝贵资源。
阿里面试:MYSQL 事务ACID,底层原理是什么? 具体是如何实现的?