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 会这样做?
在读提交 (RC) 隔离级别下需要间隙锁,以防止由于并发插入而导致的潜在完整性违规——这就是文档声明
间隙锁定仅用于外键约束检查和重复键检查。
暗示。
假设具有 RC 隔离的事务 T1 更新作为唯一键(索引)一部分的列的值。显然,这只有在新键值不存在时才有可能。假设同样具有 RC 隔离的事务 T2 也尝试插入一个新记录,其键值等于 T1 刚创建的键值。由于 RC 隔离级别 T2 看不到 T1 所做的更改,因为它尚未提交,因此不会引发重复密钥错误。如果不是针对所讨论的唯一索引的间隙锁定,这最终会导致两条记录具有相同的据称唯一键。
随着MySQL的8,你可以在使用行动的意见,看到这data_locks
和data_lock_waits
中performance_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 子句找到的所有索引记录,并使用共享间隙锁锁定它们之间的间隙。