10

启用了 InnoDB 插件的 MySQL 服务器版本 5.1.41。我有以下三个发票表:invoices、invoice_components 和 invoice_expenses。表 invoices 具有 invoice_id 主键。invoice_components 和 invoice_expenses 都与表 invoices 相关联,其中 invoice_id 作为非唯一的 foreign_key(每张发票可以有多个组件和多个费用)。两个表都有这个外键的 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第三个查询。

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

附加信息

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

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

4

3 回答 3

11

我怀疑它与间隙锁下一个键锁以及REPEATABLE READ的行为差异有关:

摘录自 MySQL 文档:SET TRANSACTION 语法

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

阅读提交

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

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

此外,如果相同的锁定发生在不连续的 id 上,比如18and 20, or 18and99

于 2011-07-19T07:12:06.577 回答
0

“对于搜索遇到的索引记录,SELECT ... FROM ... FOR UPDATE 阻止其他会话执行 SELECT ... FROM ... LOCK IN SHARE MODE 或读取某些事务隔离级别。一致的读取将忽略任何锁在读取视图中存在的记录上设置"

哪些特定的锁可以通过 select for update 应用以使其他会话无法读取锁定的记录?

于 2014-06-16T06:46:59.890 回答
-2

您正在使用交易;autocommit 不会禁用事务,它只是让它们在没有显式声明的语句结束时自动提交start transaction

正在发生的事情是,其他一些线程在某条记录上持有记录锁(您正在更新表中的每条记录!)太长时间,并且您的线程正在超时。

您可以通过在事件之后发出“SHOW ENGINE INNODB STATUS”来查看事件的更多详细信息。理想情况下,在安静的测试机器上执行此操作。

于 2011-07-14T08:38:27.667 回答