Mysql 中已提交读隔离级别中的间隙锁定

Din*_*mar 5 mysql innodb transaction locking isolation-level

MYSQL VERSION : 5.7.X
STORAGE ENGINE : Innodb
Run Code Online (Sandbox Code Playgroud)

我有一个大致的想法,即 Read Committed Isolation 将主要使用 Shared and Exclusive Record Locks。但是,根据 mysql docs,在某些情况下,甚至 Read Committed 也必须使用间隙锁定

READ COMMITTED ..... 对于锁定读取(SELECT with FOR UPDATE 或 LOCK IN SHARE MODE)、UPDATE 语句和 DELETE 语句,InnoDB 只锁定索引记录,而不锁定它们之前的间隙,因此允许自由插入新记录在锁定的记录旁边。间隙锁定仅用于外键约束检查和重复键检查。

恕我直言,只有记录锁就足够了。谁能解释一下 Gap 锁定的场景以及为什么 mysql 会这样做?

mus*_*cio 7

在读提交 (RC) 隔离级别下需要间隙锁,以防止由于并发插入而导致的潜在完整性违规——这就是文档声明

间隙锁定仅用于外键约束检查和重复键检查。

暗示。

假设具有 RC 隔离的事务 T1 更新作为唯一键(索引)一部分的列的值。显然,这只有在新键值不存在时才有可能。假设同样具有 RC 隔离的事务 T2 也尝试插入一个新记录,其键值等于 T1 刚创建的键值。由于 RC 隔离级别 T2 看不到 T1 所做的更改,因为它尚未提交,因此不会引发重复密钥错误。如果不是针对所讨论的唯一索引的间隙锁定,这最终会导致两条记录具有相同的据称唯一键。

随着MySQL的8,你可以在使用行动的意见,看到这data_locksdata_lock_waitsperformance_schema。我创建了这个示例表:

create table fruits (
  id int not null auto_increment primary key, 
  name varchar(10), 
  property varchar(20)
);
create unique index fruits_ux1 on fruits (name, property);
insert into fruits (name, property) 
values ('apple', 'red'), ('apple', 'tart'), 
       ('banana', 'yellow'), ('banana', 'sweet');
Run Code Online (Sandbox Code Playgroud)

在事务 1 中,我更新了香蕉属性:

T1> set transaction isolation level read committed;

T1> begin;

T1> update fruits set property = concat(property, '?') where name = 'banana';
Run Code Online (Sandbox Code Playgroud)

我可以看到具有新值的键条目上设置了间隙锁(这些行在提交事务之前并不真正存在,因此这里不能使用标准 X 锁):

mysql> select thread_id, object_name, index_name, engine_lock_id, lock_type, lock_mode, lock_data  from performance_schema.data_locks;
+-----------+-------------+------------+---------------------------------------+-----------+---------------+------------------------+
| thread_id | object_name | index_name | engine_lock_id                        | lock_type | lock_mode     | lock_data              |
+-----------+-------------+------------+---------------------------------------+-----------+---------------+------------------------+
|        47 | fruits      | NULL       | 140491829069632:1066:140491735749880  | TABLE     | IX            | NULL                   |
|        47 | fruits      | fruits_ux1 | 140491829069632:5:5:1:140491735746840 | RECORD    | X             | supremum pseudo-record |
|        47 | fruits      | fruits_ux1 | 140491829069632:5:5:4:140491735746840 | RECORD    | X             | 'banana', 'yellow', 3  |
|        47 | fruits      | fruits_ux1 | 140491829069632:5:5:5:140491735746840 | RECORD    | X             | 'banana', 'sweet', 4   |
|        47 | fruits      | PRIMARY    | 140491829069632:5:4:6:140491735747184 | RECORD    | X,REC_NOT_GAP | 4                      |
|        47 | fruits      | PRIMARY    | 140491829069632:5:4:7:140491735747184 | RECORD    | X,REC_NOT_GAP | 3                      |
|        47 | fruits      | fruits_ux1 | 140491829069632:5:5:6:140491735747528 | RECORD    | X,GAP         | 'banana', 'sweet?', 4  |
|        47 | fruits      | fruits_ux1 | 140491829069632:5:5:7:140491735747528 | RECORD    | X,GAP         | 'banana', 'yellow?', 3 |
+-----------+-------------+------------+---------------------------------------+-----------+---------------+------------------------+
Run Code Online (Sandbox Code Playgroud)

在事务 2 中,我现在尝试插入一个可能的重复项:

T2> set transaction isolation level read committed;

T2> begin;

T2> insert into fruits (name, property) values ('banana', 'sweet?');
Run Code Online (Sandbox Code Playgroud)

该语句阻塞,我可以看到它阻塞了该唯一索引键的间隙锁(注意blocking_engine_lock_id值):

mysql> select requesting_engine_lock_id, blocking_engine_lock_id from performance_schema.data_lock_waits;
+---------------------------------------+---------------------------------------+
| requesting_engine_lock_id             | blocking_engine_lock_id               |
+---------------------------------------+---------------------------------------+
| 140491829070536:5:5:6:140491735753104 | 140491829069632:5:5:6:140491735747872 |
+---------------------------------------+---------------------------------------+
Run Code Online (Sandbox Code Playgroud)

类似地,如果 T1 从父表中删除一行,而 T2 尝试将新记录插入到引用 T1 刚刚删除的行(它仍然看到)的子表中,外键索引上的间隙锁会阻止插入潜在的孤儿记录。


小智 1

从这里:

https://www.percona.com/blog/2012/03/27/innodbs-gap-locks/

间隙锁是对索引记录之间间隙的锁定。由于这个间隙锁,当您运行相同的查询两次时,您会得到相同的结果,无论该表上的其他会话修改如何。这使得读取一致,从而使服务器之间的复制一致。如果执行 SELECT * FROM id > 1000 FOR UPDATE 两次,您期望两次获得相同的值。为了实现这一点,InnoDB 使用排他锁锁定 WHERE 子句找到的所有索引记录,并使用共享间隙锁锁定它们之间的间隙。