MySQL的RR和RC事务隔离级别加锁类型验证

本文详细探讨了MySQL5.7中Innodb引擎在不同事务隔离级别(可重复读RR和读已提交RC)下的锁行为。在RR级别,事务使用Next-KeyLock和RecordLock,并保持一致性读视图。而在RC级别,事务仅使用RecordLock,并能读取其他事务的提交。通过示例展示了如何观察和理解锁的状态以及MVCC的读视图生成机制。

先上结轮:MySQL5.7数据库Innodb引擎在默认的 REPEATABLE-READ(可重复读RR) 事务隔离级别时,事务修改类操作对于where范围条件锁定的行区加的是Next-Key Lock 即临键间隙锁,对于确切条件锁定的行加的是RECORD LOCKS 即普通行锁,并且无法读到自身事务范围之外其他事务的提交(而且自身事务锁定的范围无法被其他事务修改或插入新值);在READ-COMMITTED(读已提交RC)事务隔离级别时,事务修改类操作对于where范围条件或确切条件锁定的行加的都是RECORD LOCKS 即普通行锁,并且能够读到自身事务锁定行范围之外其他事务的提交(但自身事务锁定的行无法被其他事务修改);

-- 打开Innodb状态输出开关
set GLOBAL innodb_status_output=ON;
-- 打开Innodb锁状态输出开关
set GLOBAL innodb_status_output_locks=ON;
-- 查看当前数据库隔离级别
select @@transaction_isolation;
-- 设置会话的事务隔离级别(RC的设为 'READ-COMMITTED' )
set @@session.transaction_isolation = 'REPEATABLE-READ'; 
COMMIT;
-- 设置会话的隔离级别(RC的设为 COMMITTED READ)
set session transaction isolation level REPEATABLE READ;
COMMIT;

-- 以后脚本手工一行一行执行(在另一个窗口,另个其他事务进行各种场景的验证,并到Linux上的MySQL客户端用 show engine innodb status\G 命令查看上锁状态)
BEGIN;
SELECT * from test_user WHERE id=1;
UPDATE test_user set NAME='1234' WHERE id<4;
SELECT * from test_user WHERE id=6;

-- 最后执行回滚
ROLLBACK;

到Linux上的MySQL客户端用 show engine innodb status\G; 命令查看上锁状态!

RR事务隔离级别时的锁状态(临键间隙锁):

lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 2, OS thread handle 139641668019968, query id 257 192.168.17.1 xurm
Trx read view will not see trx with id >= 6928, sees < 6928  # RR级别多输出的一行,表明了只会产生一次 read view,并确定了其可看到的事务id 范围!
TABLE LOCK table `test`.`test_user` trx id 6928 lock mode IX
RECORD LOCKS space id 26 page no 3 n bits 72 index PRIMARY of table `test`.`test_user` trx id 6928 lock_mode X
Record lock, heap no 2 PHYSICAL RECORD: n_fields 6; compact format; info bits 0

RC事务隔离级别时的锁状态(排他锁(X)行锁,但非间隙锁):

lock struct(s), heap size 1136, 1 row lock(s), undo log entries 1
MySQL thread id 2, OS thread handle 139641668019968, query id 630192.168.17.1 xurm
TABLE LOCKtable `test`.`test_user` trx id 6941 lock mode IXRECORD LOCKS space id 26 page no 3 n bits 80index PRIMARY of table `test`.`test_user` trx id 6941lock_mode X locks rec but not gap
Record lock, heap no 8 PHYSICAL RECORD: n_fields 6; compact format; info bits 0

  我们将事务里的更新条件由小于(<)改成大于(>):

