运行 SELECT x WHERE y FOR UPDATE 时出现死锁

问题描述 投票:0回答:1

我正在使用 Docker 映像中的 MySQL 版本

8.3.0
,采用默认配置。

我将用例简化为只有 2 列的表格。我想要实现的是阻止同时事务为同一“组”插入记录(在本例中为相同的

product_sku
),但允许它们为其他“组”插入。

通过浏览文档https://dev.mysql.com/doc/refman/8.0/en/innodb-locking-reads.html我明白,通过运行

SELECT x FROM y WHERE z FOR UPDATE
我只会锁定满足条件的行
 z
,无需锁定所有其他。然而事实似乎并非如此。

我的桌子:

CREATE TABLE `example` (
  `id` int NOT NULL AUTO_INCREMENT,
  `product_sku` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  PRIMARY KEY (`id`),
  KEY `example_product_sku_IDX` (`product_sku`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

我运行两个 MySQL 会话 -

AA
BB
:

AA> BEGIN;
Query OK, 0 rows affected (0,00 sec)

AA> SHOW VARIABLES WHERE Variable_name='autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit    | OFF   |
+---------------+-------+
1 row in set (0,00 sec)

BB> BEGIN;
Query OK, 0 rows affected (0,00 sec)

BB> SHOW VARIABLES WHERE Variable_name='autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit    | OFF   |
+---------------+-------+
1 row in set (0,00 sec)

AA> SELECT COUNT(*) FROM example WHERE product_sku = 'abc' FOR UPDATE;
+----------+
| COUNT(*) |
+----------+
|        0 |
+----------+
1 row in set (0,00 sec)

BB> SELECT COUNT(*) FROM example WHERE product_sku = 'def' FOR UPDATE;
+----------+
| COUNT(*) |
+----------+
|        0 |
+----------+
1 row in set (0,00 sec)

AA> INSERT INTO example (product_sku) VALUES ('abc');
(this query hangs up...)

BB> INSERT INTO example (product_sku) VALUES ('def');
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

(at this point query from AA executes):
Query OK, 1 row affected (4,02 sec)

根据我的理解,两个锁应该是独立的,因为它们应该锁定不同范围的行。但事实并非如此。

我尝试了以下更改,但没有成功:

  • 删除
    example_product_sku_IDX
    索引
  • SELECT COUNT(*)
    更改为
    SELECT *

我试图了解是否我做错了什么或者 MySQL 有错误。我在 MySQL 板上发现了类似的问题,但它对于解释问题没有太大帮助 - https://bugs.mysql.com/bug.php?id=96748

mysql insert deadlock
1个回答
0
投票

经过一番研究,我想我可以回答我自己的问题。

有两件事与上述问题相关。

首先 - 需要设置适当的事务隔离级别,如下所述:https://dev.mysql.com/doc/refman/8.3/en/set-transaction.html

在这种情况下,查询应如下:

SET TRANSACTION ISOLATION LEVEL READ COMMITTED;

BEGIN;

SELECT COUNT(*) FROM example WHERE product_sku = 'abc' FOR UPDATE;
INSERT INTO example (product_sku) VALUES ('abc');

COMMIT;

第二 - 在上面的示例中,如果

SELECT x FROM y FOR UPDATE
返回空结果,MySQL 将不会创建任何锁。必须至少有一条满足查询条件的记录。因此,在空表的情况下,当多个并发客户端尝试将一行插入同一个“组”时,如果使用
FOR UPDATE
类型的锁定机制,MySQL 不会阻止它们。

© www.soinside.com 2019 - 2024. All rights reserved.