如何避免mysql'试图锁定时发现死锁; 尝试重启事务'

Dav*_*vid 261 mysql deadlock

我有一个innoDB表记录在线用户.它会在用户每次刷新页面时更新,以跟踪他们所在的页面以及他们上次访问网站的日期.然后,我有一个每15分钟运行一次以删除旧记录的cron.

我得到了一个'试图锁定时发现的死锁; 尝试重新启动事务'昨晚约5分钟,似乎是在运行INSERT到此表时.有人可以建议如何避免这个错误?

===编辑===

以下是正在运行的查询:

首次访问网站:

INSERT INTO onlineusers SET
ip = 123.456.789.123,
datetime = now(),
userid = 321,
page = '/thispage',
area = 'thisarea',
type = 3
Run Code Online (Sandbox Code Playgroud)

在每个页面刷新:

UPDATE onlineusers SET
ips = 123.456.789.123,
datetime = now(),
userid = 321,
page = '/thispage',
area = 'thisarea',
type = 3
WHERE id = 888
Run Code Online (Sandbox Code Playgroud)

Cron每15分钟一次:

DELETE FROM onlineusers WHERE datetime <= now() - INTERVAL 900 SECOND
Run Code Online (Sandbox Code Playgroud)

然后它会记录一些统计数据(即:在线成员,在线访客).

Omr*_*dan 270

可以帮助解决大多数死锁的一个简单技巧是按特定顺序对操作进行排序.

当两个事务试图以相反的顺序锁定两个锁时,你会遇到死锁,即:

  • 连接1:锁定键(1),锁定键(2);
  • 连接2:锁定键(2),锁定键(1);

如果两者同时运行,则连接1将锁定密钥(1),连接2将锁定密钥(2),并且每个连接将等待另一个连接释放密钥 - >死锁.

现在,如果您更改了查询,连接将以相同的顺序锁定密钥,即:

  • 连接1:锁定键(1),锁定键(2);
  • 连接2:锁定键(1),锁定键(2);

陷入僵局是不可能的.

所以这就是我的建议:

  1. 除了delete语句之外,确保没有其他任何锁定访问多个密钥的查询.如果你这样做(我怀疑你这样做),请按升序排列他们的WHERE(k1,k2,.. kn).

  2. 修复delete语句以升序工作:

更改

DELETE FROM onlineusers WHERE datetime <= now() - INTERVAL 900 SECOND
Run Code Online (Sandbox Code Playgroud)

DELETE FROM onlineusers WHERE id IN (SELECT id FROM onlineusers
    WHERE datetime <= now() - INTERVAL 900 SECOND order by id) u;
Run Code Online (Sandbox Code Playgroud)

另外要记住的是mysql文档建议在遇到死锁的情况下,客户端应该自动重试.您可以将此逻辑添加到客户端代码中.(比如,在放弃之前对此特定错误进行3次重试).

  • 如果你启用了交易,那就是全有或全无.如果您有任何类型的例外,则保证整个交易无效.在这种情况下,你会想重新启动整个事情. (5认同)
  • 基于巨型表上的选择的删除比简单删除慢得多 (4认同)
  • @OmryYadan据我所知,在MySQL中,你不能从你正在进行更新的同一个表中的子查询中选择.dev.mysql.com/doc/refman/5.7/en/update.html (4认同)
  • 非常感谢你,老兄.'sort statements'提示解决了我的死锁问题. (3认同)
  • 如何对删除查询中的项目进行排序修复死锁? (3认同)
  • 如果我有Transaction(autocommit = false),则抛出死锁异常.仅仅重试相同的statement.executeUpdate()或整个事务现在是gimped并且应该回滚并重新运行其中运行的所有内容是否足够? (2认同)

ewe*_*nli 69

当两个事务相互等待获取锁定时发生死锁.例:

  • Tx 1:锁定A,然后锁定B.
  • Tx 2:锁定B,然后是A.

关于死锁的问题和答案很多.每次插入/更新/删除行时,都会获取锁定.为避免死锁,您必须确保并发事务不会按顺序更新行,从而导致死锁.一般来说,即使在不同的事务中也尝试以相同的顺序获取锁(例如,总是先用表A,然后用表B).

数据库死锁的另一个原因可能是缺少索引.插入/更新/删除行时,数据库需要检查关系约束,即确保关系一致.为此,数据库需要检查相关表中的外键.它可能导致获取其他锁定而不是被修改的行.确保始终在外键(当然还有主键)上有索引,否则可能导致表锁而不是行锁.如果发生表锁定,则锁争用会更高,并且死锁的可能性会增加.

  • 所以我的问题可能是用户刷新了页面,从而在cron尝试在记录上运行DELETE的同时触发记录的更新.但是,我在INSERTS上获取错误,因此cron不会删除刚刚创建的记录.那么如何在尚未插入的记录上发生死锁? (3认同)

cha*_*sap 12

如果有人仍在为这个问题苦苦挣扎:

我遇到了类似的问题,其中 2 个请求同时命中服务器。没有像下面这样的情况:

T1:
    BEGIN TRANSACTION
    INSERT TABLE A
    INSERT TABLE B
    END TRANSACTION

T2:
    BEGIN TRANSACTION
    INSERT TABLE B
    INSERT TABLE A
    END TRANSACTION
Run Code Online (Sandbox Code Playgroud)

所以,我很困惑为什么会发生死锁。

然后我发现由于外键,两个表之间存在父子关系。当我在子表中插入一条记录时,事务正在获取父表行上的锁。在那之后,我立即尝试更新触发锁定提升到 EXCLUSIVE 的父行。由于第二个并发事务已经持有共享锁,因此导致死锁。

参考:https : //blog.tekenlight.com/2019/02/21/database-deadlock-mysql.html


And*_*bel 11

delete语句很可能会影响表中总行数的很大一部分.最终,这可能会导致在删除时获取表锁.持有锁(在这种情况下是行锁或页锁)并获得更多锁定始终是一种死锁风险.但是我无法解释为什么insert语句会导致锁定升级 - 它可能与页面拆分/添加有关,但更好地了解MySQL的人必须填写那里.

首先,可以尝试立即为delete语句显式获取表锁.请参阅LOCK TABLES表锁定问题.


小智 6

您可以尝试delete通过首先将要删除的每一行的键插入到像此伪代码的临时表中来完成该作业

create temporary table deletetemp (userid int);

insert into deletetemp (userid)
  select userid from onlineusers where datetime <= now - interval 900 second;

delete from onlineusers where userid in (select userid from deletetemp);
Run Code Online (Sandbox Code Playgroud)

像这样打破它效率较低但它避免了在此期间保持键范围锁定的需要delete.

此外,修改select查询以添加where不包括超过900秒的行的子句.这样可以避免对cron作业的依赖,并允许您重新安排它以减少运行次数.

关于死锁的理论:我在MySQL中没有很多背景但是这里有...... delete它将持有日期时间的键范围锁,以防止where在事务中间添加匹配其子句的行,并且当它找到要删除的行时,它将尝试在它正在修改的每个页面上获取锁定.在insert将要收购它插入到页面上的锁,然后试图获取钥匙锁.通常情况下,insert耐心等待该密钥锁打开但如果delete尝试锁定insert正在使用的同一页面,这将会死锁,因为delete需要页面锁定和insert密钥锁定的需要.这似乎不适合插入,delete并且insert正在使用不重叠的日期时间范围,因此可能正在进行其他操作.

http://dev.mysql.com/doc/refman/5.1/en/innodb-next-key-locking.html