2016-12-07 89 views
21

我正在使用MySQL 5.5。我注意到在併發場景中發生了一個特殊的死鎖,我認爲這種僵局不會發生。當升級共享到獨佔鎖時避免MySQL死鎖

重現這樣,利用同時運行兩個MySQL客戶端會話:

MySQL的會話1

create table parent (id int(11) primary key); 
insert into parent values (1); 
create table child (id int(11) primary key, parent_id int(11), foreign key (parent_id) references parent(id)); 

begin; 
insert into child (id, parent_id) values (10, 1); 
-- this will create shared lock on parent(1) 

MySQL的會話2

begin; 
-- try and get exclusive lock on parent row 
select id from parent where id = 1 for update; 
-- this will block because of shared lock in session 1 

MySQL的會話1

-- try and get exclusive lock on parent row 
select id from parent where id = 1 for update; 
-- observe that mysql session 2 transaction has been rolled back 

MySQL的會議2

ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction 

show engine innodb status報告的信息是這樣的:

------------------------ 
LATEST DETECTED DEADLOCK 
------------------------ 
161207 10:48:56 
*** (1) TRANSACTION: 
TRANSACTION 107E67, ACTIVE 43 sec starting index read 
mysql tables in use 1, locked 1 
LOCK WAIT 2 lock struct(s), heap size 376, 1 row lock(s) 
MySQL thread id 13074, OS thread handle 0x7f68eccfe700, query id 5530424 localhost root statistics 
select id from parent where id = 1 for update 
*** (1) WAITING FOR THIS LOCK TO BE GRANTED: 
RECORD LOCKS space id 0 page no 3714 n bits 72 index `PRIMARY` of table `foo`.`parent` trx id 107E67 lock_mode X locks rec but not gap waiting 
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0 
0: len 4; hex 80000001; asc  ;; 
1: len 6; hex 000000107e65; asc  ~e;; 
2: len 7; hex 86000001320110; asc  2 ;; 

*** (2) TRANSACTION: 
TRANSACTION 107E66, ACTIVE 52 sec starting index read 
mysql tables in use 1, locked 1 
5 lock struct(s), heap size 1248, 2 row lock(s), undo log entries 1 
MySQL thread id 12411, OS thread handle 0x7f68ecfac700, query id 5530425 localhost root statistics 
select id from parent where id = 1 for update 
*** (2) HOLDS THE LOCK(S): 
RECORD LOCKS space id 0 page no 3714 n bits 72 index `PRIMARY` of table `foo`.`parent` trx id 107E66 lock mode S locks rec but not gap 
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0 
0: len 4; hex 80000001; asc  ;; 
1: len 6; hex 000000107e65; asc  ~e;; 
2: len 7; hex 86000001320110; asc  2 ;; 

*** (2) WAITING FOR THIS LOCK TO BE GRANTED: 
RECORD LOCKS space id 0 page no 3714 n bits 72 index `PRIMARY` of table `foo`.`parent` trx id 107E66 lock_mode X locks rec but not gap waiting 
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0 
0: len 4; hex 80000001; asc  ;; 
1: len 6; hex 000000107e65; asc  ~e;; 
2: len 7; hex 86000001320110; asc  2 ;; 

*** WE ROLL BACK TRANSACTION (1) 

你可以看到交易(1)不顯示任何S或X鎖已經獲得;它只是試圖獲得排他鎖。據我瞭解,由於沒有周期,所以在這種情況下不應該出現僵局。

這是一個已知的MySQL錯誤?有其他人遇到過嗎?使用了哪些解決方法?

這些都是可能的前進腳步,我們可以採取:

  • 減少我們的外鍵的使用(以我們的生產場景中,我們只軟刪除引用的錶行,但噁心)
  • 採集獨家鎖定前面而不是隱式共享鎖(會降低我們的併發吞吐量)
  • 更改我們的邏輯,以便我們不再需要在同一個事務中添加子行的排他鎖(風險和難度)
  • 更改我們的版本的MySQL到o ne沒有表現出這種行爲

我們沒有考慮其他選擇嗎?

+1

https://bugs.mysql.com/bug.php?id=48652特別是在2012年10月22日12:32由馬爾科Mäkelä評論。 – bishop

+0

剛剛轉載了上述步驟。 Mysql 5.1.73 UPD沒有錯誤。 但是,錯誤存在於5.7.17,所以我認爲它是版本> 5.5的具體行爲 –

