前言

大部分人在日常的业务开发中,其实很少去关注数据库的事务相关问题,基本上都是 CURD 一把梭。正好最近在看 MySQL 的相关基础知识,其中对于幻读问题之前一直没有理解深刻,今天就来聊聊「InnoDB 是如何解决幻读的」,话不多说,下面进入主题。

事务隔离级别

事务隔离是数据库处理的基础之一,是 ACID 中的 I。在 MySQL 的 InnoDB 引擎中支持在 SQL:1992 标准中的四种事务隔离级别,如下图所示,其中 P1 表示脏读(Dirty read),P2 表示不可重复读(Dirty read),P3 表示幻读(Phantom)。

为什么需要定义这么多隔离呢?从上图中也能猜出一二了,InnoDB 提供多个隔离级别主要原因是:让使用者可以在多个事务同时进行更改和执行查询时微调性能与结果的可靠性、一致性和可再现性之间的平衡的设置。是一种性能与结果可靠性间的 trade off

什么是幻读

在聊「InnoDB 解决幻读方式」前我们需要先了解幻读是什么官方文档的描述如下:

A row that appears in the result set of a query, but not in the result set of an earlier query.

其中我加粗的「result set」是关键的地方,两次查询返回的是结果集,说明必须是一个范围查询操作。总结下,幻读就是:在同一个事务中,在前后两次查询相同范围时,两次查询得到的结果是不一致的。所以幻读会产生数据一致性问题。

InnoDB 解决幻读方式

为了解决上述的幻读问题,InnoDB 引入了两种锁,分别是「间隙锁」和「next-key 锁」。下面通过一个示例来描述这两种锁的作用分别是什么。假如存在一个这样的 B+ Tree 的索引结构,结构中有 4 个索引元素分别是:9527、9530、9535、9540。

此时当我们使用如下 SQL 通过主键索引查询一条记录,并且加上 X 锁(排它锁)时:

select * from user where id = 9527 for update;

这时就会产生一个记录锁(也就是行锁),锁定 id = 9527 这个索引。

在被锁定的记录(这里是 id = 9527)的锁释放之前,其它事务无法对这条被锁定记录做任何操作。再回忆一下,前面说的幻读定义「在同一个事务中,在前后两次查询相同范围时,两次查询得到的结果是不一致」。注意,这里强调的是范围查询。

InnoDB 要解决幻读问题,就必须得保证在如果在一个事务中,通过如下这条语句进行锁定时:

select * from user where id > 9530 and id < 9535 for update;

此时,另外一个语句再执行一如下这条 insert 语句时,需要被阻塞,直到上面这个获得锁的事务释放锁后才能执行。

insert into user(id, name, age) values(9533, 'Jack', 44);

为此,InnoDB 引入了「间隙锁」,它的主要功能是锁定一段范围内的索引记录。比如上面查询 id > 9530 and id < 9535 的时候,对 B+ Tree 中的(9530,9535)这个开区间范围的索引加间隙锁。

在这种加了间隙锁的情况下,其它事务对这个区间的数据进行插入、更新、删除都会被锁住直到这个获取到锁的事务释放。

这种是在区间之间的情况,你可能想到另外的一种情况:锁定多个区间,如下的一条语句:

select * from user where id > 9530 for update;

上面这条查询语句是针对 id > 9530 这个条件加锁,那么此时它需要锁定多个索引区间,所以在这种情况下 InnoDB 引入了「next-key 锁」机制。其实 next-key 锁的效果相当于间隙锁和记录锁的合集,记录锁锁定存在的记录行,间隙锁锁住记录行之间的间隙,而 next-key 锁它锁住的是两者之和。

在 InnoDB 中,每个数据行上的非唯一索引列上都会存在一把 next-key 锁,当某个事务持有该数据行的 next-key 锁时,会锁住一段左开右闭区间的数据。因此,当通过 id > 9530 这样一种范围查询加锁时,会加 next-key 锁,锁定区间是范围是:

(9530,9535] (9535,9540] (9540,+∞]

间隙锁(也叫 Gap 锁)和 next-key 锁的区别在于加锁的范围,间隙锁只锁定两个索引之间的引用间隙,而 next-key 锁会锁定多个索引区间,它包含「记录锁」和「间隙锁」。所以,当我们使用了范围查询,不仅仅命中了已存在的 Record 记录,还包含了 Gap 间隙。

总结

虽然在 InnoDB 引擎中通过间隙锁和 next-key 锁的方式解决了幻读问题,但是加锁之后会影响到数据库的并发性能,因此,如果对性能要求较高的业务场景中,建议把隔离级别设置成 RC(READ COMMITTED),这个级别中不存在间隙锁,但是需要考虑到幻读问题会导致的数据一致性。

