【问题标题】:Why I'm getting a deadlock from mysql using SELECT ... FOR UPDATE lock?为什么我使用 SELECT ... FOR UPDATE 锁从 mysql 获得死锁?
【发布时间】:2021-06-08 18:25:24
【问题描述】:

我有两个线程,它们必须更新同一张表,但第一个线程使用主键锁定单个记录,第二个线程必须使用另一个索引锁定一组记录。锁是用 SELECT ... FOR UPDATE 语句制成的,我不明白他们为什么会陷入死锁。

这是桌子:

CREATE TABLE `ingressi` (
 `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
 `evento` int(10) unsigned NOT NULL,
 `stato` int(10) unsigned NOT NULL,
 ....,
 ....,
 PRIMARY KEY (`id`),
  KEY `evento` (`evento`,`stato`) USING BTREE,
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8

这是查询日志(请注意连接):

    43 Query    SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED
    43 Query    set autocommit=0
    43 Query    SELECT stato FROM ingressi WHERE id=1 FOR UPDATE
    39 Query    SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED
    39 Query    set autocommit=0
    39 Query    SELECT count(*) FROM ingressi WHERE evento=66 FOR UPDATE
    43 Query    UPDATE `ingressi` SET stato=0 WHERE id=1
    43 Query    COMMIT

就在最后一次查询之后引发死锁错误。

(conn=39) 尝试获取锁时发现死锁;尝试重新启动 交易

43 和 39 连接是使用连接池的 Spring-Java 应用程序的 JDBC 连接。

为什么第二个连接不等待而死锁?

【问题讨论】:

  • 你能把SHOW CREATE TABLE tab的结果加起来吗?所以我们不必猜测或假设您在表中有哪些索引。
  • 我刚刚用真实数据和查询编辑了这个问题,以便您了解如何设置索引。抱歉,我没有考虑索引的重要性。

标签: mysql transactions deadlock


【解决方案1】:

当你使用二级索引定位和锁定一行时,MySQL会先锁定二级索引中的条目,然后再锁定主键中的对应行。这个双重步骤可能会导致您的死锁。

让我们假设以下行:

+----+--------+-------+
| id | evento | stato |
+----+--------+-------+
|  1 |     66 |    10 |
+----+--------+-------+
  • 您的第一个事务使用主键查找带有id=1 的行,并对其设置排他锁。

  • 您的第二个事务使用索引(evento, strato) 查找具有evento=66 的条目,在该条目上放置一个排他锁(在二级索引中),然后尝试在主键中的行中获取排他锁。既然已经被锁住了,就必须等待。

  • 您的第一个事务现在想要更新该行。它有一个排他锁,所以没问题。但是该更新更改了strato。由于它是索引的,因此必须修改索引(evento, strato) 中的相应条目(这需要排他锁)。不幸的是,第二个事务有一个排他锁,所以第一个事务必须等待第二个事务。

  • 由于第二个事务已经在等待第一个事务,所以我们遇到了死锁

那么如何预防呢?

对于您的具体情况,您可以使用不同的二级索引来查找您的行,例如KEY evento1 (evento)。如果 MySQL 使用此索引(并确保您可以使用 SELECT count(*) FROM ingressi FORCE INDEX (evento1) WHERE evento=66 FOR UPDATE),这应该可以防止这种特定的死锁:

  • 第一个事务锁定主键id=1
  • 第二个事务锁定二级索引evento1中的条目(evento1=66,id=1)
  • 第二个事务尝试获取主键id=1的锁,该锁已被锁定,因此等待
  • 第一个事务更新行并且需要在二级索引evento 中获得对条目(evento1=66,stato=10,id=1) 的锁定。这一次,它起作用了,因为它现在没有锁定
  • 第一个事务提交,释放主键id=1的锁定,第二个事务可以完成

对于您的具体情况,这是否是一个合理的解决方案将取决于您的具体情况。它可能例如添加一个您实际上并不想要或不需要的索引只是为了防止每年发生两次的死锁而过大。或者,也许你有更多的情况都略有不同,可能需要不同的方法。您可以在例如找到一些额外的一般准则。 How to Minimize and Handle Deadlocks.

【讨论】:

    【解决方案2】:

    请详细说明您的用例。如果你们两个有线程试图保护一个锁,你可以简单地同时触发更新语句,并根据更新返回的行数,你可以相应地构建逻辑。需要更多信息才能发表评论。

    【讨论】:

    • 不幸的是,这不合适。我必须为试图获取公共资源的并发请求提供服务。该案例是一个活动的门票预订,在查看后我必须计算当前活动的门票以检查是否有空间容纳新门票(广告许多其他检查)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-13
    • 2015-10-21
    • 2014-03-10
    • 2010-10-31
    • 2020-11-24
    • 1970-01-01
    相关资源
    最近更新 更多