2016-06-20 196 views
1

我有一個java應用程序,並且在sql下面發生死鎖異常: insert into voucher ( id, order_id, voucher_code ) SELECT #{id}, #{orderId}, #{voucherCode} FROM DUAL WHERE NOT EXISTS (SELECT id FROM voucher where order_id = #{orderId}) 「order_id」是唯一鍵。 我相信當sql在併發執行時它會死鎖。 但是,我沒有足夠的權限來執行show engine innodb status,所以我無法獲得關於死鎖異常的信息。查找mysql innodb死鎖原因INSERT INTO SELECT

我嘗試在實驗室環境中重現該問題。 臺試驗低於: Create Table: CREATE TABLE `test` ( `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT, `info` varchar(128) NOT NULL DEFAULT '', `order_id` int(11) NOT NULL DEFAULT '0', PRIMARY KEY (`id`), UNIQUE KEY `uni_order_id` (`order_id`) ) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8

和我在兩個不同的會話執行兩個sql語句:

insert into test(info,order_id) select '12345',sleep(10) from dual where not exists (select info from test where info='12345'); 
insert into test(info,order_id) select '12345',234 from dual where not exists (select info from test where info='12345'); 

僵局日誌如下:

------------------------ 
LATEST DETECTED DEADLOCK 
------------------------ 
2016-06-20 17:26:54 700000a83000 
*** (1) TRANSACTION: 
TRANSACTION 2321, ACTIVE 9 sec inserting 
mysql tables in use 2, locked 2 
LOCK WAIT 5 lock struct(s), heap size 1184, 2 row lock(s) 
MySQL thread id 1, OS thread handle 0x700000a3f000, query id 29 localhost root executing 
insert into test(info,order_id) select '12345',234 from dual where not exists (select info from test where info='12345') 
*** (1) WAITING FOR THIS LOCK TO BE GRANTED: 
RECORD LOCKS space id 9 page no 3 n bits 72 index `PRIMARY` of table `test`.`test` trx id 2321 lock_mode X insert intention waiting 
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0 
0: len 8; hex 73757072656d756d; asc supremum;; 

*** (2) TRANSACTION: 
TRANSACTION 2320, ACTIVE 10 sec setting auto-inc lock 
mysql tables in use 2, locked 2 
3 lock struct(s), heap size 360, 1 row lock(s) 
MySQL thread id 2, OS thread handle 0x700000a83000, query id 28 localhost root User sleep 
insert into test(info,order_id) select '12345',sleep(10) from dual where not exists (select info from test where info='12345') 
*** (2) HOLDS THE LOCK(S): 
RECORD LOCKS space id 9 page no 3 n bits 72 index `PRIMARY` of table `test`.`test` trx id 2320 lock mode S 
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0 
0: len 8; hex 73757072656d756d; asc supremum;; 

*** (2) WAITING FOR THIS LOCK TO BE GRANTED: 
TABLE LOCK table `test`.`test` trx id 2320 lock mode AUTO-INC waiting 
*** WE ROLL BACK TRANSACTION (2) 

我假定子查詢select info from test where info=‘12345’可持有SLOCK ,並且insert into … select想要XLock。 但我沒有找到官方文件來支持我的觀點。

所以我的問題如下:
1.是我的再現設計權嗎?
2.是我的supposal(subquery select info from test where info=‘12345’可能會持有SLOCK)對不對?任何官方文件可以支持我的支持?

回答

1

當您運行INSERT ... SELECT時,默認情況下,MySQL將鎖定SELECT中的所有行。 如果您將隔離級別更改爲READ-COMMITTED,則SELECT中的行未鎖定。在你的情況下,這應該解決死鎖問題。 PS:我建議閱讀關於隔離級別並理解它們之間的差異。

http://dev.mysql.com/doc/refman/5.7/en/innodb-locks-set.html

INSERT INTO t選擇...從s WHERE ...上插入T.每一行設置的專用索引 記錄鎖定(無間隙地鎖定)如果 事務隔離級別讀取已提交,或 innodb_locks_unsafe_for_binlog啓用和交易 隔離級別不是SERIALIZABLE,InnoDB執行作爲 一致讀取(無鎖)S上搜索。否則,InnoDB的套從S. InnoDB的行共享的next-key鎖定 在後一種情況下設置鎖:從備份在 向前滾動恢復,每個SQL語句必須 以完全相同的方式,它已完成執行本來。

CREATE TABLE ... SELECT ...使用共享下一鍵 鎖或作爲一致讀取執行SELECT操作,如同INSERT ... SELECT。

當一個SELECT用於構造時REPLACE INTO t SELECT ... FROM s WHERE ...或UPDATE t ... WHERE col IN(SELECT ... FROM s ...), InnoDB sets在表s的行上共享下一個鍵鎖。

+0

我想在這種情況下找到死鎖的原因,而不是解決方案。 – FatGhosta

0

你工作太辛苦。我認爲做同樣的事情:

INSERT IGNORE INTO voucher 
    (id, order_id, voucher_code) 
    VALUES 
    (#{id}, #{orderId}, #{voucherCode}) 

如果訂單ID已經在表中,UNIQUE鍵會抱怨,但IGNORE會沉默吧。

但是...你有另一個問題...你的查詢(和我的)檢查id是重複的。也就是說,可能有2件東西打到UNIQUE,但只檢查一件。

通常,一個只會離開了id

INSERT IGNORE INTO voucher 
    (order_id, voucher_code) 
    VALUES 
    (#{orderId}, #{voucherCode}) 

但是,你可能希望在插入時獲得新的id?所以......

INSERT INTO voucher 
    (order_id, voucher_code) 
    VALUES 
    (#{orderId}, #{voucherCode}) 
    ON DUPLICATE KEY UPDATE id = LAST_INSERT_ID(id); 
SELECT LAST_INSERT_ID(); 

如果,另一方面,你真的想與唯一鍵的發揮,考慮這個凌亂聲明:

REPLACE INTO voucher 
    (id, order_id, voucher_code) 
    VALUES 
    (#{id}, #{orderId}, #{voucherCode}) 

這將

  1. DELETE任何匹配的行或者唯一密鑰(id或order_id)
  2. INSERT的值。 (既然你指定了id,AUTO_INCREMENT沒有效果。)

現在,讓我們繼續這一步。爲什麼有兩個獨特的鑰匙?你不能完全擺脫id嗎?然後讓order_idPRIMARY KEY

現在,您已經簡化了模式,簡化了查詢,並且很可能消除了死鎖。