BEGIN;
SELECT * from test_user WHERE id=1;
-- 换成>4
UPDATE test_user set NAME='1234' WHERE id>4;
SELECT * from test_user WHERE id=6;

  RR事务隔离级别时的锁状态(显示有2把锁,但没有输出具体哪2把锁,其实是一把IX排他意向锁和一把(4,+∞)的间隙锁:

---TRANSACTION 7437, ACTIVE 3 sec
lock struct(s), heap size 1136, 9 row lock(s), undo log entries 8
MySQL thread id 3, OS thread handle 139860111124224, query id 239 192.168.17.1 xurm
--------
FILE I/O
--------

  我们将事务里的更新条件改成大于(>=):

BEGIN;
SELECT * from test_user WHERE id=1;
-- 换成>4
UPDATE test_user set NAME='1234' WHERE id>=4;
SELECT * from test_user WHERE id=6;

  RR事务隔离级别时的锁状态(显示有3把锁,但没有输出具体哪3把锁,其实是一把IX排他意向锁、一把针对id=4的行的记录锁(普通行锁)和一把(4,+∞)的间隙锁:

---TRANSACTION 7448, ACTIVE 3 sec
lock struct(s), heap size 1136, 10 row lock(s), undo log entries 9
MySQL thread id 3, OS thread handle 139860111124224, query id 361 192.168.17.1 xurm
--------
FILE I/O
--------

  新开一个事务会话验证修改id=4的记录:

BEGIN;
SELECT * from test_user WHERE id=4;
-- 其他事务验证修改
UPDATE test_user set NAME='4321工12' WHERE id=4;
SELECT * from test_user WHERE id=4;

-- 最后执行回滚
ROLLBACK;

  修改id=4的行时发生了锁等待,查看RR事务隔离级别时的锁状态(新的事务想在id=4的行上加一把 记录锁(普通行锁) 结果需要等待):

---TRANSACTION 7451, ACTIVE 5 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 8, OS thread handle 139860108728064, query id 394 192.168.17.1 xurm updating
UPDATE test_user set NAME='4321工12' WHERE id=4
------- TRX HAS BEEN WAITING 5 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 26 page no 3 n bits 128 index PRIMARY of table `test`.`test_user` trx id 7451 lock_mode X locks rec but not gap waiting
Record lock, heap no 62 PHYSICAL RECORD: n_fields 6; compact format; info bits 0
 0: len 8; hex 8000000000000004; asc         ;;
 1: len 6; hex 000000001d1a; asc       ;;
 2: len 7; hex 3100000143049c; asc 1   C  ;;
 3: len 4; hex 31323334; asc 1234;;
 4: len 4; hex 8000001f; asc     ;;
 5: len 7; hex e6b58be8af9531; asc       1;;

------------------
---TRANSACTION 7450, ACTIVE 12 sec
3 lock struct(s), heap size 1136, 10 row lock(s), undo log entries 9
MySQL thread id 3, OS thread handle 139860111124224, query id 390 192.168.17.1 xurm
--------
FILE I/O
--------

  新开一个事务会话验证在id>=4的后续间隙插入新的记录:

BEGIN;
SELECT * from test_user WHERE id=4;
-- 其他事务验证插入
INSERT INTO `test`.`test_user` (`name`, `age`, `remark`) VALUES ('Rainy123555', 31, '测试1155');

-- 最后执行回滚
ROLLBACK;

  尝试在id>=4的后续间隙插入新的记录行时发生了锁等待,查看RR事务隔离级别时的锁状态(新的事务想在id>=4的后续间隙加一把 插入意向锁 结果需要等待,注意【插入意向锁】跟加在表上的【意向锁】是两类不同的锁):

---TRANSACTION 7454, ACTIVE 4 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s)
MySQL thread id 8, OS thread handle 139860108728064, query id 442 192.168.17.1 xurm update
INSERT INTO `test`.`test_user` (`name`, `age`, `remark`) VALUES ('Rainy123555', 31, '测试1155')
------- TRX HAS BEEN WAITING 4 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 26 page no 3 n bits 136 index PRIMARY of table `test`.`test_user` trx id 7454 lock_mode X insert intention waiting
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
 0: len 8; hex 73757072656d756d; asc supremum;;

------------------
---TRANSACTION 7453, ACTIVE 21 sec
3 lock struct(s), heap size 1136, 10 row lock(s), undo log entries 9
MySQL thread id 3, OS thread handle 139860111124224, query id 437 192.168.17.1 xurm
--------
FILE I/O
--------

  最后,再配合两种隔离级别下事务范围内的 read view (读视图)生成机制:RR事务隔离级别只在第一次MVCC读(即快照读)时生成一次事务的read view,即一次性确定read view的可见事务范围,因此可以做到可重复读而RC事务隔离级别会在每一次MVCC读时都重新生成一次事务的read view,因此能够读到其他事务的已提交数据

<think>好的,用户问的是在已提交读(READ COMMITTED)隔离级别下,为什么间隙锁(Gap Locks)临键锁(Next-Key Locks)都会失效。我需要先理解这个问题的背景,然后逐步分析原因。 首先,隔离级别数据库事务处理中的一个重要概念,不同的级别控制事务之间的可见性锁定行为。已提交读(RC)是其中一种常见的隔离级别,而可重复读(RR)是另一个更高级别的隔离。在MySQL的InnoDB引擎中,通常RR级别会使用间隙锁临键锁来防止幻读,但RC级别下这些锁可能不会生效。 接下来,我需要回忆RCRR的主要区别。RC允许事务看到其他事务已提交的更改,所以每次读取都会获取最新的提交数据。而RR则保证在同一个事务中多次读取同一范围的数据时,结果一致,防止幻读。为了实现这一点,RR使用了间隙锁临键锁来锁定索引范围,阻止其他事务插入新数据,从而避免幻读。 那为什么在RC下这些锁会失效呢?可能的原因包括RC的设计目标是不需要防止幻读,所以不需要这些锁。此外,RC的锁定策略可能更宽松,只锁定实际存在的行,而不是范围。这样虽然提高了并发性,但牺牲了防止幻读的能力。 需要确认InnoDB在RC下是否真的不使用间隙锁临键锁。根据MySQL文档,RC隔离级别下,InnoDB确实不会使用间隙锁或临键锁,仅使用记录锁(行锁)。因此,在RC下,事务只锁定涉及的具体行,而不会锁定行之间的间隙,这样其他事务可以插入新行到间隙中,导致幻读。 但用户的问题可能还涉及到为什么这样设计。可能是因为RC更注重并发性能,减少锁的范围,允许更高的并发,而幻读在某些应用场景中是可以接受的,或者通过其他方式处理,比如应用层的检查。 总结来说,RC隔离级别间隙锁临键锁失效的原因在于该级别不要求防止幻读,因此数据库优化了锁定机制,仅使用行锁来提高并发,而放弃了范围锁定,导致这些锁不再生效。</think>在已提交读(READ COMMITTED,RC隔离级别下,间隙锁(Gap Locks)临键锁(Next-Key Locks)失效的核心原因与其设计目标有关。以下是逐步分析: --- ### **1. 隔离级别的设计目标** RC 隔离级别的核心要求是: - **只读取已提交的数据**(避免脏读) - **不保证可重复读**(允许其他事务修改已读数据) - **不防止幻读**(允许其他事务插入新数据) 由于不要求防止幻读,数据库系统无需对**未命中的查询范围**加锁(这正是间隙锁临键锁的作用)。 --- ### **2. 锁机制的行为差异** #### **在可重复读(REPEATABLE READ,RR)中**: - 临键锁(Next-Key Lock)= **记录锁(Record Lock) + 间隙锁(Gap Lock)** - 锁住索引记录本身及其前后的间隙,防止其他事务插入新数据(避免幻读)。 #### **在已提交读(RC)中**: - **仅使用记录锁(Record Lock)**:仅锁定实际访问到的行。 - **放弃间隙锁临键锁**:不锁定行之间的间隙,允许其他事务插入新数据。 --- ### **3. 具体失效原因** #### **(1) 间隙锁失效** - **间隙锁**的作用是锁住索引区间,防止其他事务在范围内插入数据。 - RC 允许幻读,因此无需锁定未访问的间隙。例如: ```sql -- RC 隔离级别下 SELECT * FROM users WHERE age > 20 AND age < 30; ``` 仅锁定表中实际存在的 `age=21`、`age=25` 等具体行,而不会锁定 `20 < age < 30` 的间隙。 #### **(2) 临键锁失效** - **临键锁**是 InnoDB 在 RR 级别下默认的锁机制,本质是扩大化的记录锁。 - RC 隔离级别下,临键锁退化为简单的**记录锁**。例如: ```sql -- RC 隔离级别下 UPDATE accounts SET balance = 100 WHERE id = 5; ``` 仅锁定 `id=5` 的行,不会锁住 `id>5` 的间隙。 --- ### **4. 性能与一致性的权衡** - **RC 的优势**:减少锁冲突,提高并发性(允许更多事务并行修改数据)。 - **RC 的代价**:允许幻读非重复读(同一事务中多次查询结果可能不一致)。 --- ### **5. 验证示例** #### 场景描述: 1. 事务 A(RC 级别)执行范围查询: ```sql SELECT * FROM products WHERE price BETWEEN 10 AND 20; ``` 2. 事务 B 插入 `price=15` 的新记录并提交。 3. 事务 A 再次执行相同查询,可能读到新插入的行(幻读)。 #### 结果: - 在 RC 下,事务 A 不会阻止事务 B 的插入(无间隙锁)。 - 在 RR 下,事务 B 会被阻塞(临键锁锁住范围)。 --- ### **总结** | 隔离级别 | 间隙锁/临键锁 | 幻读防护 | 并发性能 | |-------------|-------------|--------|---------| | READ COMMITTED | ❌ 失效 | ❌ 不防护 | ⭐⭐⭐ 高 | | REPEATABLE READ | ✅ 生效 | ✅ 防护 | ⭐ 低 | 已提交读(RC)通过放弃间隙锁临键锁,以允许幻读为代价,换取了更高的并发性能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值