MySQL 5.7中的SELECT ... LIMIT ... FOR UPDATE行为

u3r8eeie  于 2023-11-16  发布在  Mysql
关注(0)|答案(1)|浏览(139)

MySQL 5.7使用InnoDB。

简单测试模式:

create table test(
    id int primary key,
    name varchar(8)
);

字符串
插入50行:

insert into test values (1, 'Test 1'), (2, 'Test 2') .... till 50 rows


我想要SELECT ... LIMIT OFST, LMT ... FOR UPDATE,这样我就可以锁定LMT行。

测试一:
交易一:

begin 
    select * from test limit 0, 1 for update; (1)
    sleep(10); -- locking for 10 seconds for sake of testing
commit


从(1)返回的行:

id|name  |
--+------+
 1|Test 1|

交易二:

begin 
    select * from teste where id = 2 for update; (1)
commit


事务2立即返回,而不必等待10秒等待事务1提交。

测试二:
交易一:

begin 
    select * from test limit 0, 1 for update;
    sleep(10);
commit;


从(1)返回的行:

id|name  |
--+------+
 1|Test 1|

交易二:

begin 
    select * from test limit 1, 1 for update; (1)
commit;


从(1)返回的行:

id|name  |
--+------+
 2|Test 2|


即使事务2返回了不同的结果集,它也必须等待10秒才能提交事务1。为什么会发生这种情况?有什么解决方法吗?如果是MySQL 8.0+,一个简单的SKIP LOCKED就可以解决我的问题。
我正在开发一个系统,需要从一个表中的代码池中选择凭证,以便可以出售它们。这是一个遗留系统。
系统执行以下查询:

select
    *
from
    vouchers v
where
    v.status = 1
limit
    0, ? -- limit inferred by programming language (it can be 1 to 50)
for update


问题是,当有高吞吐量时,应用程序开始崩溃,因为所有的事务都在等待其他事务提交(由于锁)。如何避免这种情况?

de90aj5v

de90aj5v1#

innodb locks set by sql statements上的MySQL手册有描述你所经历的行为的关键点。
1.行级锁实际上是索引记录锁。
1.锁定读取锁定在搜索要检索的行时遇到的所有行,即使该行后来从结果集中排除。
1.如果有一个合适的唯一索引来确定哪些行需要锁定,那么只有那些行会被锁定。
1.如果没有合适的索引可用于锁定读取,则语句必须扫描整个表,从而有效地锁定所有行。
基于上述规则,第一个测试的第一个事务启动一个表扫描,可能会按物理顺序锁定行。限制部分在第一个记录之后停止锁定。
这就是为什么第二个事务可以立即返回,因为它使用PK来检索并锁定id=2的行。
然而,第二个测试块的第二个事务在没有索引的情况下,MySQL必须从第一行开始阅读整个表,因为它不能在不扫描第一行的情况下确定第二行。它试图锁定第一行,但必须等到第一个事务提交。

相关问题