如何避免使用复合主键对表的索引进行死锁?

时间:2014-07-23 12:06:59

标签: mysql innodb database-deadlocks

我有一张桌子:

CREATE TABLE `DeviceGrants` (
    `DeviceId` BINARY(16) NOT NULL
        COMMENT "128-bit UID",
    `Grant` ENUM("AcceleratedRead") NOT NULL
        COMMENT "The kind of grant this is",
    `ExpiryTime` DATETIME NOT NULL
        COMMENT "The date/time at which this grant will expire",

    PRIMARY KEY(`DeviceId`, `Grant`),
    KEY (`ExpiryTime`),
    FOREIGN KEY (`DeviceId`) REFERENCES `Devices` (`DeviceId`)
      ON DELETE CASCADE
) ENGINE=InnoDB;

Grant目前只能获取一个值,但此列表很可能在将来的版本中增长,因此该列适用于向前兼容性。)

我目睹了这个(我认为之间的僵局:

INSERT INTO `DeviceGrants` (`DeviceId`, `Grant`, `ExpiryTime`)
VALUES(
    UNHEX('<x>'),
    'AcceleratedRead',
    NOW() + INTERVAL 60 SECOND
)
ON DUPLICATE KEY UPDATE
    `ExpiryTime` = NOW() + INTERVAL 60 SECOND

和此:

DELETE FROM `DeviceGrants` WHERE `ExpiryTime` <= NOW()

现在,按照this excellent answer中的建议,我想通过重写第二个语句来避免死锁。但是,由于该表没有单列自动增量主键(在语义上是不必要的),我不知道如何去做。

这里最好的选择是什么?

2 个答案:

答案 0 :(得分:1)

我不太确定the other solution 如何保证按键以正确的顺序锁定(子查询根本不能使用主键)但我想我们可以很容易将此解决方案扩展到:

DELETE FROM DeviceGrants
WHERE (DeviceId, Grants) IN (
    -- the other solution did not mention: You can't specify target table 'xx' for update in FROM clause
    -- I used the workaround suggested in https://stackoverflow.com/a/45498/1446005
    -- hence the sub-sub-query
    SELECT * FROM (
      SELECT DeviceId, Grants
      FROM DeviceGrants
      WHERE expire <=  NOW()
    ) AS subq) ;

由于我不完全理解原始解决方案,我无法证明上述内容是正确的,但似乎是。运行以下测试10分钟并未引发任何死锁(删除ORDER BY子句并发生死锁):

mysql> CREATE TABLE t (id INT, gr INT, expire DATETIME, PRIMARY KEY(id, gr), KEY(expire));

bash#1> while 1; \
  do mysql test -e "insert into t values (2, 2, NOW()) on duplicate key update expire = NOW() + SLEEP(3);"; \
  done;

bash#2> while true; \
  do mysql test -e "delete from t where (id, gr) in (select * from (select id,gr from t where expire <=  now() order by id, gr ) as sub)" ; \
  done;

答案 1 :(得分:0)

来自MySQL documentation

  

InnoDB使用自动行级锁定。即使只是插入或删除单行的事务,您也可能会遇到死锁。那是因为这些操作并非真正的“原子”;它们会自动设置对插入或删除的行的(可能是几个)索引记录的锁定。

如果是数据损坏的问题,我会使用表锁来确保没有两个事务同时执行。从同一页面我会做这样的事情:

SET autocommit=0;
LOCK TABLES DeviceGrants WRITE;
DELETE FROM DeviceGrants WHERE ExpiryTime <= NOW()
COMMIT;
UNLOCK TABLES;