我有一张桌子:
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中的建议,我想通过重写第二个语句来避免死锁。但是,由于该表没有单列自动增量主键(在语义上是不必要的),我不知道如何去做。
这里最好的选择是什么?
答案 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)
InnoDB使用自动行级锁定。即使只是插入或删除单行的事务,您也可能会遇到死锁。那是因为这些操作并非真正的“原子”;它们会自动设置对插入或删除的行的(可能是几个)索引记录的锁定。
如果是数据损坏的问题,我会使用表锁来确保没有两个事务同时执行。从同一页面我会做这样的事情:
SET autocommit=0;
LOCK TABLES DeviceGrants WRITE;
DELETE FROM DeviceGrants WHERE ExpiryTime <= NOW()
COMMIT;
UNLOCK TABLES;