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

相关文章

Linux下MySQL数据库定时备份脚本与Crontab配置教学

《Linux下MySQL数据库定时备份脚本与Crontab配置教学》在生产环境中,数据库是核心资产之一,定期备份数据库可以有效防止意外数据丢失,本文将分享一份MySQL定时备份脚本,并讲解如何通过cr... 目录备份脚本详解脚本功能说明授权与可执行权限使用 Crontab 定时执行编辑 Crontab添加定

MySQL中On duplicate key update的实现示例

《MySQL中Onduplicatekeyupdate的实现示例》ONDUPLICATEKEYUPDATE是一种MySQL的语法,它在插入新数据时,如果遇到唯一键冲突,则会执行更新操作,而不是抛... 目录1/ ON DUPLICATE KEY UPDATE的简介2/ ON DUPLICATE KEY UP

MySQL分库分表的实践示例

《MySQL分库分表的实践示例》MySQL分库分表适用于数据量大或并发压力高的场景,核心技术包括水平/垂直分片和分库,需应对分布式事务、跨库查询等挑战,通过中间件和解决方案实现,最佳实践为合理策略、备... 目录一、分库分表的触发条件1.1 数据量阈值1.2 并发压力二、分库分表的核心技术模块2.1 水平分

Python与MySQL实现数据库实时同步的详细步骤

《Python与MySQL实现数据库实时同步的详细步骤》在日常开发中,数据同步是一项常见的需求,本篇文章将使用Python和MySQL来实现数据库实时同步,我们将围绕数据变更捕获、数据处理和数据写入这... 目录前言摘要概述:数据同步方案1. 基本思路2. mysql Binlog 简介实现步骤与代码示例1

使用shardingsphere实现mysql数据库分片方式

《使用shardingsphere实现mysql数据库分片方式》本文介绍如何使用ShardingSphere-JDBC在SpringBoot中实现MySQL水平分库,涵盖分片策略、路由算法及零侵入配置... 目录一、ShardingSphere 简介1.1 对比1.2 核心概念1.3 Sharding-Sp

MySQL 表空却 ibd 文件过大的问题及解决方法

《MySQL表空却ibd文件过大的问题及解决方法》本文给大家介绍MySQL表空却ibd文件过大的问题及解决方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考... 目录一、问题背景:表空却 “吃满” 磁盘的怪事二、问题复现:一步步编程还原异常场景1. 准备测试源表与数据

Mac电脑如何通过 IntelliJ IDEA 远程连接 MySQL

《Mac电脑如何通过IntelliJIDEA远程连接MySQL》本文详解Mac通过IntelliJIDEA远程连接MySQL的步骤,本文通过图文并茂的形式给大家介绍的非常详细,感兴趣的朋友跟... 目录MAC电脑通过 IntelliJ IDEA 远程连接 mysql 的详细教程一、前缀条件确认二、打开 ID

MySQL的配置文件详解及实例代码

《MySQL的配置文件详解及实例代码》MySQL的配置文件是服务器运行的重要组成部分,用于设置服务器操作的各种参数,下面:本文主要介绍MySQL配置文件的相关资料,文中通过代码介绍的非常详细,需要... 目录前言一、配置文件结构1.[mysqld]2.[client]3.[mysql]4.[mysqldum

MySQL中查询和展示LONGBLOB类型数据的技巧总结

《MySQL中查询和展示LONGBLOB类型数据的技巧总结》在MySQL中LONGBLOB是一种二进制大对象(BLOB)数据类型,用于存储大量的二进制数据,:本文主要介绍MySQL中查询和展示LO... 目录前言1. 查询 LONGBLOB 数据的大小2. 查询并展示 LONGBLOB 数据2.1 转换为十

Go语言连接MySQL数据库执行基本的增删改查

《Go语言连接MySQL数据库执行基本的增删改查》在后端开发中,MySQL是最常用的关系型数据库之一,本文主要为大家详细介绍了如何使用Go连接MySQL数据库并执行基本的增删改查吧... 目录Go语言连接mysql数据库准备工作安装 MySQL 驱动代码实现运行结果注意事项Go语言执行基本的增删改查准备工作