共计 1953 个字符,预计需要花费 5 分钟才能阅读完成。
前言
大部分人在日常的业务开发中,其实很少去关注数据库的事务相干问题,基本上都是 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),这个级别中不存在间隙锁,然而须要思考到幻读问题会导致的数据一致性。