当前位置:网站首页>MySQL你到底都加了什么锁?
MySQL你到底都加了什么锁?
2022-08-01 19:13:00 【wang0907】
写在前面
本文一起看下sql语句delete from t1 where id = 10在不同的场景下都会加哪些锁,但是在此之前必须先来明确几个概念,下面先来一起看下。
MVCC
MVCC全称是multi version concurrency control,即版本并发控制,其本质是一种协议,一种数据读操作的协议,规定读分为快照度和当前读,其中快照读基于数据快照读取,读取到的可能是数据的某历史版本,这在不同隔离级别中大有用武之地,另一种当前读,是需要读取最新的数据版本。在MySQL中InnoDB对当前协议提供了具体的实现,我们通过InnoDB来看下哪些操作是快照读,哪些操作又是当前读。
- 快照读
select ? from ? where ?;
- 当前读
select ? from ? where ? lock in share mode;
select ? from ? where ? for update;
insert into ? value (?,?);
update ? set ? where ?;
delete from ? where ?;
其中只有select ? from ? where ? lock in share mode;是Sshare共享锁,其他的都是Xexclusive排他锁。
两阶段锁协议
即在不同的阶段加锁和释放锁,以innodb事务为例,在语句执行前获取锁,在其所在的事务提交时释放锁。
隔离级别
RU:读未提交,读取到其他事务没有提交的修改,实际中不会使用。
RC:读以提交,读取到其他事物已经提交的修改。
RR:可重复读,每次读取到的数据的信息一样。
Serializale:串行,实际中几乎不会使用。
1:不同场景分析
准备数据:
drop table t;
CREATE TABLE `t` (
`id` int(11) NOT NULL,
`c` int(11) DEFAULT NULL,
`d` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `c` (`c`)
) ENGINE=InnoDB;
insert into t values(0,10,100),(5,50,500),
(100,1000,10000),(150,1500,15000),(200,2000,2000),(250,2500,25000);
1.1:主键+RC
注意将数据恢复到初始状态!!!
- 修改数据库隔离级别为RC
mysql> select @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| REPEATABLE-READ |
+-----------------------+
1 row in set (0.00 sec)
mysql> select @@global.transaction_isolation;
ERROR 1193 (HY000): Unknown system variable 'transaction_isolation'
mysql> select @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| REPEATABLE-READ |
+-----------------------+
1 row in set (0.00 sec)
mysql> set global transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec)
新开一个会话查看是否修改成功!!!
mysql> select @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| READ-COMMITTED |
+-----------------------+
1 row in set (0.00 sec)
- 开启两个会话A,B
- 在会话A执行操作
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> delete from t where id=5;
Query OK, 1 row affected (0.00 sec)
- 在会话B执行操作
mysql> update t set c=c where id=5;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
可以看到阻塞等待,只有id=5的record排它行锁。注意锁是加在聚簇索引上。此时加锁如下(图仅示意):

