MySQL InnoDB在Repeatable Read事务隔离级别的加锁情况

MySQL版本:8.0.30

REPEATABLE READ事务隔离级别索引加锁测试

sql 复制代码
CREATE TABLE `user` (
  `id` int NOT NULL,
  `money` int DEFAULT NULL,
  `age` int DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `index_age` (`age`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

INSERT INTO user VALUES(1,1,10);
INSERT INTO user VALUES(4,4,14);
INSERT INTO user VALUES(8,8,18);
INSERT INTO user VALUES(12,12,22);
INSERT INTO user VALUES(16,16,26);

设置和查看事务隔离级别

sql 复制代码
SET SESSION TRANSACTION ISOLATION LEVEL  REPEATABLE READ;
SELECT @@TRANSACTION_ISOLATION;

MySQL默认的事务隔离级别为REPEATABLE READ

唯一索引命中

时间 事务1 事务2
T1 BEGIN;
T2 UPDATE user SET money=44 WHERE id=4;
T3 BEGIN;
T4 阻塞: SELECT * FROM user WHERE id=4 for UPDATE; 1205 - Lock wait timeout exceeded; try restarting transaction
T5 不阻塞: INSERT INTO user VALUES(3,3,13); INSERT INTO user VALUES(5,5,15);
T6 COMMIT;
T7 不阻塞: SELECT * FROM user WHERE id=4 for UPDATE;
T8 ROLLBACK;
T9 UPDATE user SET money=4 WHERE id=4;
  • 事务1对主键索引(或唯一索引)id=4加了记录互斥锁,所以T4时,事务2申请对id=4加记录互斥锁的请求被阻塞
  • T5时,往id=3id=5插入数据都可以成功
  • T6时,事务1提交事务,释放了持有的id=4的记录锁,所以T7时,事务2再次申请id=4的记录互斥锁成功。
  • T8和T9还原现有的环境,用于后续实验。
  • 所以唯一索引命中,加记录互斥锁。

T4时,performance_schema.data_locks表的锁占用情况:

唯一索引未命中

时间 事务1 事务2
T1 BEGIN;
T2 UPDATE user SET money=3 WHERE id=3;
T3 BEGIN;
T4 不阻塞: SELECT * FROM user WHERE id=3 for UPDATE; UPDATE user SET money=33 WHERE id=3; SELECT * FROM user WHERE id=1 FOR UPDATE; SELECT * FROM user WHERE id=4 FOR UPDATE; INSERT INTO user VALUES(5,5,15);
T5 ROLLBACK; BEGIN;
T6 阻塞: INSERT INTO user VALUES(2,2,12); INSERT INTO user VALUES(3,3,13);
T7 COMMIT;
T8 不阻塞: INSERT INTO user VALUES(2,2,12); INSERT INTO user VALUES(3,3,13);
T10 ROLLBACK;
  • T2时,事务1访问id=3的主键索引,由于索引不存在,InnoDB在(1,4)范围加间隙锁
  • T4时,事务2尝试在(1,4)的边界14获取互斥锁都能够成功;尝试在边界外5插入数据,也能够成功;另外有一个重要的注意点,间隙锁和间隙锁之间并不互斥 ,事务T1在(1,4)范围加了间隙锁后,事务T2能马上在(1,4)范围加间隙锁,并不会阻塞。
  • T5时,回滚事务,重新开启一个事务,是为了简化data_locks表中的记录数,便于观察
  • T6时,事务2尝试在(1,4)的间隙锁范围插入数据均被阻塞
  • 所以,唯一索引未命中 的情况下,会加间隙锁开区间,间隙锁会阻塞在对应范围的数据插入,

T4截图

T6截图

唯一索引范围查询

时间 事务1 事务2
T1 BEGIN;
T2 SELECT * FROM user WHERE id BETWEEN 3 AND 6 FOR UPDATE;
T3 BEGIN;
T4 不阻塞: SELECT * FROM user WHERE id=1 for UPDATE; SELECT * FROM user WHERE id=8 for UPDATE;
T5 阻塞: INSERT INTO user VALUES(2,2,12); INSERT INTO user VALUES(3,3,13); SELECT * FROM user WHERE id=4 FOR UPDATE; INSERT INTO user VALUES(5,5,15);
T6 COMMIT;
T7 不阻塞: INSERT INTO user VALUES(2,2,12); INSERT INTO user VALUES(3,3,13);
T8 ROLLBACK;
T9
  • T2时,事务1获取了主键索引id=4的记录互斥锁,并获取了id=8的间隙锁,照理说应该锁定的范围是[4,8)
  • T4时,事务2为id=1id=8加记录互斥锁,都不阻塞
  • T5时,事务2尝试在[4,8)范围获取记录互斥锁,均被阻塞,意料之中;但是出乎意料的是,id=2id=3的记录也无法插入,所以事务1实际锁定的范围在(1,8)
  • 从T5的截图可以看到,事务2的插入意向锁需要锁定(1,4)的间隙,而事务1锁定了id=4的记录互斥锁和(4,8)的间隙锁,不应该冲突啊,有没有哪位大佬解释下?
  • 所以,唯一索引范围查询,使用开区间,临键锁

T2时,锁占用情况

T4时,锁占用情况

T5时,锁占用情况

非唯一索引命中

时间 事务1 事务2
T1 BEGIN;
T2 SELECT * FROM user WHERE age=14 FOR UPDATE;
T3 BEGIN;
T4 不阻塞: SELECT * FROM user WHERE age=10 for UPDATE; SELECT * FROM user WHERE age=18 for UPDATE;
T5 阻塞: SELECT * FROM user WHERE id=4 for UPDATE; INSERT INTO user VALUES(3,3,13); INSERT INTO user VALUES(5,5,15);
T6 COMMIT;
T7 不阻塞: SELECT * FROM user WHERE id=4 for UPDATE;
T8 ROLLBACK;
T9
  • T2时,事务1对非唯一索引age=14获取互斥锁,从截图可以看到,InnoDB对主键索引id=4加了互斥记录锁,对age=14加了互斥记录锁,对age=(14,18)加了互斥间隙锁
  • T4时,事务2获取边界age=10age=18的互斥锁都请求成功
  • T5时,事务2获取id=4的互斥记录锁,被阻塞,符合预期;尝试在age=(14,18)插入数据,被阻塞,被阻塞;尝试在age=(10,14)插入数据,被阻塞,说明事务1锁定了(11,14)的范围
  • 所以,非唯一索引的等值匹配命中,使用临键锁 ,锁定当前记录+前后间隙,是开区间 (10,18),且会锁定对应的主键索引的记录锁

T2,加锁情况

T4,获取age=10的锁情况

非唯一索引未命中

时间 事务1 事务2
T1 BEGIN;
T2 SELECT * FROM user WHERE age=15 FOR UPDATE;
T3 BEGIN;
T4 不阻塞: SELECT * FROM user WHERE age=10 for UPDATE; SELECT * FROM user WHERE age=14 for UPDATE; SELECT * FROM user WHERE age=18 for UPDATE; INSERT INTO user VALUES(3,3,13);
T5 阻塞: INSERT INTO user VALUES(5,5,15);
T6 COMMIT;
T7 不阻塞: INSERT INTO user VALUES(5,5,15);
T8 ROLLBACK;
T9
  • T2时,事务1对非唯一索引age=15获取互斥锁,由于未命中,从截图可以看到,InnoDB对age=(14,18)加了互斥间隙锁
  • T4~T7验证了age=(14,18)的互斥间隙锁。

T2时,锁占用情况

非唯一索引范围查询

时间 事务1 事务2
T1 BEGIN;
T2 SELECT * FROM user WHERE age BETWEEN 13 AND 16 FOR UPDATE;
T3 BEGIN;
T4 不阻塞: SELECT * FROM user WHERE age=10 FOR UPDATE;
T5 阻塞: INSERT INTO user VALUES(2,2,12); INSERT INTO user VALUES(3,3,13); SELECT * FROM user WHERE age=14 FOR UPDATE; INSERT INTO user VALUES(5,5,15); SELECT * FROM user WHERE age=18 FOR UPDATE;
T6 COMMIT;
T7 不阻塞: INSERT INTO user VALUES(2,2,12); INSERT INTO user VALUES(3,3,13); SELECT * FROM user WHERE age=14 FOR UPDATE; INSERT INTO user VALUES(5,5,15); SELECT * FROM user WHERE age=18 FOR UPDATE;
T8 ROLLBACK;
T9
  • T2时,事务1执行非唯一索引的范围查询后,InnoDB锁定了主键索引id=4,并锁定了age=14age=18的非唯一索引。
  • T4时,事务2访问age=10获取互斥锁,成功获取
  • T5时,事务2无法获取age=14age=18的非唯一索引的互斥锁,符合预期;但是,对于age=(10,14)age=(14,18)范围的互斥锁也无法访问,说明InnoDB实际上在age=(10,18]范围加了临键锁。
  • 所以,非唯一索引的范围查询使用临键锁 ,且区间为前开后闭 。且会锁定区间内的主键索引的记录锁

T2时,锁占用情况

T5执行插入age=12加锁情况

无索引命中

时间 事务1 事务2
T1 BEGIN;
T2 SELECT * FROM user WHERE money=8 FOR UPDATE;
T3 BEGIN;
T4 阻塞: SELECT * FROM user WHERE money=1 FOR UPDATE; SELECT * FROM user WHERE money=3 FOR UPDATE; SELECT * FROM user WHERE money=4 FOR UPDATE; SELECT * FROM user WHERE money=8 FOR UPDATE; SELECT * FROM user WHERE money=16 FOR UPDATE; SELECT * FROM user WHERE money=22 FOR UPDATE; SELECT * FROM user WHERE money=23 FOR UPDATE; INSERT INTO user VALUES(100,100,100);
T5 COMMIT;
T6 ROLLBACK;
T7
T8
T9
  • T2时,事务1申请锁定money=8的记录,可以看到,InnoDB直接加了表锁,锁定了整张表,导致后续的查询都被阻塞。

T2时,加锁情况

无索引未命中

时间 事务1 事务2
T1 BEGIN;
T2 SELECT * FROM user WHERE money=9 FOR UPDATE;
T3 BEGIN;
T4 阻塞: SELECT * FROM user WHERE money=1 FOR UPDATE; SELECT * FROM user WHERE money=3 FOR UPDATE; SELECT * FROM user WHERE money=4 FOR UPDATE; SELECT * FROM user WHERE money=8 FOR UPDATE; SELECT * FROM user WHERE money=16 FOR UPDATE; SELECT * FROM user WHERE money=22 FOR UPDATE; SELECT * FROM user WHERE money=23 FOR UPDATE; INSERT INTO user VALUES(100,100,100);
T5 COMMIT;
T6 ROLLBACK;
T7
T8
T9
  • 无索引情况下,即使不命中数据,也会加表锁。

无索引范围查询

时间 事务1 事务2
T1 BEGIN;
T2 SELECT * FROM user WHERE money BETWEEN 5 AND 12 FOR UPDATE;
T3 BEGIN;
T4 阻塞: SELECT * FROM user WHERE money=1 FOR UPDATE; SELECT * FROM user WHERE money=3 FOR UPDATE; SELECT * FROM user WHERE money=4 FOR UPDATE; SELECT * FROM user WHERE money=8 FOR UPDATE; SELECT * FROM user WHERE money=16 FOR UPDATE; SELECT * FROM user WHERE money=22 FOR UPDATE; SELECT * FROM user WHERE money=23 FOR UPDATE; INSERT INTO user VALUES(100,100,100);
T5 COMMIT;
T6 ROLLBACK;
T7
T8
T9
  • 无索引情况下,范围查询,也会加表锁。

总结

  • 无索引情况下,一致性锁定读,即加锁读,直接加表锁
  • 唯一索引:命中,记录互斥锁;未命中,会加开区间,间隙锁;范围查询,开区间,临键锁
  • 非唯一索引:命中,主键索引加记录互斥锁,非唯一索引使用开区间,临键锁;未命中,会加开区间,间隙锁;范围查询,前开后闭,临键锁。
相关推荐
烙印6011 分钟前
MyBatis原理剖析(二)
java·数据库·mybatis
RestCloud2 分钟前
如何通过ETLCloud实现跨系统数据同步?
数据库·数据仓库·mysql·etl·数据处理·数据同步·集成平台
你是狒狒吗4 分钟前
TM中,return new TransactionManagerImpl(raf, fc);为什么返回是new了一个新的实例
java·开发语言·数据库
千|寻15 分钟前
【画江湖】langchain4j - Java1.8下spring boot集成ollama调用本地大模型之问道系列(第一问)
java·spring boot·后端·langchain
程序员岳焱29 分钟前
Java 与 MySQL 性能优化:MySQL 慢 SQL 诊断与分析方法详解
后端·sql·mysql
龚思凯35 分钟前
Node.js 模块导入语法变革全解析
后端·node.js
天行健的回响37 分钟前
枚举在实际开发中的使用小Tips
后端
wuhunyu43 分钟前
基于 langchain4j 的简易 RAG
后端
techzhi43 分钟前
SeaweedFS S3 Spring Boot Starter
java·spring boot·后端
Channing Lewis1 小时前
sql server如何创建表导入excel的数据
数据库·oracle·excel