前言

大部分人在日常的业务开发中,其实很少去重视数据库的业务相关问题,基本上都是 CURD 一把梭。正好最近在看 MySQL 的相关根底知识,其间对于幻读问题之前一向没有了解深入,今日就来聊聊「InnoDB 是怎么处理幻读的」,话不多说,下面进入主题。

业务阻隔等级

业务阻隔是数据库处理的根底之一,是 ACID 中的 I。在 MySQL 的 InnoDB 引擎中支撑在 SQL:1992 规范中的四种业务阻隔等级,如下图所示,其间 P1 表明脏读(Dirty read),P2 表明不可重复读(Dirty read),P3 表明幻读(Phantom)。

InnoDB 是如何解决幻读的

为什么需求界说这么多阻隔呢?从上图中也能猜出一二了,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 处理幻读方法

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

InnoDB 是如何解决幻读的

此刻当咱们运用如下 SQL 经过主键索引查询一条记载,而且加上 X 锁(排它锁)时:

select * from user where id = 9527 for update;

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

InnoDB 是如何解决幻读的

在被确定的记载(这儿是 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)这个开区间规模的索引加空隙锁。

在这种加了空隙锁的状况下,其它业务对这个区间的数据进行刺进、更新、删除都会被锁住直到这个获取到锁的业务开释。

InnoDB 是如何解决幻读的

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

select * from user where id > 9530 for update;

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

InnoDB 是如何解决幻读的

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

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

InnoDB 是如何解决幻读的

空隙锁(也叫 Gap 锁)和 next-key 锁的差异在于加锁的规模,空隙锁只确定两个索引之间的引证空隙,而 next-key 锁会确定多个索引区间,它包含「记载锁」和「空隙锁」。所以,当咱们运用了规模查询,不仅仅命中了已存在的 Record 记载,还包含了 Gap 空隙。

总结

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