如何避免MySQL';尝试获取锁时发现死锁;尝试重新启动事务';

如何避免MySQL';尝试获取锁时发现死锁;尝试重新启动事务';,mysql,deadlock,Mysql,Deadlock,我有一个记录在线用户的innoDB表。它会在用户每次刷新页面时更新,以跟踪他们所在的页面以及他们最后访问站点的日期。然后我有一个cron,它每15分钟运行一次以删除旧记录 我在尝试获取锁时发现了“死锁”;昨晚尝试重新启动“事务”大约5分钟,在向该表中运行INSERT时似乎是这样。有人能建议如何避免这个错误吗 ==编辑=== 以下是正在运行的查询: 首次访问网站: INSERT INTO onlineusers SET ip = 123.456.789.123, datetime = now(),

我有一个记录在线用户的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
DELETE FROM onlineusers WHERE datetime <= now() - INTERVAL 900 SECOND
每页刷新:

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
DELETE FROM onlineusers WHERE datetime <= now() - INTERVAL 900 SECOND
每隔15分钟进行一次Cron:

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
DELETE FROM onlineusers WHERE datetime <= now() - INTERVAL 900 SECOND

DELETE FROM onlineusers,其中datetime死锁发生在两个事务互相等待获取锁时。例如:

  • Tx 1:锁定A,然后锁定B
  • Tx 2:锁定B,然后锁定A
关于僵局有很多问题和答案。每次插入/更新/或删除行时,都会获得一个锁。为了避免死锁,您必须确保并发事务不会以可能导致死锁的顺序更新行。一般来说,即使在不同的事务中,也要尝试以相同的顺序获取锁(例如,总是先获取表A,然后获取表B)


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

delete语句可能会影响表中大部分行。最终,这可能导致在删除时获取表锁。持有一个锁(在本例中为行锁或页锁)并获取更多锁始终存在死锁风险。但是,我无法解释为什么insert语句会导致锁升级——这可能与页面拆分/添加有关,但是需要有更了解MySQL的人来填充


首先,可以尝试立即为delete语句显式获取表锁。请参阅和。

处理大多数死锁的一个简单技巧是按特定顺序对操作进行排序

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

  • 连接1:锁钥匙(1),锁钥匙(2)
  • 连接2:锁钥匙(2),锁钥匙(1)
如果两者同时运行,连接1将锁定键(1),连接2将锁定键(2),每个连接将等待另一个释放键->死锁

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

  • 连接1:锁钥匙(1),锁钥匙(2)
  • 连接2:锁钥匙(1),锁钥匙(2
不可能出现僵局

这就是我的建议:

  • 确保除了delete语句外,没有其他查询一次锁定访问多个密钥。如果你这样做了(我怀疑你是这样做的),按升序排列它们在(k1,k2,…kn)中的位置

  • 将delete语句修正为按升序工作:

  • 改变

    DELETE FROM onlineusers 
    WHERE datetime <= now() - INTERVAL 900 SECOND
    
    从在线用户中删除
    
    其中datetime您可以尝试通过首先将要删除的每一行的键插入一个临时表(如伪代码)来运行该
    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);
    
    创建临时表deletetemp(userid int);
    插入deletetemp(用户ID)
    
    从onlineusers中选择userid,其中datetime对于使用Spring的Java程序员,我使用AOP特性避免了这个问题,AOP特性自动重试遇到暂时死锁的事务


    有关更多信息,请参见Javadoc。

    我有一个方法,其内部封装在MySqlTransaction中

    当我并行运行相同的方法时,死锁问题就出现了

    运行该方法的单个实例时没有问题

    当我删除MySqlTransaction时,我能够在没有问题的情况下并行运行该方法


    只是分享我的经验,我不提倡任何东西。

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

    我遇到了类似的问题,两个请求同时命中服务器。没有出现如下情况:

    T1:
        BEGIN TRANSACTION
        INSERT TABLE A
        INSERT TABLE B
        END TRANSACTION
    
    T2:
        BEGIN TRANSACTION
        INSERT TABLE B
        INSERT TABLE A
        END TRANSACTION
    
    所以,我很困惑为什么会出现僵局

    然后我发现由于外键的原因,两个表之间存在父子关系。当我在子表中插入一条记录时,事务正在父表的行上获取一个锁。紧接着,我试图更新父行,这将触发锁的提升为独占。由于第二个并发事务已经持有一个共享锁,它导致了死锁


    请参阅:

    cron
    是危险的。如果cron的一个实例未能在下一个实例到期之前完成,那么它们很可能会相互争斗

    最好有一个连续运行的作业,删除一些行,休眠一些行,然后重复

    另外,
    INDEX(datetime)
    对于避免死锁非常重要

    但是,如果datetime测试包含超过20%的表,则
    DELETE
    将进行表扫描。更频繁地删除较小的块是一种解决方法

    使用较小块的另一个原因是锁定较少的r