select for update加了行锁还是表锁?

news2025/1/18 2:15:23

最近在开发需求的时候,用到了select......for update。在代码评审的时候,一位同事说 ,唯一索引+一个非索引字段,是否可能会锁全表呢?本文将通过9个实验操作的例子,给大家验证select......for update到底加了什么锁,是表锁还是行锁

以下是本文的提纲哈:

 

因为加锁是跟数据库的隔离级别息息相关的。而常用的数据库隔离级别也就RC(读已提交)和RR(可重复读),所以本文分别根据RC(读已提交) 和 RR(可重复读)隔离级别展开讲述。

1. 环境准备

设置数据库隔隔离级别

mysql> set global TRANSACTION ISOLATION level read COMMITTED;
Query OK, 0 rows affected (0.00 sec)

mysql> select @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| READ-COMMITTED          |
+-------------------------+
1 row in set (0.00 sec)
复制代码

自动提交关闭

mysql> set @@autocommit=0;  //设置自动提交关闭
Query OK, 0 rows affected (0.00 sec)

mysql> select @@autocommit;
+--------------+
| @@autocommit |
+--------------+
|            0 |
+--------------+
1 row in set (0.00 sec)

复制代码

建表语句

CREATE TABLE `user_info_tab` (
  `id` int NOT NULL AUTO_INCREMENT,
  `user_name` varchar(255) DEFAULT NULL,
  `age` int DEFAULT NULL,
  `city` varchar(255) DEFAULT NULL,
  `status` varchar(4) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_user_name` (`user_name`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1570072 DEFAULT CHARSET=utf8mb3;
复制代码

初始化数据(接下来的实验证明,都是基于这几条初始数据)

insert into user_info_tab(`user_name`,`age`,`city`,`status`) values('杰伦',18,'深圳','1');
insert into user_info_tab(`user_name`,`age`,`city`,`status`) values('奕迅',26,'湛江','0');
insert into user_info_tab(`user_name`,`age`,`city`,`status`) values('俊杰',28,'广州','1');
复制代码

MYSQL 版本

mysql> select @@version;  
+-----------+
| @@version |
+-----------+
| 8.0.31    |
+-----------+
1 row in set (0.00 sec)
复制代码

2.RC 隔离级别

2.1 RC隔离级别 + 唯一索引

先把隔离级别设置为RC,因为user_name为唯一索引,我们使用user_name为条件去执行select......for update语句,然后开启另外一个事务去更新数据同一条数据,发现被阻塞了。如下图:

事务二的更新语句为什么会阻塞呢?

因为事务一的select......for update已经加了锁了嘛。那加的是行锁还是表锁呢? 如果加的是表锁的话,我们更新其他行的记录的话,应该是也会阻塞的,如果是行锁的话,更新其他记录是可以顺利执行的。

大家可以再看下这个图:

通过实验,可以发现:如果事务中是更新其他行记录的话,是可以顺利执行的。因此在RC隔离级别下,如果条件是唯一索引,那么select......for update加的应该是行锁

有些小伙伴会很好奇,到底加了什么锁呢? 接下来带大家看看,具体加的是什么锁。

我用的MySQL版本是8.0+,用这个语句查看:

SELECT * FROM performance_schema.data_locks\G;
复制代码

如下图,select * from user_info_tab where user_name ='杰伦' for update语句一共加了三把锁,分别是 IX意向排他锁(表级别的锁,不影响插入)、两把X排他锁(行锁,分别对应唯一索引,主键索引)

有些读者朋友说,这里不是加了IX表锁嘛?为什么不阻塞其他行的更新? 其实这个是意向排他锁

意向排他锁:简称IX锁,当事务准备在某条记录上加上X锁时,需要在表级别加一个IX锁。如select ... for update,要给表设置IX锁;

那既然有表锁,为啥事务二在执行其他行的更新语句时,并不会阻塞,这是因为:意向锁仅仅表明意向的锁,意向锁之间不会互斥,是可以并行的。,锁的兼容性如下:

有些小伙伴可能还有疑问,为啥会有两把的X锁呢? 不是都锁住的是同一行嘛?其实RC隔离级别下,唯一索引的加锁是这样的:

为什么不是唯一索引上加X锁就可以了呢?为什么主键索引上的记录也要加锁呢?

如果并发的一个SQL,通过唯一索引条件,来更新主键索引update user_info_tab set user_name = '学友' where id = '1570068';此时,如果select...for update语句没有将主键索引上的记录加锁,那么并发的update就会感知不到select...for update语句的存在,违背了同一记录上的更新/删除需要串行执行的约束。

2.2 RC 隔离级别 + 主键

RC 隔离级别下,如果select......for update的查询条件是主键id,加的又是什么锁呢?

我们执行语句:select * from user_info_tab where id ='1570070' for update;然后开启另外一个事务去更新数据同一条数据,发现被阻塞了。如下图:

如果事务二更新的是其他行的记录,则是可以顺利执行的,如下图:

通过实验,可以发现:

如果事务中是更新其他行记录的话,是可以顺利执行的。因此在RC隔离级别下,如果条件是主键,那么select......for update锁的也是行

根据2.1小节的结论,select......for update都会加个表级别的IX意向排他锁。所以,查询条件是id的话,select......for update会加两把锁,分表是IX意向排他锁(表锁,不影响插入)一把X排他锁(行锁,对于主键索引)

我们执行语句,查询一下到底加的是什么锁。

begin;
select * from user_info_tab where id ='1570070' for update;
SELECT * FROM performance_schema.data_locks\G;
复制代码

因此在RC隔离级别下,如果条件是主键,那么select......for update加的就是两把锁,一把IX意向排他锁(不影响插入),一把对应主键的X排他锁(行锁,会锁住那一行)。

2.3 RC 隔离级别 + 普通索引

RC 隔离级别下,如果select......for update的查询条件是普通索引,加的又是什么锁呢?

我们这里先给原来表加上普通索引:

alter table user_info_tab add index idx_city (city);
复制代码

我们执行语句:select * from user_info_tab where city ='广州' for update;然后开启另外一个事务去更新同一条数据,发现被阻塞了。如下图:

如果事务二更新的是其他行的记录,还是可以顺利执行的,如下图:

我们看一下select * from user_info_tab where city ='广州' for update;到底加了什么锁,如下图:

发现一共加了三把锁,分别是:IX意向排他锁(表锁)两把X排他锁(行锁,分别对应普通索引的X锁,对应主键的X锁)

如果查询条件,没有命中数据库表的记录又加什么锁呢?

我们把查询条件改一下:select * from user_info_tab where city ='广州' and status='0' for update;

发现只加了一把锁,即IX意向排他锁(表锁,不影响插入)

2.4 RC 隔离级别 + 无索引

RC 隔离级别下,如果select......for update的查询条件是无索引呢,加的又是什么锁呢?

多数读者凭感觉都是锁表了,我们来验证一下。

我们执行语句:select * from user_info_tab where age ='26' for update;(age是没有加索引的),然后开启另外一个事务去更新数据。如下图:

由上图可以发现,事务一 先执行select......for update,然后事务二先更新别的行,发现可以顺利执行,如果执行for update的同一行,还是会阻塞等待

可推出结论,select......for update的查询条件是无索引,主要还是行锁。我们看下具体的加锁情况:

SELECT * FROM performance_schema.data_locks\G;
复制代码

发现一共加了两把锁,分别是:IX意向排他锁(表锁)一把X排他锁(行锁,对应主键的X锁)

这个为啥呢?为什么不是一个锁表的X锁呢? 这是因为:

age列上没有索引,MySQL会走聚簇(主键)索引进行全表扫描过滤。每条记录都会加上X锁。但是,为了效率考虑,MySQL在这方面进行了改进,在扫描过程中,若记录不满足过滤条件,会进行解锁操作。同时优化违背了2PL原则。

3.RR 隔离级别

3.1 RR隔离级别 + 唯一索引

如果是RR(可重复)的数据库隔离级别呢,select......for update的查询条件是唯一索引的话,加的又是什么锁呢?

我们知道RR隔离级别RC隔离级别,主要差异还是有间隙锁这个概念。接下来我们还是通过实验去验证,先把数据库隔离级别设置为RR

mysql> set global transaction isolation level repeatable read; (设置完好像要重启一下)

Query OK, 0 rows affected (0.00 sec)

我们执行语句:select * from user_info_tab where user_name ='杰伦' for update;user_name是唯一索引的),然后开启另外一个事务去更新数据。如下图:

由上图可以发现,即使是RR数据库隔离级别,事务一先执行select......for update,然后事务一先更新别的行,发现可以顺利执行,如果执行更新for update的那一行,还是会阻塞超时

再去看下具体加了什么锁:

大家可以发现,不管是RC隔离级别还是RR隔离级别select......for update,查询条件是唯一索引,命中数据库表记录时,一共会加三把锁:一把IX意向排他锁 (表锁,不影响插入),一把对应主键的X排他锁(行锁),一把对应唯一索引的X排他锁 (行锁)

3.2 RR 隔离级别 + 主键

如果是 RR(可重复读)的数据库隔离级别,select......for update的查询条件是主键的话,加的又是什么锁呢?

根据前面的实验结果,我们其实可以推测得出来了,应该跟RC隔离级别一样,会加两把锁:一把IX意向排他锁(表锁,不影响插入),一把对应主键的X排他锁(行锁,影响对应主键那一行的插入)。

我们通过语句确认一下,先输入一下语句:

begin;
select * from user_info_tab where id ='1570070' for update;
SELECT * FROM performance_schema.data_locks\G;
复制代码

大家可以看下,跟我们的推测是一样的:

3.3 RR 隔离级别 + 普通索引

在RR隔离级别下,如果select......for update的查询条件是普通索引的话,除了会加X锁,IX锁,还会加Gap 锁

Gap锁的提出,是为了解决幻读问题引入的,它是一种加在两个索引之间的锁。

我们来验证一下,先开始事务一,执行一下操作:

begin;
select * from user_info_tab where city ='广州' for update;
复制代码

接着开启事务

begin;
insert into user_info_tab(`user_name`,`age`,`city`,`status`) values('方燕',27,'汕头','1');
复制代码

验证流程图如下:

大家可以发现,插入新记录,会被阻塞,那是因为有间隙锁的缘故,我们再看下到底加了哪些锁:

发现相对于RC隔离级别,确实多了间隙锁,锁住范围了。我画了这种场景的加锁示意图,大家可以看一下:

 

如果要插入汕头城市的记录,会被Gap锁锁住了,因此会阻塞。

因此,在RR隔离级别下,如果select......for update的查询条件是普通索引的话,命中查询记录的话,除了会加两把X锁(行锁),IX锁(表锁,不影响插入),还会加Gap 锁(间隙锁,会影响插入)

3.4 RR隔离级别 + 无索引

在RR隔离级别下,如果select......for update的查询条件是无索引的话,会锁全表嘛?来一起验证一下

我们直接按顺序执行以下这些语句:

begin;
select * from user_info_tab where age ='26' for update;
select OBJECT_NAME,INDEX_NAME, LOCK_TYPE,LOCK_MODE, LOCK_STATUS, LOCK_DATA FROM performance_schema.data_locks;
复制代码

可以发现加了这么多锁:

一共把锁,这个IX锁(表级别,意向排他锁),我们可以理解,跟前面几个例子的一样。后面三把行锁,就是把每一行的数据记录,都加了X排他锁(行锁,锁的对象对应于主键Id),我们也可以理解。 但是这个第二行,是一把怎么样的X锁呢?

我谷歌了一下,什么是supremum pseudo-record,找到了这个解释:

For the last interval, the next-key lock locks the gap above the largest value in the index and the “supremum” pseudo-record having a value higher than any value actually in the index. The supremum is not a real index record, so, in effect, this next-key lock locks only the gap following the largest index value.

翻译过来,大概意思就是:相当于比索引中所有值都大,但却不存在索引中,相当于最后一行之后的间隙锁。我理解就是如果查询条件有索引的话,类似于一个(索引最大值,+无穷)的虚拟间隙锁。

但是因为我们的查询字段age并没有索引,锁为X锁,lock_data值为supremum pseudo-record,它表示:全表行锁要走聚簇索引进行全部扫描

也就是说RR隔离级别下,对于select......for update查询条件无索引的话,会加一个IX锁(表锁,不影响插入),每一行实际记录行的X锁,还有对应于supremum pseudo-record虚拟全表行锁。这种场景,通俗点讲,其实就是锁表了

我们来做个实验,验证虚拟全表行锁的存在,先开启事务一,执行:

begin;
select * from user_info_tab where age ='26' for update;
复制代码

然后开启事务二,执行一个插入语句:

begin;
insert into user_info_tab(id,`user_name`,`age`,`city`,`status`) values(1,'小明',31,'北京','1');
复制代码

大家可以看下,阻塞了,:

加餐

都写到这里了,大家有没有发现,田螺哥列举RR数据库隔离级别的例子,select......for update条件都是命中数据库表记录的。在这里,田螺哥给大家出道题。在RR隔离级别下,如果select......for update的查询条件,是没命中当前数据表记录的话,又加什么锁呢?

我们来搞点刺激的,select......for update 搞两个条件:一个唯一索引(user_name) + 一个无索引(status),然后没命中当前数据表记录,你觉得会加什么锁呢?

CREATE TABLE `user_info_tab` (
  `id` int NOT NULL AUTO_INCREMENT,
  `user_name` varchar(255) DEFAULT NULL,
  `age` int DEFAULT NULL,
  `city` varchar(255) DEFAULT NULL,
  `status` varchar(4) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  UNIQUE KEY `idx_user_name` (`user_name`) USING BTREE,
  KEY `idx_city` (`city`)
) ENGINE=InnoDB AUTO_INCREMENT=1570074 DEFAULT CHARSET=utf8mb3
复制代码

我们按顺序执行者几条语句:

begin;
select * from  user_info_tab  where user_name ='杰伦'  and status ='0'  for update ;
select OBJECT_NAME,INDEX_NAME, LOCK_TYPE,LOCK_MODE, LOCK_STATUS, LOCK_DATA FROM performance_schema.data_locks;
复制代码

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/155687.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

迁移环境时,忘记私钥证书密码怎么办?

知行之桥的版本在进行不断更新,相较之前的版本而言,知行之桥每一次更新的版本,无论在操作还是功能亦或是便利性上都有更好的优势,因此不少企业会在新版本更新之后果断选择新的版本,企业选择版本更新之后,需…

He3 新版上新

系统功能更新 支持拖动工具,调整位置 支持置顶 支持自定义分类 新增工具 Paseto 生成器 2. 文本分析 JSON 转 PHP,YAML 转 PHP UTF7 编码、UTF7 解码 6. UTM 生成器 CSS 边框圆角生成器 CSV 类转换工具,目前支持 CSV 与 Markdown、HTML、JS…

什么是无代码ITSM工具

拥有强大 ITSM 团队的企业已经能够生存下来,并且在某些情况下在整个大流行期间表现出色。成功的 IT 团队以其在日常运营中断时快速恢复的能力而闻名。 当您需要重新组织服务交付流程时,ITSM 平台可以减少工程工作量,这对于制定弹性 ITSM 战略…

Python学习笔记——元组

Python将不能修改的值称为不可变的,而不可变的列表被称为元组。定义元组元组创建只需要在括号中添加元素,并使用逗号隔开即可。元组使用小括号 ( ),列表使用方括号 [ ]。定义元组后,就可以使用索引来访问其元素,就像访…

ansible作业二

ansible匹配自定义路径清单文件 查看当前匹配的清单文件路径 [rootserver ~]# ansible --version ansible [core 2.13.5]config file /etc/ansible/ansible.cfg --- 默认配置文件configured module search path [/root/.ansible/plugins/modules, /usr/share/ansible/plugin…

力扣(LeetCode)1150. 检查一个数是否在数组中占绝大多数(C++/Python3)

遍历 直观思考&#xff0c;一次遍历数组&#xff0c;计数 target 。用 target 出现次数和数组长度的一半做比较&#xff0c;即可得到答案。 class Solution { public:bool isMajorityElement(vector<int>& nums, int target) {int cnt 0;for(auto &x:nums)if(…

7、Servlet——Servlet核心接口和类、创建Servlet的三种方式

目录 一、Servlet核心接口和类 1、Servlet接口 2、GenericServlet抽象类 3、HttpServlet类 二、创建Servlet的三种方式 1、实现Servlet接口 2、继承GenericServlet抽象类 3、继承HttpServlet类 三、web.xml中其他配置 1、启动优先级&#xff1a; 2、url-pattern定…

【SpringCloud02】订单-支付微服务模块(支付模块构建)

1.微服务cloud整体聚合父工程Project New Project 聚合总父工程名字 Maven选版本 工程名字 字符编码 注解生效激活 Java编译版本选8 File Type过滤 2.父工程POM 2.1Pom.xml文件 <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi&quo…

docker中MySQL忘记登录密码找回方法

查看docker启动列表 docker ps 进入启动的mysql容器中 docker exec -it mysql /bin/bash 修改mysql配置文件 vim /etc/mysql/conf.d/docker.cnf 在最后添加 skip-grant-tables 如果出现命令没找到&#xff0c;则需要安装。 依次使用以下命令安装vim apt-get update apt…

java 系列文章之异常

文章目录一、概念二、异常分类1. Error2. Exception三、异常的处理方式四、Throw 和 throws 的区别1. 位置不同2. 功能不同总结一、概念 果某个方法不能按照正常的途径完成任务&#xff0c;就可以通过另一种路径退出方法。在这种情况下会抛出一个封装了错误信息的对象。此时&a…

关于单行处理函数——ifnull()

我们先来看一个实例&#xff1a; 需求&#xff1a;在员工表emp中查看员工的年薪 分析&#xff1a;计算年薪除了月薪外补助也应该计算在内&#xff0c;所以年薪 (月薪 补助) * 12 sql语句&#xff1a;select ename,(sal comm) *12 as 年薪 from emp; …

SpringBoot+Vue项目在线拍卖系统

文末获取源码 开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7/8.0 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.3.9 浏…

编译原理分析器大作业之字幕分析器

写这篇文章的主要目的呢是分享一下编译原理大作业——电影字幕分析器&#xff0c;分享一下我的做法&#xff0c;可能采用的做法不是特别好的用法&#xff0c;希望各位多多指点&#xff0c;觉得文章不错给点小赞赞喔!!! 题目介绍 写一个srt字幕解释器&#xff0c;需要分析单词…

动手深度学习-线性神经网络:线性回归

目录线性模型&#xff08;1&#xff09;度量模型质量-损失函数&#xff08;2&#xff09;更新模型以提高模型预测质量-随机梯度下降线性回归的从零开始实现线性回归的简洁实现参考教程&#xff1a;https://courses.d2l.ai/zh-v2/线性模型 定义&#xff1a;回归&#xff08;reg…

FFmpeg音频重采样API(libswresample)

目录 参考 lswr功能介绍 lswr使用说明 示例代码 \1. 参考 [1] FFmpeg/Libswresample Documentation [2] FFmpeg/Libswresample Detailed Description [3] FFmpeg/doc/examples/resampling_audio.c \2. lswr功能介绍 FFmpeg中重采样的功能由libswresample(后面简写为lswr…

代码随想录训练营第五十七天

1.回文子串 题647 ①dp数组含义 判断回文子串可以用头元素和尾元素是否相等的方式&#xff0c;设dp[i] [j]为[i,j]子串是否为回文子串&#xff0c;是则为true&#xff0c;否为false。 ②递推公式 若 s[i] s[j] &#xff0c;分三种情况&#xff1a;i j&#xff0c;即只有一…

Leetcode数组专题专练:经典题目+思路解读

文章目录 系列&#xff1a;数组专练 语言&#xff1a;java & go 题目来源&#xff1a;Leetcode 常考点&#xff1a; 二分 & 双指针 & 滑动窗口 & 模拟行为 思路和参考答案文章目录数组专题总结二分法专练双指针专练滑动窗口专练模拟行为专练题目描述数组专题总…

cnn平移等变性和不变性

通俗说法: 等变性&#xff08;Equivariance&#xff09;&#xff1a;对一个输入施加某种变换后所产生的结果同样反应在输出上&#xff0c;说明该变换具有等变性。寻找一个从输入图像到输出类别的映射&#xff0c;这个映射对目标的几何变换(如平移&#xff0c;旋转&#xff0c;…

linux系统中如何使用QT来进行网络编程实现

大家好&#xff0c;今天主要和大家分享一下&#xff0c;如何使用QT进行网络编程与实现。 目录 第一&#xff1a;网络编程基本简介 第二&#xff1a;TCP通信简介 第三&#xff1a;TCP服务器端代码具体实现 第四&#xff1a;源文件mainwindow.cpp的具体实现 第一&#xff1a;…

console的常用方法

console的常用方法&#x1f913; 这篇文章我们来总结一下前端常用的一些调试技巧。&#x1f913;&#x1f913;&#x1f913;&#x1f913;&#x1f913; 说到调试&#xff0c;我们避免不了console这个对象&#xff0c;在它身上有许多的方法&#xff0c;我们来看看最常用的几…