【发布时间】:2014-07-30 12:59:15
【问题描述】:
我有以下两个导致死锁的事务:
交易 1:
SELECT *
FROM bots b
WHERE b.status = :available
AND (b.last_checked_at IS NULL OR b.last_checked_at < :fiveMinsAgo)
ORDER BY b.last_checked_at ASC
LIMIT 1
FOR UPDATE
*AND* (I don't think that this statement is part of the issue)
UPDATE bots b
SET b.last_checked_at = :now
WHERE b.id = :botId
交易 2:
SELECT b.*, bu.*
FROM bots b
LEFT JOIN bot_users bu ON bu.id = b.assigned_to_bot_user_id
WHERE b.currency = :currency
AND (b.status = :available OR (b.status = :reserved AND bu.last_action_at < :threeMinsAgo))
AND ((b.slot_count - b.items_count) >= :amount)
ORDER BY b.keys_count ASC, b.id ASC
LIMIT 1
FOR UPDATE
*OR*
SELECT b.*, bu.*
FROM bots b
LEFT JOIN bot_users bu ON bu.assigned_bot_id = b.id
WHERE b.currency = :currency
AND (b.status = :available OR (b.status = :reserved AND bu.last_action_at < :threeMinsAgo))
AND (b.keys_count >= :amount)
ORDER BY b.keys_count DESC, b.id ASC
LIMIT 1
FOR UPDATE
*AND*
UPDATE bots SET status = :reserved WHERE id = :id
说明:
事务 #1 选择 1 个可用且在过去 5 分钟内未检查的机器人,检查它,然后更新 last_checked_at 字段。
事务 #2(案例 1)正在选择 1 个处理某种货币的机器人,其状态为可用或已保留且“已过期”(3 分钟内没有用户操作),并且有足够的可用插槽。
交易 #2(案例 2)正在选择 1 个处理某种货币的机器人,其状态为可用或已保留且“已过期”(3 分钟内没有用户操作),并且有足够的密钥来满足请求的金额。
注意事项:
在 b.keys_count、b.currency 和 b.status 上有索引。
死锁示例:
------------------------
LATEST DETECTED DEADLOCK
------------------------
2014-07-04 20:33:11 7fc46f270700
*** (1) TRANSACTION:
TRANSACTION 1657246652, ACTIVE 0 sec fetching rows
mysql tables in use 1, locked 1
LOCK WAIT 4 lock struct(s), heap size 1184, 36 row lock(s)
MySQL thread id 30422870, OS thread handle 0x7fc46f437700, query id 1217244940 10.0.88.8 mydb Creating sort index
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 4752665 page no 3 n bits 176 index `PRIMARY` of table `mydb`.`bots` trx id 1657246652 lock_mode X locks rec but not gap waiting
*** (2) TRANSACTION:
TRANSACTION 1657246644, ACTIVE 0 sec updating or deleting
mysql tables in use 1, locked 1
10 lock struct(s), heap size 2936, 37 row lock(s), undo log entries 1
MySQL thread id 30422999, OS thread handle 0x7fc46f270700, query id 1217244943 10.0.88.8 mydb updating
UPDATE bots SET status = 1 WHERE id = 19
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 4752665 page no 3 n bits 176 index `PRIMARY` of table `mydb`.`bots` trx id 1657246644 lock_mode X locks rec but not gap
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 4752665 page no 6 n bits 168 index `status_idx` of table `mydb`.`bots` trx id 1657246644 lock_mode X locks rec but not gap waiting
*** WE ROLL BACK TRANSACTION (1)
查询似乎锁定了大部分行,而不仅仅是一个。
防止这些死锁的最佳方法是什么?最好不要在事务#1 中使用 FOR UPDATE,然后尝试使用基于 ID 的选择获取锁,该选择还包括其他条件?例如:
SELECT *
FROM bots b
WHERE b.status = :available
AND (b.last_checked_at IS NULL OR b.last_checked_at < :fiveMinsAgo)
ORDER BY b.last_checked_at ASC
LIMIT 1
SELECT *
FROM bots b
WHERE b.id = :idSelectedAbove
AND b.status = :available
AND (b.last_checked_at IS NULL OR b.last_checked_at < :fiveMinsAgo)
FOR UPDATE
这显然会在一定百分比的时间内失败,具体取决于各种因素。
在使用 ORDER BY, FOR UPDATE 时有什么方法可以只锁定一行吗?
【问题讨论】:
标签: mysql sql transactions database-deadlocks