select ... for update是锁表还是锁行?

461 阅读2分钟

「这是我参与2022首次更文挑战的第1天,活动详情查看:2022首次更文挑战

1.定义

select ... for update;
官方文档给出定义: 读最新的可见数据,并为读取的行设置独占锁。

2.结论

使用select...for update;语句时,

如果查询条件是明确的主键/索引条件时,行锁;否则,表锁;

3.实操

首先我们创建一个简单的表:

create table if not exists `user`(
	`id` bigint unsigned auto_increment,
	`name` varchar(20) not null,
	`password` varchar(20) not null,
	primary key (`id`)
) ENGINE=InnoDB default charset=utf8;

并且创建了主键id
向这张表中插入2条数据:

1.png

查看mysql的自动提交配置:

select @@autocommit;

2.png

关闭自动提交:

set @@autocommit = 0;

3.png

我们再开一个终端窗口:

这时候查看mysql自动提交配置发现还是开着的,重复上述操作,关闭自动提交。

4.png

我们在第一个窗口中执行以下命令:

begin;
select * from user where id = 1 for update;

5.png

可以看到根据主键id进行准确查询,此时不提交当前事务。

我们在第二个窗口进行查询:

6.png

可以看到,不管是全表查询还是条件查询都可以正常进行。

我们尝试修改下id为2的数据:

7.png

可以看到成功执行了,我们对id为1的数据再次执行select ... for update; 8.png

这时候会发现,这条语句阻塞在这里了,过一会就提示超时

9.png

那我们修改id为1的数据呢? 10.png

同样的结果 提交第一个窗口的事务; 11.png

可以看到第二个窗口的命令执行成功了:

12.png

由此我们可以得出第一个结论(索引方式此处不贴了):

使用select ... for update; 查询条件为主键/索引时,发生行锁。

我们再次尝试第二种情况,第一个窗口中执行非主键/索引的查询条件语句: 13.png

在第二个窗口尝试更新id为2的数据:

14.png

结果是阻塞了。

并且此时执行select ... for update也是阻塞的。 15.png

窗口1事务提交后,窗口2可以正常执行更新语句。

16.png

由此,得出第二个结论:

使用select ... for update; 查询条件为非主键/索引时,发生表锁。

那如果是主键/索引范围查询呢? 17.png 我们给出的条件是对id>1的数据进行锁定查询,在另一个事务中,可以看到对不满足条件的语句是可以正常执行select ... for update;和update语句的,但是对于id>1数据,就被锁住了。

那非主键/索引的范围查询呢?

我把数据改成了下面这样:

18.png

对name模糊匹配的china的数据进行查询锁定: 19.png

可以看到不管是范围内的还是范围外的都被锁住了,即表锁:

20.png