2014-01-15 125 views
3

我有一个中央数据库服务器和正在执行的查询像这样同时几个“工人”服务器:MySQL的死锁问题与InnoDB的

UPDATE job_queue 
SET 
    worker = '108.166.81.112', 
    attempts = attempts + 1, 
    started = '2014-01-14 10:34:03', 
    token = '13eb3e6a8c3e1becb34051e08f19fd62' 
WHERE completed = '0000-00-00 00:00:00' 
    AND (started = '0000-00-00 00:00:00' OR started < '2014-01-14 10:29:03') 
    AND attempts < 2 
ORDER BY priority DESC, inserted 
LIMIT 1 

偶尔我job_queue表锁起来,如果我运行“SHOW ENGINE INNODB STATUS ”我得到的是这样的:

------------------------ 
LATEST DETECTED DEADLOCK 
------------------------ 
140114 10:34:15 
*** (1) TRANSACTION: 
TRANSACTION 0 46984514, ACTIVE 0 sec, process no 590, OS thread id 140366633146112 fetching rows 
mysql tables in use 1, locked 1 
LOCK WAIT 20 lock struct(s), heap size 3024, 545 row lock(s) 
MySQL thread id 677401, query id 19385205 10.179.103.110 root init 
UPDATE job_queue SET worker='108.166.81.112', attempts=attempts+1, started='2014-01-14 10:34:03', token='13eb3e6a8c3e1becb34051e08f19fd62' WHERE completed='0000-00-00 00:00:00' AND (started='0000-00-00 00:00:00' OR started<'2014-01-14 10:29:03') AND attempts<2 ORDER BY priority DESC, inserted LIMIT 1 
*** (1) WAITING FOR THIS LOCK TO BE GRANTED: 
RECORD LOCKS space id 0 page no 245767 n bits 128 index `PRIMARY` of table `database`.`job_queue` trx id 0 46984514 lock_mode X waiting 
Record lock, heap no 34 PHYSICAL RECORD: n_fields 12; compact format; info bits 0 
0: len 3; hex 800210; asc ;; 1: len 6; hex 000002cced25; asc  %;; 2: len 7; hex 000003c00f1970; asc  p;; 3: len 30; hex 4f3a31343a2243425343616368654170704a6f62223a363a7b733a31393a; asc O:14:"CBSCacheAppJob":6:{s:19:;...(truncated); 4: len 1; hex 80; asc ;; 5: len 8; hex 800012513c58bf24; asc Q<X $;; 6: len 8; hex 800012513c58cc17; asc Q<X ;; 7: len 14; hex 31302e3137392e3130332e313333; asc 10.179.103.133;; 8: len 1; hex 81; asc ;; 9: len 8; hex 800012513c58cc32; asc Q<X 2;; 10: len 0; hex ; asc ;; 11: len 30; hex 353264393033616162656634346239626536306463346438666432303066; asc 52d903aabef44b9be60dc4d8fd200f;...(truncated); 

*** (2) TRANSACTION: 
TRANSACTION 0 46984485, ACTIVE 17 sec, process no 590, OS thread id 140366633547520 starting index read, thread declared inside InnoDB 500 
mysql tables in use 1, locked 1 
4 lock struct(s), heap size 1216, 2 row lock(s), undo log entries 1 
MySQL thread id 676723, query id 19385209 10.179.103.133 root init 
UPDATE job_queue SET worker='10.179.103.133', attempts=attempts+1, started='2014-01-14 10:34:03', token='efd21d0d34f44badbc30386db4dd252e' WHERE completed='0000-00-00 00:00:00' AND (started='0000-00-00 00:00:00' OR started<'2014-01-14 10:29:03') AND attempts<2 ORDER BY priority DESC, inserted LIMIT 1 
*** (2) HOLDS THE LOCK(S): 
RECORD LOCKS space id 0 page no 245767 n bits 128 index `PRIMARY` of table `database`.`job_queue` trx id 0 46984485 lock_mode X locks rec but not gap 
Record lock, heap no 34 PHYSICAL RECORD: n_fields 12; compact format; info bits 0 
0: len 3; hex 800210; asc ;; 1: len 6; hex 000002cced25; asc  %;; 2: len 7; hex 000003c00f1970; asc  p;; 3: len 30; hex 4f3a31343a2243425343616368654170704a6f62223a363a7b733a31393a; asc O:14:"CBSCacheAppJob":6:{s:19:;...(truncated); 4: len 1; hex 80; asc ;; 5: len 8; hex 800012513c58bf24; asc Q<X $;; 6: len 8; hex 800012513c58cc17; asc Q<X ;; 7: len 14; hex 31302e3137392e3130332e313333; asc 10.179.103.133;; 8: len 1; hex 81; asc ;; 9: len 8; hex 800012513c58cc32; asc Q<X 2;; 10: len 0; hex ; asc ;; 11: len 30; hex 353264393033616162656634346239626536306463346438666432303066; asc 52d903aabef44b9be60dc4d8fd200f;...(truncated); 

*** (2) WAITING FOR THIS LOCK TO BE GRANTED: 
RECORD LOCKS space id 0 page no 57 n bits 120 index `PRIMARY` of table `database`.`job_queue` trx id 0 46984485 lock_mode X waiting 
Record lock, heap no 2 PHYSICAL RECORD: n_fields 12; compact format; info bits 0 
0: len 3; hex 800001; asc ;; 1: len 6; hex 000002ccdab1; asc  ;; 2: len 7; hex 000003c0352b3f; asc  5+?;; 3: len 30; hex 4f3a31323a224175746f50696c6f744a6f62223a363a7b733a31383a2200; asc O:12:"AutoPilotJob":6:{s:18:" ;...(truncated); 4: len 1; hex 82; asc ;; 5: len 8; hex 800012513c58af57; asc Q<X W;; 6: len 8; hex 800012513c58bf22; asc Q<X ";; 7: len 14; hex 3130382e3136362e38312e313132; asc 108.166.81.112;; 8: len 1; hex 81; asc ;; 9: len 8; hex 800012513c58bf23; asc Q<X #;; 10: len 0; hex ; asc ;; 11: len 30; hex 616331376430346339326163613366323330646164323239363764336266; asc ac17d04c92aca3f230dad22967d3bf;...(truncated); 

*** WE ROLL BACK TRANSACTION (1) 
------------ 
TRANSACTIONS 
------------ 
Trx id counter 0 46989905 
Purge done for trx's n:o < 0 46986227 undo n:o < 0 0 
History list length 24 
LIST OF TRANSACTIONS FOR EACH SESSION: 
---TRANSACTION 0 0, not started, process no 590, OS thread id 140366628529920 
MySQL thread id 703864, query id 20047015 localhost root 
SHOW ENGINE INNODB STATUS 
---TRANSACTION 0 46989894, not started, process no 590, OS thread id 140366636758784 
MySQL thread id 702822, query id 20046897 10.179.1.63 root 
---TRANSACTION 0 46986223, ACTIVE 39782 sec, process no 590, OS thread id 140366626322176 
25 lock struct(s), heap size 3024, 710 row lock(s), undo log entries 9 
MySQL thread id 677706, query id 19994505 10.179.103.114 root 
Trx read view will not see trx with id >= 0 46986224, sees < 0 46986224 

任何进一步写入表,则时间在我身上,直到我重新启动我的MySQL服务器(或手动杀死锁死作业):

PHP Fatal error: Lock wait timeout exceeded; try restarting transaction(Query: "UPDATE job_queue SET worker='108.166.81.250', attempts=attempts+1, started='2014-01-14 21:27:45', token='369eae55a7f0eacad3b678a3410de8e4' WHERE completed='0000-00-00 00:00:00' AND (started='0000-00-00 00:00:00' OR started<'2014-01-14 21:22:45') AND attempts<2 ORDER BY priority DESC, inserted LIMIT 1") in /utilities/Database.php on line 53 

任何人都可以向我解释为什么这个查询导致死锁?我的印象是InnoDB表上的所有查询都是原子地发生的。有任何想法吗?

回答

4

由于UPDATE查询正在锁定表中的所有行,并且根据使用的索引(或缺少索引),两个不同的会话可能会以稍微不同的顺序锁定它们,这会导致死锁。请记住,UPDATE,DELETESELECT ... FOR UPDATE将锁定它们遇到的所有行,无论这些行是否匹配所有WHERE条件。因此,在使用它们时,应该尽量确保它们遇到尽可能少的行,通过使用索引(理想情况下是主键)并避免模糊或宽泛的选择条件。

我对工作队列的建议非常普遍:尽可能少地锁定,并始终按确定性顺序锁定。所以,一般为:

  1. 使用非锁定读取(常规SELECT)找工作的找东西,你的工人知道怎么做,目前无人认领(lease_owner IS NULL AND lease_expiry IS NULL - 或类似)的事情。
  2. 选择一个工作项目(或者如果你敢的话,或者更简单一些,通常允许完美可接受的性能)。
  3. 更新您的工作项目(声称它,但无论如何它需要更新):
    1. 打开一个事务。
    2. SELECT ... FOR UPDATE锁定您选择的工作项目 - 如果不再无人认领,请放弃并选择另一项。
    3. 用您的工人ID和您的租约到期时间更新您选择的工作项目。
    4. 立即提交您的交易。
  4. 开始您租用的工作项目。
  5. 在其他一些过程中,另一个轮询器会查找已放弃的工作并取消它的声明(通过上面的相同更新过程)。

使用此设计,您可以轻松获得非常高的吞吐量(每秒数千个作业),并且基本上不存在争用和排序问题。选择不太可能与其他轮询者冲突的工作的优化是简单且有效的(例如,为了避免工作不足而选择的作业ID或类似模数)。关键是要记住工作选择上的冲突是好吧 - 只要放弃并再试一次,一切都很快就会移动。

所有锁定写入工作队列中的项目/任务都只能在单行和主键来完成。

+0

此锁定方案效果很好。感谢您的洞察! – Kenny