InnoDB 是如何解决幻读的的更多相关文章

  1. 【面试普通人VS高手系列】innoDB如何解决幻读

    前天有个去快手面试的小伙伴私信我,他遇到了这样一个问题: "InnoDB如何解决幻读"? 这个问题确实不是很好回答,在实际应用中,很多同学几乎都不关注数据库的事务隔离性. 所有问题 ...

  2. InnoDB在MySQL默认隔离级别下解决幻读

    1.结论 在RR的隔离级别下,Innodb使用MVVC和next-key locks解决幻读,MVVC解决的是普通读(快照读)的幻读,next-key locks解决的是当前读情况下的幻读. 2.幻读 ...

  3. MySQL 是如何解决幻读的

    MySQL 是如何解决幻读的 一.什么是幻读 在一次事务里面,多次查询之后,结果集的个数不一致的情况叫做幻读. 而多出来或者少的哪一行被叫做 幻行 二.为什么要解决幻读 在高并发数据库系统中,需要保证 ...

  4. MySQL的可重复读级别能解决幻读吗

    引言 之前在深入了解数据库理论的时候,了解到事物的不同隔离级别可能存在的问题.为了更好的理解所以在MySQL数据库中测试复现这些问题.关于脏读和不可重复读在相应的隔离级别下都很容易的复现了.但是对于幻 ...

  5. MVCC 能解决幻读吗?

    MySQL通过MVCC(解决读写并发问题)和间隙锁(解决写写并发问题)来解决幻读 MySQL InnoDB事务的隔离级别有四级,默认是“可重复读”(REPEATABLE READ). 未提交读(REA ...

  6. MYSQL如何解决幻读

    第一部分 首先要了解下mysql数据库的事务特征之一隔离级别: READ UNCOMMITTED(未提交读): 在READUNCOMMITTED级别,事务中的修改,即使没有提交,对其他事务也都是可见的 ...

  7. 何为幻读?MySQL又是如何解决幻读的?

    一.什么是幻读 在一次事务里面,多次查询之后,查询的结果集的个数不一致的情况叫做幻读.而多出来或者少的哪一行被叫做 幻行 二.为什么要解决幻读 在高并发数据库系统中,需要保证事务与事务之间的隔离性,还 ...

  8. MySQL 到底是怎么解决幻读的?

    ; 原理:将历史数据存一份快照,所以其他事务增加与删除数据,对于当前事务来说是不可见的. 2. next-key 锁 (当前读) next-key 锁包含两部分: 记录锁(行锁) 间隙锁 记录锁是加在 ...

  9. 【大厂面试03期】MySQL是怎么解决幻读问题的?

    问题分析 首先幻读是什么? 根据MySQL文档上面的定义 The so-called phantom problem occurs within a transaction when the same ...

  10. MySQL锁(三)行锁:幻读是什么?如何解决幻读?

    概述 前面两篇文章介绍了MySQL的全局锁和表级锁,今天就介绍一下MySQL的行锁. MySQL的行锁是各个引擎内部实现的,不是所有的引擎支持行锁,例如MyISAM就不支持行锁. 不支持行锁就意味着在 ...

随机推荐

  1. HDFS相关问题处理

    机房搬迁后datanode启动失败,报错如下: 2022-10-21 10:28:40,551 INFO org.apache.hadoop.hdfs.server.common.Storage: L ...

  2. Vuex----Mutations

    注意: 只能通过 mutations里的函数才能修改 state 中的数据 第一种方法: const store = new Vuex.Store({ state:{ count:0 }, mutat ...

  3. 页面与java后台之 上传文件与服务器加载

    添加相关依赖包commons-fileupload等: 页面(注:编码enctype="multipart/form-data"   文件按钮属性multiple) java    ...

  4. antv g6 出现 n.addEdge is not a function问题

    问题描述直接上图 解决方式就是将edge里面边的source和target对应的id换成字符串类型就行. 例如: edges: [ { id: 299, source": 3629.toSt ...

  5. oracle表名中带@什么意思,例如:select * from dim.dim_area_no@to_dw

    转载自:https://zhidao.baidu.com/question/259154968.html @是调用数据库链接(database link)的意思. 数据库链接的作用是从a数据库到b数据 ...

  6. golang sync.RWMutex总结笔记

    背景 最近项目中遇到两次RWMutex死锁问题,所以稍微看了一下资料和源码,稍作记录 源码 type RWMutex struct { w Mutex // held if there are pen ...

  7. C# DevExpress gridview 字符串尾部带数字如何排序

    我们经常遇到这样的问题,字符串尾部带数字,如何正确排序; 首先设置GridView ,Columns 的相关列,设置属性中,SortMode为Custom 解决思路,把字符串尾缀数字,分离出来.先比较 ...

  8. 读取nrf52832的ADC,并且获取N个数组中的中间值

    //****读取中间值****// short GetMedianNum(short * bArray, short iFilterLen) { short i,j,bTemp;// 排序循环 for ...

  9. 一道测试Java值传递的题目

    请给出下列代码的执行结果: public class T3 { public static void main(String[] args) { T3 t3 = new T3(); t3.first( ...

  10. 在IDEA中通过插件显示每一行代码的最后编辑的人

    !!!首先:需要结合Git使用 插件github:https://github.com/zielu/GitToolBox/wiki/Manual 一.安装插件并重启 二.打开设置(重启默认打开) 三. ...