前言
最近在开发需求的时候,用到了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+
,用这个语句查看:
SELEC