一尘不染

InnoDB SELECT … FOR UPDATE 语句锁定表中的所有行

sql

MySQL 服务器版本 5.1.41,启用了 InnoDB 插件。我有以下三个发票表:invoices、invoice_components 和 invoice_expenses。表发票有 invoice_id 主键。invoice_components 和 invoice_expenses 都链接到表发票,其中 invoice_id 作为非唯一的外键(每张发票可以有多个组件和多个费用)。两个表都有这个外键的 BTREE 索引。

我有以下交易:

交易 1

START TRANSACTION;
SELECT * FROM invoices WHERE invoice_id = 18 FOR UPDATE;
SELECT * FROM invoice_components WHERE invoice = 18 FOR UPDATE;
SELECT * FROM invoice_expenses WHERE invoice = 18 FOR UPDATE;
第一个事务一切正常,行被选中并锁定。

交易2

START TRANSACTION; 
SELECT * FROM invoices WHERE invoice_id = 19 FOR UPDATE; 
SELECT * FROM invoice_components WHERE invoice = 19 FOR UPDATE; 
SELECT * FROM invoice_expenses WHERE invoice = 19 FOR UPDATE; 

第二个事务ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction为第三个查询返回。

当我尝试选择… FOR UPDATE其他发票及其组成和费用时,也会发生同样的情况。似乎第一笔交易已经锁定了 invoice_expenses 表中的所有行。任何想法为什么会发生这种情况?

附加信息

事务 2 在事务 1 的第三次查询之后开始。服务器上没有其他用户、连接或事务。

该问题发生在默认的 REPEATABLE READ 事务隔离级别。它通过更改为 READ COMMITTED 级别来修复。这是一个解决方案,但它仍然没有解释为什么问题发生在 invoice_expenses 而不是 invoice_components 上。


阅读 193

收藏
2021-05-30

共1个答案

一尘不染

我怀疑这与间隙锁和next-key 锁以及REPEATABLE READ行为的差异有关:

摘录来自 MySQL 文档:SET TRANSACTION 语法

对于锁定读取(SELECT with FOR UPDATE 或 LOCK IN SHARE MODE)、UPDATE 和 DELETE 语句,锁定取决于语句是使用具有唯一搜索条件的唯一索引还是范围类型搜索条件。对于具有唯一搜索条件的唯一索引,InnoDB 只锁定找到的索引记录,而不锁定它之前的间隙。对于其他搜索条件,InnoDB 锁定扫描的索引范围,使用间隙锁或 next-key(间隙加索引记录)锁来阻止其他会话插入范围所覆盖的间隙。

和读取提交:

注意:在 MySQL 5.1 中,如果使用 READ COMMITTED 隔离级别或启用了 innodb_locks_unsafe_for_binlog 系统变量,则除了外键约束检查和重复键检查之外,没有 InnoDB 间隙锁定。此外,在 MySQL 评估 WHERE 条件后,将释放不匹配行的记录锁。

也许 OP 可以告诉我们innodb_locks_unsafe_for_binlog system变量的状态,以及在更改此变量的设置时是否发生相同的锁定。

此外,如果相同的锁定发生在非顺序 id 的情况下,例如18and 20,或18and99

2021-05-30