RC不存在间隙锁,所以需要考虑,其实这种场景就算是RR也不会有间隙锁,因为id=5的行是存在的,也是只会加record 的X锁。
1.2:唯一索引+RC
注意将数据恢复到初始状态!!!
- 修改数据库隔离级别为RC
mysql> select @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| REPEATABLE-READ |
+-----------------------+
1 row in set (0.00 sec)
mysql> select @@global.transaction_isolation;
ERROR 1193 (HY000): Unknown system variable 'transaction_isolation'
mysql> select @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| REPEATABLE-READ |
+-----------------------+
1 row in set (0.00 sec)
mysql> set global transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec)
新开一个会话查看是否修改成功!!!
mysql> select @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| READ-COMMITTED |
+-----------------------+
1 row in set (0.00 sec)
- 添加唯一索引
mysql> alter table t add unique index uni_idx_d(`d`);
Query OK, 0 rows affected (0.10 sec)
Records: 0 Duplicates: 0 Warnings: 0
- 开启两个会话A,B
- 在会话A执行操作
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> delete from t where id=5;
Query OK, 1 row affected (0.00 sec)
- 在会话B执行操作
mysql> update t set c=c where id=5;
阻塞
此时show processlist结果如下:
mysql> show processlist;
+----+------+-----------+------+---------+------+----------+-----------------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+------+-----------+------+---------+------+----------+-----------------------------+
| 7 | root | localhost | test | Sleep | 29 | | NULL |
| 9 | root | localhost | test | Query | 23 | updating | update t set c=c where id=5 |
| 11 | root | localhost | NULL | Query | 0 | starting | show processlist |
+----+------+-----------+------+---------+------+----------+-----------------------------+
3 rows in set (0.01 sec)
其中| 9 | root | localhost | test | Query | 23 | updating | update t set c=c where id=5 |就是阻塞的线程,从状态updating可以看出来是被行锁阻塞了,但是这里其实是有两把行X锁的,一把是二级索引树c上的,另一把是聚簇索引上的,这是因为同一记录上的更新/删除操作需要串行执行,不然就会出现数据一致性的问题。
1.3:非唯一索引+RC
注意将数据恢复到初始状态!!!
- 修改数据库隔离级别为RC
mysql> select @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| REPEATABLE-READ |
+-----------------------+
1 row in set (0.00 sec)
mysql> select @@global.transaction_isolation;
ERROR 1193 (HY000): Unknown system variable 'transaction_isolation'
mysql> select @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| REPEATABLE-READ |
+-----------------------+
1 row in set (0.00 sec)
mysql> set global transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec)
新开一个会话查看是否修改成功!!!
mysql> select @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| READ-COMMITTED |
+-----------------------+
1 row in set (0.00 sec)
- 新开2个会话A,B
- 在会话A执行如下操作
mysql> start transaction with consistent snapshot;
Query OK, 0 rows affected, 1 warning (0.01 sec)
mysql> delete from t where c=10;
Query OK, 1 row affected (0.00 sec)
- 在会话B执行如下操作
mysql> delete from t where c=10;
阻塞
此时show processlist结果如下:
mysql> show processlist;
+----+------+-----------+------+---------+------+----------+-----------------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+------+-----------+------+---------+------+----------+-----------------------------+
| 7 | root | localhost | test | Sleep | 29 | | NULL |
| 9 | root | localhost | test | Query | 23 | updating | update t set c=c where id=5 |
| 11 | root | localhost | NULL | Query | 0 | starting | show processlist |
+----+------+-----------+------+---------+------+----------+-----------------------------+
3 rows in set (0.01 sec)
其中| 9 | root | localhost | test | Query | 23 | updating | update t set c=c where id=5 |就是阻塞的线程,从状态updating可以看出来是被行锁阻塞了,但是这里其实是有c=10的行数 * 2把行X锁的,一半的锁是二级索引树c上的,另一半是聚簇索引上的,这是因为同一记录上的更新/删除操作需要串行执行,不然就会出现数据一致性的问题。
1.4:普通列+RC
注意将数据恢复到初始状态!!!
- 修改数据库隔离级别为RC
mysql> select @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| REPEATABLE-READ |
+-----------------------+
1 row in set (0.00 sec)
mysql> select @@global.transaction_isolation;
ERROR 1193 (HY000): Unknown system variable 'transaction_isolation'
mysql> select @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| REPEATABLE-READ |
+-----------------------+
1 row in set (0.00 sec)
mysql> set global transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec)
新开一个会话查看是否修改成功!!!
mysql> select @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| READ-COMMITTED |
+-----------------------+
1 row in set (0.00 sec)
- 新开2个会话A,B
- 在会话A执行如下操作
mysql> delete from t where d=25000;
Query OK, 1 row affected (0.01 sec)
- 在会话B执行如下操作
mysql> update t set c=c where d=25000;
阻塞
show processlist结果如下:
mysql> show processlist;
+----+------+-----------+------+---------+------+----------+--------------------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+------+-----------+------+---------+------+----------+--------------------------------+
| 7 | root | localhost | test | Sleep | 164 | | NULL |
| 9 | root | localhost | test | Query | 3 | updating | update t set c=c where d=25000 |
| 11 | root | localhost | NULL | Query | 0 | starting | show processlist |
+----+------+-----------+------+---------+------+----------+--------------------------------+
3 rows in set (0.00 sec)
其中| 9 | root | localhost | test | Query | 3 | updating | update t set c=c where d=25000 |就代表被record lock阻塞了,这里加锁的行是聚簇索引上所有满足d=25000的行。
1.5:主键列+RR
注意将数据恢复到初始状态!!!
- 修改数据库隔离级别为RR
Repeatable Read
mysql> select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| READ-COMMITTED |
+----------------+
1 row in set, 1 warning (0.01 sec)
mysql> set global transaction isolation level repeatable read;
Query OK, 0 rows affected (0.01 sec)
新开一个会话,查看RR隔离级别是否生效
mysql> show variables like 'tx_isolation';
+---------------+-----------------+
| Variable_name | Value |
+---------------+-----------------+
| tx_isolation | REPEATABLE-READ |
+---------------+-----------------+
1 row in set (0.05 sec)
- 开启两个会话A,B
- 在会话A执行操作
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> delete from t where id=5;
Query OK, 1 row affected (0.00 sec)
- 在会话B执行操作
mysql> update t set c=c where id=5;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
可以看到阻塞等待,只有id=5的record排它行锁。注意锁是加在聚簇索引上。此时加锁如下(图仅示意):