回答

5

這是一個長期存在的BUG,你可以閱讀從更多:This bug report

這是在MySQL級表鎖定問題。

在InnoDB內部,FOREIGN KEY約束檢查可以讀取(或者,帶有ON UPDATE或ON DELETE子句的 )寫入父表或子表。

通常,表訪問由以下鎖管理: 1. MySQL元數據鎖 2. InnoDB表鎖 3。InnoDB的記錄鎖

所有這些鎖一直持有到交易結束。

的InnoDB表和記錄鎖定會跳過某些模式,但不 期間外鍵檢查。因爲MySQL 獲取元數據只對那些明確在SQL語句中提到 表鎖(S)的僵局造成的。

我想一個解決方法可能是在問題FOREIGN KEY操作之前,在事務開始時訪問子表(或父表) 表。

閱讀的討論,它的答覆的

2

原因更新父行沒有給出, 但我會假設這必須做一些德正常化的基礎上,從問題的順序:

-- session 1 
begin; 
insert into child (id, parent_id) values (10, 1); 
... 
select id from parent where id = 1 for update; 

例如,順序(父表)具有列量, ,其被保持爲所有命令行(子 表)的量的總和。

這似乎保持父數據邏輯應用程序 本身(有明確的更新語句),它具有以下後果編碼:

  • 如果插入到孩子在許多不同的地方做, 然後必須在所有這些地方更新客戶端中的應用程序邏輯 以保持完整性。這是代碼重複。

  • 即使這隻在一個地方完成,添加子項時需要更新父表 的事實對於服務器來說是不可能找到的。根據需要

    子表上定義觸發器,即更新父表:

相反,請考慮以下選項。

它具有以下含義:

  • 第一,保持父表中的邏輯不再(可能) 重複的,因爲它是在觸發器本身。

  • 其次,這是這裏的重要組成部分,MySQL服務器現在知道,只要孩子記錄插入 父表的更新,也正因爲如此 ,適當的鎖(獨佔而非共享)是拍攝。

測試8.0,見下文。

關於關於併發吞吐量的關注,

  • 不同父行操作不同的交易將在 並行執行,因爲排他鎖採取父(不同的)行,不 父表。

  • 在同一父行上同時操作的交易實際上是 被序列化......這實際上是預期結果,因爲它們無論如何都在 上完成同一記錄。

序列化是保證成功應該提供更好的吞吐量(只要應用程序工作負載而言)具有 一些交易失敗,只重試他們的交易。

很顯然,更新和刪除觸發器也應該也需要更新父項,具體取決於應用程序邏輯。

設置

create table parent (
    id int(11) primary key, 
    number_of_children int(11)); 

create table child (
    id int(11) primary key, 
    parent_id int(11), 
    foreign key (parent_id) references parent(id)); 

delimiter $$; 
create trigger bi_child before insert on child 
for each row 
begin 
    update parent 
    set number_of_children = number_of_children + 1 
    where id = NEW.parent_id; 
end 
$$ 
delimiter ;$$ 

begin; 
insert into parent values (1, 0); 
insert into parent values (2, 0); 
commit; 

會話1

begin; 
insert into child values (10, 1); 

會話2

begin; 
insert into child values (20, 2); 

未被阻塞,作爲differen t父母被使用。

會話3

begin; 
-- this now blocks, waiting for an X lock on parent row 1. 
insert into child values (11, 1); 

會話1

-- unlocks session 3 
commit; 

會話3

提交;

會話2

提交;

結果

select * from parent; 
id  number_of_children 
1  2 
2  1 
+0

不,它不是非規範化,並且邏輯不能作爲觸發器實現。父母的狀態基本上表明正在發生背景處理。異步地,行被添加到引用父行的另一個表中。但是,如果父行已經處於該狀態,我們不會啓動異步作業。所以我們在父行上使用獨佔鎖定來控制與後臺作業的同步。奇怪的是,應該是安全的序列變成檢測到的僵局。 –

+0

@BarryKelly,很抱歉聽到觸發器不能用於你的情況。這個約束在這個問題中沒有記錄......如果你問「我們沒有考慮其他選擇嗎?」,我認爲解釋你想達到的目標是公平的。 –

+0

我知道。對不起。我把它縮減到了一小部分可重複的步驟。這樣做,我超出了我的應用要求。但是如果我描述了我的應用程序需求,我們會進行更大範圍的討論。 –