当前位置:网站首页>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 ...))
,判断的成本必定不可预估。
写在后面
参考文章列表:
边栏推荐
- 【webrtc】sigslot : 继承has_slot 及相关流程和逻辑
- 安装GBase 8c数据库的时候,报错显示“Resource:gbase8c already in use”,这怎么处理呢?
- app直播源码,点击搜索栏自动弹出下拉框
- Write code anytime, anywhere -- deploy your own cloud development environment based on Code-server
- 力扣刷题之求两数之和
- 首篇 NLP 领域图神经网络综述:127 页,从图构建到实际应用面面观
- 网站建设流程
- cf:D. Magical Array【数学直觉 + 前缀和的和】
- 屏:全贴合工艺之GFF、OGS、Oncell、Incell
- A simple Flask PIN
猜你喜欢
随机推荐
Find the sum of two numbers
vtk体绘制代码报错的解决办法(代码在vtk7,8,9中都能运行),以及VTK数据集网站
Tencent Cloud Hosting Security x Lightweight Application Server | Powerful Joint Hosting Security Pratt & Whitney Version Released
Use of message template placeholders
Try compiling QT test on Allwinner V853 development board
Win11如何删除升级包?Win11删除升级包的方法
在Map传值与对象传值中模糊查询
通配符 SSL/TLS 证书
【综述专栏】IJCAI 2022 | 图结构学习最新综述:研究进展与未来展望
重保特辑|拦截99%恶意流量,揭秘WAF攻防演练最佳实践
【木棉花】#夏日挑战赛# 鸿蒙小游戏项目——数独Sudoku(3)
Multi-Party Threshold Private Set Intersection with Sublinear Communication-2021:解读
【LeetCode】Day109-最长回文串
ClassID的计算中,&表示啥意思
C#/VB.NET Extract table from PDF
What are the application advantages of SaaS management system?How to efficiently improve the digital and intelligent development level of food manufacturing industry?
When compiling a program with boost library with VS2013, it prompts fatal error C1001: An internal error occurred in the compiler
腾讯云主机安全 x 轻量应用服务器|强强联合主机安全普惠版重磅发布
ExcelPatternTool: Excel form-database mutual import tool
请你说说多线程