1.6:唯一索引列+RR
同1.2:唯一索引+RC,主键索引树和二级索引树分别一般record 的写锁。
1.7:普通索引列+RR
注意将数据恢复到初始状态!!!
- 修改数据库隔离级别为RR
Repeatable Read
mysql> select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| READ-COMMITTED |
+----------------+
1 row in set, 1 warning (0.01 sec)
mysql> set global transaction isolation level repeatable read;
Query OK, 0 rows affected (0.01 sec)
新开一个会话,查看RR隔离级别是否生效
mysql> show variables like 'tx_isolation';
+---------------+-----------------+
| Variable_name | Value |
+---------------+-----------------+
| tx_isolation | REPEATABLE-READ |
+---------------+-----------------+
1 row in set (0.05 sec)
- 开启两个会话A,B
- 在会话A执行操作
mysql> start transaction with consistent snapshot;
Query OK, 0 rows affected (0.00 sec)
mysql> delete from t where c=1000;
Query OK, 1 row affected (0.00 sec)
- 在会话A执行操作
证明间隙锁的存在:
mysql> begin;
mysql> insert into t values(RAND() * 900 + 100, 6, RAND() * 900 + 100); /*间隙(负无穷,10)*/
Query OK, 1 row affected (0.00 sec)
mysql> insert into t values(RAND() * 900 + 100, 36, RAND() * 900 + 100); /*间隙(10,50)*/
Query OK, 1 row affected (0.00 sec)
mysql> insert into t values(RAND() * 900 + 100, 999, RAND() * 900 + 100); /*间隙(50,1000)*/
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into t values(RAND() * 900 + 100, 1333, RAND() * 900 + 100); /*间隙(1000,1500)*/
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into t values(RAND() * 900 + 100, 1833, RAND() * 900 + 100); /*间隙(1500,2000)*/
Query OK, 1 row affected (0.00 sec)
mysql> insert into t values(RAND() * 900 + 100, 2433, RAND() * 900 + 100); /*间隙(2000,2500)*/
Query OK, 1 row affected (0.00 sec)
mysql> insert into t values(RAND() * 900 + 100, 99999, RAND() * 900 + 100); /*间隙(2500,正无穷)*/
Query OK, 1 row affected (0.00 sec)
可以看到间隙(50,1000)和(1000,1500)被加上了锁。
证明行写锁的存在:
mysql> update t set c=c where id=100;
阻塞
show processlist结果:
mysql> show processlist;
+----+------+-----------------+------+---------+------+----------+-------------------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+------+-----------------+------+---------+------+----------+-------------------------------+
| 13 | root | localhost:39648 | test | Query | 2 | Updating | update t set c=c where id=100 |
| 14 | root | localhost:39948 | test | Sleep | 2271 | | NULL |
| 15 | root | localhost:44283 | NULL | Query | 0 | NULL | show processlist |
+----+------+-----------------+------+---------+------+----------+-------------------------------+
1.8:普通列+RR
- 启动2个会话A,B
- 在会话A启动事务,并使用当前读执行查询
mysql> start transaction;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t where d=500 for update;
+----+------+------+
| id | c | d |
+----+------+------+
| 5 | 50 | 500 |
+----+------+------+
1 row in set (0.00 sec)
- 在会话B插入
ID=9的数据

可以看到阻塞等待,实际上加的间隙锁如下图:

