一尘不染

如何避免mysql'试图获取锁时发现死锁;尝试重新开始交易”

mysql

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

我在尝试获取锁时发现了“死锁;昨晚尝试重新启动事务”大约5分钟,这似乎是在向该表中运行INSERT时出现的情况。有人可以建议如何避免此错误吗?

===编辑===

以下是正在运行的查询:

首次访问网站:

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

在每个页面上刷新:

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

每15分钟Cron:

DELETE FROM onlineusers WHERE datetime <= now() - INTERVAL 900 SECOND

然后,它会做一些计数来记录一些统计信息(即:在线成员,在线访客)。


阅读 343

收藏
2020-05-17

共1个答案

一尘不染

一个可以解决大多数僵局的简单技巧就是按特定顺序对操作进行排序。

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

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

如果两个都同时运行,则连接1将锁定键(1),连接2将锁定键(2),每个连接将等待另一个释放键->死锁。

现在,如果您更改查询以使连接以相同的顺序锁定键,即:

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

陷入僵局是不可能的。

所以这是我的建议:

  1. 确保除了delete语句外,没有其他一次锁定访问多个键的查询。如果您这样做(我怀疑您这样做),请按升序在(k1,k2,.. kn)中订购他们的WHERE。

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

更改

DELETE FROM onlineusers WHERE datetime <= now() - INTERVAL 900 SECOND

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

要记住的另一件事是,mysql文档建议在发生死锁的情况下,客户端应自动重试。您可以将此逻辑添加到客户代码中。(说,在放弃之前,3个重试此特定错误)。

2020-05-17