MySQL行锁范围分析(行锁、间隙锁、临键锁)

2023-12-11 18:04

本文主要是介绍MySQL行锁范围分析(行锁、间隙锁、临键锁),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

MySQL 中锁的概念

排它锁(Exclusive Lock)

X 锁,也称为写锁,若事务T对对象A加上X锁,则只允许T读取和修改A,其他任何事物都不能再对A 加任何锁,直到T释放A上的锁。
SELECT…FOR UPDATE 对读取的行记录加一个X锁,其他事务不能对已锁定的行加上任何锁。

共享锁(Shared Lock)

**S 锁,**也称为读锁,若事务T对数据对象A加上S锁,则事务T可以读A,但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。
SELECT…LOCK IN SHARE MODE对读取的行记录加一个S锁,其他事务可以向被锁定 的行加S锁,但是如果加X锁,则会被阻塞。

活锁

事务T1封锁了R,T2又请求封锁R,于是T2等待,T3也请求封锁R,当T1释放了R 上的锁,系统首先批准了T3的请求,T2继续等待,这就是活锁。

死锁

事务T1封锁了R1,T2封锁了R2,T1又请求封锁R2,因为T2已经封锁了R2,于是T1等待T2释放R2上的锁,接着T2又申请封锁R1,因为T1已经封锁了R1,T2只能等待T1释放R1上的锁,这就是死锁。
解决死锁的方法
一次封锁法 每个事务必须将所有要使用的数据全部加锁,否则就不能执行,弊端 加大封锁范围,降低了并发速度。

乐观锁

总是假设最好的情况,在事务提交前不会对数据进行锁定,而是在更新数据时会进行版本或时间戳的比较,以确定数据是否被其他事务修改过。如果数据没有被修改,则允许提交;如果数据被修改,则需要进行冲突解决

悲观锁