其中区间的就是间隙锁,即影响的行的ID值落到区间内都会被间隙锁所阻塞,另外上面的数字的代表是行锁,即(-∞,0)间隙锁,0行锁,(0,5)间隙锁,5行锁,(5,100)间隙锁,100行锁,(100,150)间隙锁,150行锁,(150,200)间隙锁,200行锁,(200,250)间隙锁,250行锁,(250,+∞)间隙锁,其中间隙锁和行锁的组合我们叫做next-key lock,使用左开右闭的格式来表示,即(-∞,0]next-key lock,(0,5]next-key lock,(5,10]next-key lock,(10,15]next-key lock,(15,20]next-key lock,(20,25]next-key lock,(25,supremum]next-key lock,也就是只要是落到了这些区间的就都会被阻塞,为什么要在所有这些间隙都加锁呢,因为所有间隙都有可能产生满足where=500条件的新数据!!!其实此时就是任何值都会阻塞。
如下一些insert的操作(因为insert操作可能会破坏where d=500的条件,所以都会阻塞):
mysql> insert into t values(9,9,9);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> update t set d=5 where id=0;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into t values (7,7,7);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into t values (23,23,23);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> insert into t values (9999,9999,9999);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
如下是一些update的操作,分为两种情况,第一种是update有匹配的行,此时就会被阻塞,因为可能会破坏where d=500的条件,而当没有匹配的行时,因为不可能会破坏where d=500的条件,所以不会被阻塞,如下分别测试有匹配行和没有匹配行的情况(是否有匹配行已经给出了注释,另外set部分不重要,重要的是where部分):
mysql> update t set c=c where id=0; /*有匹配行*/
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> update t set d=9090 where id=0; /*有匹配行*/
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> update t set d=8 where id=0; /*有匹配行*/
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> update t set c=c where id=0; /*有匹配行*/
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql> update t set d=8 where id=2; /*无匹配行*/
Query OK, 0 rows affected (0.00 sec)
Rows matched: 0 Changed: 0 Warnings: 0
mysql> update t set d=8 where id=2222; /*无匹配行*/
Query OK, 0 rows affected (0.00 sec)
Rows matched: 0 Changed: 0 Warnings: 0
mysql> update t set d=8 where id=250; /*有匹配行*/
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction
mysql>
看到这里,不知道你有没有一个疑问,比如insert操作insert into t values (7,7,7)生成的新数据,并不满足where=500的条件,比如update操作update t set c=c where id=0; /*有匹配行*/也不会产生满足where=500的数据,但是为什么就阻塞了呢?MySQL就这么笨不会加上这个判断吗?我认为不这样做的原因是,代价太大!!!我们的例子肯定是很好判断的,但是实际的情况可就是千变万化了,比如insert into t values (7,7,select dVal from xxx where xxx in(selet ...)),判断的成本必定不可预估。
写在后面
参考文章列表:
边栏推荐
- Heavy cover special | intercept 99% malicious traffic, reveal WAF offensive and defensive drills best practices
- The solution to the vtk volume rendering code error (the code can run in vtk7, 8, 9), and the VTK dataset website
- From ordinary advanced to excellent test/development programmer, all the way through
- 【木棉花】#夏日挑战赛# 鸿蒙小游戏项目——数独Sudoku(3)
- 百度无人驾驶商业化已“上路”
- 即时通讯开发移动端弱网络优化方法总结
- odoo+物联网
- 483-82 (23, 239, 450, 113)
- mysql函数的作用有哪些
- PHP 安全最佳实践
猜你喜欢

哈哈!一个 print 函数,还挺会玩啊!

MySQL数据库————流程控制
![[pyqt5] Custom controls to achieve scaling sub-controls that maintain the aspect ratio](/img/99/34f223614449fcee8e9322dff2e839.png)
[pyqt5] Custom controls to achieve scaling sub-controls that maintain the aspect ratio

通配符 SSL/TLS 证书

【周赛复盘】LeetCode第304场单周赛

How to install voice pack in Win11?Win11 Voice Pack Installation Tutorial

屏:全贴合工艺之GFF、OGS、Oncell、Incell

Combining two ordered arrays

#yyds dry goods inventory# Interview must brush TOP101: the last k nodes in the linked list

安装win32gui失败,解决问题
随机推荐
Live chat system technology (8) : vivo live IM message module architecture practice in the system
Prometheus's Recording rules practice
【综述专栏】IJCAI 2022 | 图结构学习最新综述:研究进展与未来展望
ExcelPatternTool: Excel表格-数据库互导工具
123123123123
How many steps does it take to convert an ENS domain name into music?
在GBase 8c数据库后台,使用什么样的命令来对gtm、dn节点进行主备切换的操作?
明尼苏达大学团队结合高通量实验与机器学习,实现有效可预测的特定位点重组过程,可调节基因编辑速度
通配符 SSL/TLS 证书
AntDB database appeared in the 24th high-speed exhibition, helping smart high-speed innovative applications
Use of message template placeholders
重保特辑|拦截99%恶意流量,揭秘WAF攻防演练最佳实践
Source code analysis of GZIPOutputStream class
DAO开发教程【WEB3.0】
C#/VB.NET Extract table from PDF
The solution to the vtk volume rendering code error (the code can run in vtk7, 8, 9), and the VTK dataset website
To drive efficient upstream and downstream collaboration, how can cross-border B2B e-commerce platforms release the core value of the LED industry supply chain?
力扣刷题之求两数之和
Three solutions: npm WARN config global --global, --local are deprecated. Use --location=global instead.
在Map传值与对象传值中模糊查询