MySQL是如何加行级锁的
SQL 代码 : 仓库链接
MySQL 的锁
MySQL 的锁按照加锁范围可以分为 全局锁 表级锁 和 行级锁。
行级锁是 innodb 存储引擎才支持的;
一般加行级锁前也会给表加上 意向锁,意向共享锁或者意向独占锁;
通过 select * from performance_schema.data_locks
可以查看当前数据库加了哪些锁, 比如:
行级锁的类型主要有三类
加锁的对象是索引 基本单位是 临键锁。
Record Lock,记录锁,也就是把一条记录锁上;
Gap Lock,间隙锁,锁定一个范围,但是不包含记录本身;
Next-Key Lock: Record Lock+ Gap Lock的组合,锁定一个范围,并且锁定记录本身。
实战
总结
建表,插入数据
其中 id 为唯一索引, age 为非唯一索引。
CREATE TABLE `user` (`id` bigint NOT NULL AUTO_INCREMENT,`name` varchar(30) COLLATE utf8mb4_unicode_ci NOT NULL,`age` int NOT NULL,PRIMARY KEY (`id`),KEY `index_age` (`age`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;INSERT INTO `user` VALUES (1, 'A', 10);
INSERT INTO `user` VALUES (2, 'B', 14);
INSERT INTO `user` VALUES (3, 'C', 17);
INSERT INTO `user` VALUES (4, 'D', 29);
INSERT INTO `user` VALUES (5, 'E', 35);
INSERT INTO `user` VALUES (15, 'F', 40);## 查看隔离级别 为 可重复读
SELECT @@global.transaction_isolation;SELECT @@transaction_isolation;
# REPEATABLE-READ
唯一索引等值查询
主键索引等值查询 (where id = xxx)
记录存在就是记录锁,不存在就是间隙锁,就可以 避免幻读,具体的:
如果查询记录存在,那么就不会加临键锁 而是会变为记录锁。
如果记录不存在 那么就在找到 第一条大于该查询记录的记录 后 ,将记录中的 临键锁 变为 间隙锁。因为记录本身不存在 ,也没有办法上记录锁。
锁住之后,其他事务想要 更改或者增加一条查询记录,都无法成功,只有等到 当前事务结束释放锁后才能成功。
记录存在
# 锁定读 记录存在 主键索引加记录锁 表加意向独占锁 防止幻读:
begin;
SELECT * FROM user WHERE id = 1 FOR UPDATE;commit ;
# 查询 当前的加锁情况 结果如下:
select * from performance_schema.data_locks;
记录不存在 但没有超过主键最大值
# 锁定读 记录不存在 主键索引加间隙锁 表加意向独占锁 防止幻读:
begin;
SELECT * FROM user WHERE id = 10 FOR UPDATE;commit ;
# 查询 当前的加锁情况
select * from performance_schema.data_locks;
记录不存在 但超过主键最大值
# 锁定读 记录不存在且超过了主键最大值 主键索引加临键锁 表加意向独占锁 :
begin;
SELECT * FROM user WHERE id = 100 FOR UPDATE;commit ;
# 查询 当前的加锁情况
select * from performance_schema.data_locks;
在MySQL InnoDB存储引擎中,supremum pseudo-record
是一个特殊的存在,通常被描述为 “正无穷大” ,用于表述索引记录中最大值之后的间隙。在 next-key lock
的机制中,supremum pseudo-record
用于锁定索引中最大值之后的范围。它并不是一个真实的索引记录,而是代表索引中可能存在的最大值,设置在 supremum pseudo-record
上的 next-key lock
锁定了“此索引中可能存在的最大值”以及这个值前面的间隙
非唯一索引查询
二级索引 等值查询(where age = xxx)
记录存在
# 锁定读 记录存在 age 加临键锁 以及 间隙锁 主键索引加记录锁 表加意向独占锁:
begin;
SELECT * FROM user WHERE age = 14 FOR UPDATE;commit ;select * from performance_schema.data_locks;
记录不存在 但没有超过 age 最大值
# 锁定读 记录不存在 非唯一索引 age索引 加间隙锁 表加意向独占锁:
begin;
SELECT * FROM user WHERE age = 25 FOR UPDATE;commit ;select * from performance_schema.data_locks;
记录不存在 但超过 age 最大值
# 锁定读 记录不存在 且大于age 最大值 非唯一索引 age索引 加临键锁 表加意向独占锁:
begin;
SELECT * FROM user WHERE age = 99 FOR UPDATE;commit ;select * from performance_schema.data_locks;