总是假设最坏的情况,在整个事务过程中,假设其他事务会对数据进行修改,因此在读取或修改数据时,会先对数据进行锁定,以防止其他事务对数据进行干扰(排它锁、共享锁都是悲观锁,共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程

MySQL 行锁加锁的分析

版本使用 MySQL 8.2.0
MySQL InnoDB 中支持三种行锁的方式:行锁(Record Lock)、间隙锁(Gap Lock)、临键锁(Next-Key Lock),默认加的是临键锁,但是会根据不同的查询条件进行优化。创建一个 user 表用来测试,表中 id 是主键索引,name 是唯一索引,salary 是普通索引,gender 没有索引。

idnamesalarygender
10惠月48000
20光济50000
30杰霖55000
40紫妤60000
50娜溱70000

创建表并插入数据

CREATE TABLE `user`  (`id` int(11) NOT NULL,`name` varchar(255) DEFAULT NULL,`salary` int DEFAULT NULL,`gender` varchar(255) DEFAULT NULL,PRIMARY KEY (`id`) USING BTREE,INDEX `salary`(`salary`) USING BTREE,UNIQUE INDEX `name`(`name` ASC) USING BTREE
);INSERT INTO `user` VALUES (10, '惠月', 48000, '女');
INSERT INTO `user` VALUES (20, '光济', 50000, '男');
INSERT INTO `user` VALUES (30, '杰霖', 55000, '男');
INSERT INTO `user` VALUES (40, '紫妤', 62000, '女');
INSERT INTO `user` VALUES (50, '娜溱', 75000, '女');

加锁情况

-- 普通的select查询是快照读,不加锁
SELECT * FROM user WHERE id=30;
-- 查询时给主键索引加S共享锁时,是当前读
SELECT * FROM user WHERE id=30 LOCK IN SHARE MODE;
-- 查询时加 X排他锁,为当前读
SELECT * FROM user WHERE id=30 FOR UPDATE

执行 SELECT * FROM … FOR UPDATE 会对表加上 IX 写意向锁,表示有可能会对这些记录进行写操作,并且给记录加一个X,REC_NOT_GAP,锁定了该条数据。 执行SELECT * FROM … FOR SHARE 会对表加上一个 IS 读意向锁,并且会给记录加一个S,REC_NOT_GAP。

主键索引

主索引等值查询,数据存在的情况

事务 1

BEGIN;
SELECT * FROM user WHERE id=30 FOR UPDATE;
-- SELECT * FROM user WHERE id=40 FOR SHARE;
-- ROLLBACK

查看锁的情况

-- mysql 8
SELECT * FROM performance_schema.data_locks;-- mysql 5.7
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;

image.png
1、表锁是意向写锁
2、数据加行锁
各字段意思:
INDEX_NAME 锁定索引的名称 PRIMARY 说明是主键索引
LOCK_TYPE 锁的类型,RECORD 行锁 、 TABLE 表锁
LOCK_MODE 锁的模式,IS 读意向锁、IX 写意向锁、S 读锁,又称共享锁、X 写锁,又称排它锁、GAP 间隙锁。
**LOCK_DATA **要锁定的数据,当 LOCK_TYPE 是 RECORD行锁时。当锁在主键索引上时,显示主键索的值。当锁是在辅助索引上时,显示主索引和辅助索引的值。

LOCK_MODELOCK_DATA锁范围
X,REC_NOT_GAP40id=40 行锁
X,GAP40id=40 间隙锁,不包含 40(前开后开)
X40id=40 临键锁,包含 40(前开后闭)

事务 2
1、会对主索引 id=30 添加行锁

BEGIN;
-- 更新会失败,因为事务 1 对 id=30 加行锁。
UPDATE user SET salary = 56000 WHERE id = 30;

主索引等值查询,数据不存在的情况

事务 1

BEGIN;
SELECT * FROM user WHERE id=31 FOR UPDATE; -- 数据库中没有id=30的记录

查看加锁情况
image.png
1、表加的是意向写锁
2、id 加的是 GAP Lock,范围是(30, 40)
注意
LOCK_MODE 是 X,GAP 表示间隙锁,LOCK_DATA 是 40 表示锁定的范围是在 id 为 40 之前的间隙
事务 2
1、会锁住主索引 id=31 所在的间隙

BEGIN;
-- 可以执行成功
UPDATE `bostore`.`user` SET `salary` = 56000.00 WHERE `id` = 30;
UPDATE `bostore`.`user` SET `salary` = 63000.00 WHERE `id` = 40;
-- 执行失败
INSERT INTO `bostore`.`user` VALUES (33, '六零', 68000.00, '女');

主索引范围查询,前闭后开情况

事务 1

BEGIN;
SELECT * FROM user WHERE id>=30 AND id<33 FOR UPDATE;

查看锁情况
image.png
1、 表示 IX 意向写锁
2、id=30 是行锁
3、id=40 加的是 GAP Lock,范围是(30, 40)
事务 2

BEGIN;
-- 会阻塞
UPDATE user SET salary = 57000.00 WHERE id = 30;
INSERT INTO user VALUES (35, '六零', 68000.00, '女');
INSERT INTO user VALUES (33, '合吧', 64000.00, '女');
-- 不会阻塞
UPDATE user SET salary = 63000.00 WHERE id = 40;

主索引范围查询,前开后闭情况

事务 1

BEGIN;
SELECT * FROM user WHERE id>30 AND id<=40 FOR UPDATE;

查看锁情况
image.png
1、表加的是意向写锁
2、id=40 加 NEXT-Key Lock,范围是(30, 40]
事务 2

BEGIN;
-- 会阻塞
UPDATE user SET salary = 63000 WHERE id = 40;
INSERT INTO user VALUES (35, '六零', 68000, '女');
INSERT INTO user VALUES (33, '合吧', 64000, '女');-- 不会阻塞
UPDATE user SET salary = 57000 WHERE id = 30;
UPDATE user SET salary = 78000 WHERE id = 50;
INSERT INTO user VALUES (53, '考拉', 84000, '女');

普通索引

普通索引(普通索引只针对表中的单一列进行索引,普通索引可以是唯一的,也可以不唯一,普通索引对于等值查询(例如WHERE column = value)和范围查询(例如WHERE column > value)都能提供较好的性能提升)

普通索引等值查询,数据存在的情况

事务 1

BEGIN;
SELECT * FROM user WHERE salary = 62000 FOR UPDATE;

查看锁情况
image.png
1、 表加意向写锁 IX
2、 索引salary加临键锁,范围是(55000, 62000]
3、 主键 id = 40 加行锁
4、 索引salary加间隙锁,范围是(62000, 75000)
事务 2
1、 主键 id=40 加了行锁,不能更新和删除

-- 修改id=40有行锁会阻塞
UPDATE user SET salary = 63000 WHERE id = 40;
UPDATE user SET name="紫是" where salary = 62000;

2、salary 在(55000, 62000] 范围加了 NEXT-Key Lock(针对该范围的 id 也会加锁),insert 时salary 在此范围,会阻塞
image.png
3、salary 在(62000, 75000)范围加了 GAP Lock(避免幻读),insert 时salary 在此范围,会阻塞
image.png
4、 插入 salary=55000时,id<30可以不阻塞,id>30会阻塞

-- id=40  salary=62000 之前有临键锁(55000, 62000]
-- 是对salary的锁,但是整个区间都会被锁住包括主索引id
INSERT INTO user VALUES (35, '六零', 55000, '女'); -- 阻塞
INSERT INTO user VALUES (51, '哈西', 55000, '女'); -- 阻塞
-- id=30  salary=55000 之前没有间隙锁
INSERT INTO user VALUES (29, '湖西', 55000, '女'); -- 不阻塞

5、 插入 salary=62000时,会阻塞

INSERT INTO user VALUES (39, '哈子', 62000, '女');
INSERT INTO user VALUES (44, '靠是', 62000, '女');
INSERT INTO user VALUES (55, '西欧', 62000, '女');
-- 自增id时也是会阻塞
INSERT INTO `bostore`.`user`(`name`, `salary`, `gender`) VALUES ('学律', 62000, '女');

6、插入 salary=75000时,id<50 会阻塞,id>50 不阻塞

-- LOCK_MODE为X,GAP 
-- LOCAK_DATA为75000, 50 表示 要插入salary为75000,id<50时会加锁,即会阻塞
INSERT INTO user VALUES (29, '合吧', 75000, '女');
-- id > 50 不会阻塞
INSERT INTO user VALUES (51, '欧下', 75000, '女');

7、当salary 不在(55000, 62000] 和(62000, 75000)范围时,id 不会加锁

INSERT INTO user VALUES (31, '湖西', 54000, '女');
INSERT INTO user VALUES (49, '离下', 54000, '女');
INSERT INTO user VALUES (45, '的大', 76000, '女');
UPDATE user SET salary = 78000 WHERE id = 50;-- id < 30 和 salary < 55000 不阻塞
INSERT INTO user VALUES (29, '六零', 50000, '女');
-- id > 50 和 salary > 75000 不阻塞
INSERT INTO user VALUES (63, '合吧', 94000, '女');

普通索引等值查询,数据不存在的情况

事务 1

BEGIN;
SELECT * FROM user WHERE salary = 60000 FOR UPDATE;

查看锁情况
image.png
1、表加意向写锁
2、salary 加间隙锁,范围是(55000, 62000)
事务 2
1、salary 在(55000, 62000)范围加了 GAP Lock,insert 时salary 在此范围,会阻塞
image.png
2、插入 salary=55000时,id<30可以不阻塞,id>30会阻塞

INSERT INTO user VALUES (35, '六零', 55000, '女'); -- 阻塞
INSERT INTO user VALUES (51, '哈西', 55000, '女'); -- 阻塞
-- id=30  salary=55000 之前没有间隙锁
INSERT INTO user VALUES (29, '湖西', 55000, '女'); -- 不阻塞

3、插入 salary=62000时,id<40 会阻塞,id>40 不阻塞

-- id < 40 会阻塞
INSERT INTO user VALUES (39, '合吧', 62000, '女');
-- id > 40 不会阻塞
INSERT INTO user VALUES (41, '欧下', 62000, '女');

4、当 salary 不在(55000, 62000),id 不会加锁

INSERT INTO user VALUES (31, '湖西', 54000, '女');
INSERT INTO user VALUES (39, '离下', 63000, '女');
UPDATE user SET salary = 63000 WHERE id = 40;-- id < 30 和 salary < 55000 不阻塞
INSERT INTO user VALUES (29, '六零', 50000, '女');
-- id > 40 和 salary > 62000 不阻塞
INSERT INTO user VALUES (41, '合吧', 65000, '女');

普通索引范围查询,前闭后开的情况

事务 1

BEGIN;
SELECT * FROM user WHERE salary>=55000 AND salary<62000 FOR UPDATE;

查看锁情况
image.png
1、表加意向写锁
2、salary 加 NEXT-Key Lock 范围是(50000, 55000]
3、salary 加 NEXT-Key Lock 范围是(55000, 62000]
4、id=30 加行锁
事务 2
1、 插入 salary=50000时,id<20可以不阻塞,id>20会阻塞

INSERT INTO user VALUES (19, '六零', 50000, '女'); -- 不阻塞
INSERT INTO user VALUES (21, '哈西', 50000, '女'); -- 阻塞
INSERT INTO user VALUES (41, '湖西', 50000, '女'); -- 阻塞

2、 插入 salary=55000时,会阻塞

INSERT INTO user VALUES (29, '哈子', 55000, '女');
INSERT INTO user VALUES (35, '靠是', 55000, '女');
INSERT INTO user VALUES (44, '西欧', 55000, '女');
-- 自增id时也是会阻塞
INSERT INTO `bostore`.`user`(`name`, `salary`, `gender`) VALUES ('学律', 55000, '女');

3、 插入 salary=62000时,id<40 会阻塞,id>40 不阻塞

-- 阻塞
INSERT INTO user VALUES (39, '合吧', 62000, '女');
-- 不阻塞
INSERT INTO user VALUES (41, '欧下', 62000, '女');

4、当salary 不在(50000, 55000] 和(55000, 62000]范围时,id 不会加锁

INSERT INTO user VALUES (31, '湖西', 44000, '女');
INSERT INTO user VALUES (49, '离下', 44000, '女');
INSERT INTO user VALUES (45, '的大', 66000, '女');-- id < 20 和 salary < 50000 不阻塞
INSERT INTO user VALUES (19, '六零', 40000, '女');
-- id > 40 和 salary > 62000 不阻塞
INSERT INTO user VALUES (53, '合吧', 94000, '女');

5、id=30 加行锁,不能删除和更新

UPDATE user SET salary = 56000 WHERE id = 30;
UPDATE user SET name="紫下" where salary = 55000;

普通索引范围查询,前开后闭的情况

事务 1

BEGIN;
SELECT * FROM user WHERE salary>55000 AND salary<=62000 FOR UPDATE;

查看锁情况
image.png
1、表加意向写锁
2、salary 加 NEXT-Key Lock 范围是(55000, 62000]
3、salary 加 NEXT-Key Lock 范围是(62000, 75000]
4、id=40 加行锁
事务 2 的加锁情况和上面类似

没有索引的情况

BEGIN;
SELECT * FROM user WHERE gender='男' FOR UPDATE;

查看加锁情况
image.png
InnoDB 的锁是加上在索引上的,没有索引的时候,就会给所有的记录都加上锁 NEXT-Key Lock,相当于表锁。

这篇关于MySQL行锁范围分析(行锁、间隙锁、临键锁)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/481589

相关文章

canal实现mysql数据同步的详细过程

《canal实现mysql数据同步的详细过程》:本文主要介绍canal实现mysql数据同步的详细过程,本文通过实例图文相结合给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的... 目录1、canal下载2、mysql同步用户创建和授权3、canal admin安装和启动4、canal

SQL中JOIN操作的条件使用总结与实践

《SQL中JOIN操作的条件使用总结与实践》在SQL查询中,JOIN操作是多表关联的核心工具,本文将从原理,场景和最佳实践三个方面总结JOIN条件的使用规则,希望可以帮助开发者精准控制查询逻辑... 目录一、ON与WHERE的本质区别二、场景化条件使用规则三、最佳实践建议1.优先使用ON条件2.WHERE用

MySQL存储过程之循环遍历查询的结果集详解

《MySQL存储过程之循环遍历查询的结果集详解》:本文主要介绍MySQL存储过程之循环遍历查询的结果集,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录前言1. 表结构2. 存储过程3. 关于存储过程的SQL补充总结前言近来碰到这样一个问题:在生产上导入的数据发现

MySQL 衍生表(Derived Tables)的使用

《MySQL衍生表(DerivedTables)的使用》本文主要介绍了MySQL衍生表(DerivedTables)的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学... 目录一、衍生表简介1.1 衍生表基本用法1.2 自定义列名1.3 衍生表的局限在SQL的查询语句select

MySQL 横向衍生表(Lateral Derived Tables)的实现

《MySQL横向衍生表(LateralDerivedTables)的实现》横向衍生表适用于在需要通过子查询获取中间结果集的场景,相对于普通衍生表,横向衍生表可以引用在其之前出现过的表名,本文就来... 目录一、横向衍生表用法示例1.1 用法示例1.2 使用建议前面我们介绍过mysql中的衍生表(From子句

MyBatis Plus 中 update_time 字段自动填充失效的原因分析及解决方案(最新整理)

《MyBatisPlus中update_time字段自动填充失效的原因分析及解决方案(最新整理)》在使用MyBatisPlus时,通常我们会在数据库表中设置create_time和update... 目录前言一、问题现象二、原因分析三、总结:常见原因与解决方法对照表四、推荐写法前言在使用 MyBATis

六个案例搞懂mysql间隙锁

《六个案例搞懂mysql间隙锁》MySQL中的间隙是指索引中两个索引键之间的空间,间隙锁用于防止范围查询期间的幻读,本文主要介绍了六个案例搞懂mysql间隙锁,具有一定的参考价值,感兴趣的可以了解一下... 目录概念解释间隙锁详解间隙锁触发条件间隙锁加锁规则案例演示案例一:唯一索引等值锁定存在的数据案例二:

Python主动抛出异常的各种用法和场景分析

《Python主动抛出异常的各种用法和场景分析》在Python中,我们不仅可以捕获和处理异常,还可以主动抛出异常,也就是以类的方式自定义错误的类型和提示信息,这在编程中非常有用,下面我将详细解释主动抛... 目录一、为什么要主动抛出异常?二、基本语法:raise关键字基本示例三、raise的多种用法1. 抛

MySQL JSON 查询中的对象与数组技巧及查询示例

《MySQLJSON查询中的对象与数组技巧及查询示例》MySQL中JSON对象和JSON数组查询的详细介绍及带有WHERE条件的查询示例,本文给大家介绍的非常详细,mysqljson查询示例相关知... 目录jsON 对象查询1. JSON_CONTAINS2. JSON_EXTRACT3. JSON_TA

MySQL 设置AUTO_INCREMENT 无效的问题解决

《MySQL设置AUTO_INCREMENT无效的问题解决》本文主要介绍了MySQL设置AUTO_INCREMENT无效的问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参... 目录快速设置mysql的auto_increment参数一、修改 AUTO_INCREMENT 的值。