1. 程式人生 > >總結線上遇到的MySQL死鎖問題

總結線上遇到的MySQL死鎖問題

線上遇到了MySQL死鎖的相關問題,需要檢視MySQL出現的Deadlock日誌,可以通過執行:
show engine innodb status
  來檢視innodb型別資料庫的狀態,查詢laster detected deadlock部分,可以看到最近造成死鎖的兩條sql
------------------------
LATEST DETECTED DEADLOCK
------------------------
161020 17:58:11
*** (1) TRANSACTION:
TRANSACTION ED354BF4, ACTIVE 0 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 6 lock struct(s), heap size 1248, 3 row lock(s), undo log entries 1
MySQL thread id 2938474, OS thread handle 0x2b9ffd19b940, query id 3121991643 192.168.1.163 apitest140715 Updating
 
UPDATE xxx SET fix_stock=fix_stock+-1 WHERE aaa = 1 AND aaa=101488 AND fix_stock+-1>=0 AND stock>=fix_stock+-1
 
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 196984 page no 743 n bits 1272 index `xxxx` of table `xxx`.`xxxx` trx id ED354BF4 lock_mode X waiting
Record lock, heap no 581 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 
0: len 4; hex 80018c70; asc p;;
1: len 4; hex 80018ce8; asc ;;
 
*** (2) TRANSACTION:
TRANSACTION ED354C8C, ACTIVE 0 sec starting index read
mysql tables in use 1, locked 1
6 lock struct(s), heap size 1248, 4 row lock(s)
MySQL thread id 2938340, OS thread handle 0x2b9ffcae8940, query id 3121991660 192.168.1.115 163test Updating
update xxx
set fix_stock=fix_stock+1
where product_spec_id=101488
and fix_stock+1>=0
and stock>=fix_stock+1
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 196984 page no 743 n bits 1272 index `xxx` of table `shop_zp`.`gt_goods_warehouse_index` trx id ED354C8C lock_mode X
Record lock, heap no 581 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 
0: len 4; hex 80018c70; asc p;;
1: len 4; hex 80018ce8; asc ;;
 
*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 196974 page no 2114 n bits 176 index `PRIMARY` of table `xxxx`.`xxxx` trx id ED354C8C lock_mode X locks rec but not gap waiting
Record lock, heap no 85 PHYSICAL RECORD: n_fields 35; compact format; info bits 0
0: len 4; hex 00018c70; asc p;;
1: len 6; hex 0000ed354bf4; asc 5K ;;
2: len 7; hex 7600011487203d; asc v =;;
3: len 1; hex 00; asc ;;
4: len 4; hex 80000000; asc ;;
5: len 1; hex 00; asc ;;
6: len 0; hex ; asc ;;
7: len 12; hex 373030323335343037303836; asc 700235407086;;
8: len 0; hex ; asc ;;
9: len 4; hex 0010225f; asc "_;;
10: len 4; hex 00000338; asc 8;;
 
11: len 4; hex 800186a0; asc ;;
12: len 4; hex 80000056; asc V;;
13: len 4; hex 80000000; asc ;;
14: len 4; hex 80000000; asc ;;
15: len 9; hex 800000000000000577; asc w;;
16: len 5; hex 8000000000; asc ;;
17: len 5; hex 8000000000; asc ;;
18: len 1; hex 81; asc ;;
19: len 9; hex 800000000000000af0; asc ;;
20: len 1; hex 80; asc ;;
21: len 4; hex 0000011b; asc ;;
22: len 4; hex 000000e0; asc ;;
23: len 4; hex 80000000; asc ;;
24: len 4; hex 80000000; asc ;;
25: len 1; hex 81; asc ;;
26: len 4; hex d5684647; asc hFG;;
27: len 4; hex 58089533; asc X 3;;
28: len 0; hex ; asc ;;
 
29: len 5; hex 800002e505; asc ;;
30: len 5; hex 8000036303; asc c ;;
31: len 4; hex 000f4240; asc 
[email protected]
;; 32: len 4; hex 80000000; asc ;; 33: len 4; hex 0000803f; asc ?;; 34: SQL NULL; *** WE ROLL BACK TRANSACTION (2) ------------ TRANSACTIONS ------------
 

MySQL鎖機制

相對其他資料庫而言,MySQL的鎖機制比較簡單,其最顯著的特點是不同的儲存引擎支援不同的鎖機制。比如,MyISAM和MEMORY儲存引擎採用的是表級鎖(table-level locking);BDB儲存引擎採用的是頁面鎖(page-level locking),但也支援表級鎖;InnoDB儲存引擎既支援行級鎖(row-level locking),也支援表級鎖,但預設情況下是採用行級鎖。 MySQL這3種鎖的相關指標可以參考:開銷、加鎖速度、死鎖、粒度、併發效能:
  • 表級鎖:開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖衝突的概率最高,併發度最低。
  • 行級鎖:開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖衝突的概率最低,併發度也最高。
  • 頁面鎖:開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定粒度界於表鎖和行鎖之間,併發度一般。
從上述特點可見,很難籠統地說哪種鎖更好,只能就具體應用的特點來說哪種鎖更合適!僅從鎖的角度來說:表級鎖更適合於以查詢為主,只有少量按索引條件更新資料的應用,如Web應用;而行級鎖則更適合於有大量按索引條件併發更新少量不同資料,同時又有併發查詢的應用,如一些線上事務處理(OLTP)系統。這一點在本書的“開發篇”介紹表型別的選擇時,也曾提到過。下面幾節我們重點介紹MySQL表鎖和 InnoDB行鎖的問題,由於BDB已經被InnoDB取代,即將成為歷史,在此就不做進一步的討論了。 我們當前使用的是InnoDB,與MyISAM的最大不同有兩點:一是支援事務,二是採用了行級鎖,行級鎖與表級鎖有很多不同之處。 引入資料庫的事務支援之後,相對於序列處理來說,併發事務處理能夠大大增加資料庫資源的利用率,提高資料庫系統的事務吞吐量,支援更多使用者,但同時併發使用者也同時帶來一些問題:
  • 更新丟失(Lost Update):當兩個或多個事務選擇同一行,然後基於最初選定的值更新該行時,由於每個事務都不知道其他事務的存在,就會發生丟失更新問題--最後的更新覆蓋了由其他事務所做的更新。例如,兩個編輯人員製作了同一文件的電子副本。每個編輯人員獨立地更改其副本,然後儲存更改後的副本,這樣就覆蓋了原始文件。最後儲存其更改副本的編輯人員覆蓋另一個編輯人員所做的更改。如果在一個編輯人員完成並提交事務之前,另一個編輯人員不能訪問同一檔案,則可避免此問題。
  • 髒讀(Dirty Reads):一個事務正在對一條記錄做修改,在這個事務完成並提交前,這條記錄的資料就處於不一致狀態;這時,另一個事務也來讀取同一條記錄,如果不加控制,第二個事務讀取了這些“髒”資料,並據此做進一步的處理,就會產生未提交的資料依賴關係。這種現象被形象地叫做"髒讀"。
  • 不可重複讀(Non-Repeatable Reads):一個事務在讀取某些資料後的某個時間,再次讀取以前讀過的資料,卻發現其讀出的資料已經發生了改變、或某些記錄已經被刪除了!這種現象就叫做“不可重複讀”。
  • 幻讀(Phantom Reads):一個事務按相同的查詢條件重新讀取以前檢索過的資料,卻發現其他事務插入了滿足其查詢條件的新資料,這種現象就稱為“幻讀”。
防止上述問題有些並不是通過資料庫事務控制器來解決,例如“更新丟失”,需要應用程式對要更新的資料加上必要的鎖來解決,這些應該是應用的責任,其他三種其實都是資料庫一致性問題,必須由資料庫提供一定的事務隔離機制,資料庫實現事務隔離的方式基本上可以分成以下兩種:
  • 讀取資料前,對其加鎖,阻止其他事務對資料進行修改;
  • 不用加任何鎖,通過一定機制生成一個數據請求時間點的一致性資料快照,並用這個快照來提供一定級別,也成為多版本併發控制。
資料庫的事務隔離越是嚴格,併發副作用就越小,付出的代價就越大,因為事務隔離實質上就是使得事務在一定程度上”序列化“進行,與併發相矛盾的。 為了解決隔離與併發之間的矛盾,ISO/ANSI SQL92定義了4個事務隔離級別,每個級別的隔離程度不同,允許出現的副作用也就不同,應用可以根據自己的業務邏輯要求選擇不同的隔離級別來平衡隔離與併發之間的矛盾。
讀資料一致性 髒讀 不可重複讀 幻讀
未提交讀 最低級別
已提交讀 語句級
可重複讀 事務級
可序列化 最高級別,事務級
注意:各個具體資料庫並不一定完全實現上述4個隔離級別。 InnoDB的行鎖模式以及加鎖方法 共享鎖:允許一個事務去讀一行,阻止其他事務獲得相同資料集的排他鎖; 排他鎖:允許獲得排它鎖的事務更新資料,阻止其他事務取得相同資料集的共享讀鎖和排他寫鎖; 意向共享鎖:事務打算給資料行加行共享鎖,事務在給一個數據行加共享鎖前必須先取得該表的意向共享鎖; 意向排他鎖:事務打算給資料行加行排他鎖,事務在給一個數據行加排它鎖前必須先取得該表的意向排他鎖。

  意向鎖是InnoDB自動加的,不需要使用者干預,對於update, insert, delete語句InnoDB會自動給涉及資料集加排他鎖,對於普通select語句InnoDB不會加任何鎖,事務可以通過以下語句顯式地給記錄集加上共享鎖和排他鎖。
select * from table_name where … lock in share mode; //共享鎖
select * from table_name where … for update; //排它鎖
  InnoDB行鎖是通過給索引上的索引項加鎖來實現的,這一點與Oracle不同,後者是通過在資料塊中對相應資料行加鎖實現的,InnoDB這種行鎖實現特點意味著:只有通過索引條件檢索資料,InnoDB才使用行級鎖,否則將使用表鎖! 由於MySQL的行鎖是針對索引加的鎖,不是針對記錄加的鎖,雖然訪問不同行的記錄,但如果使用相同的索引鍵是會出現鎖衝突的。 當表有多個索引的時候,不同的事務可以使用不同的索引鎖定不同的行,不論是使用主鍵索引、唯一鍵索引或是普通索引,InnoDB都會使用行鎖來對資料加鎖。 但有一種特殊情況,即便在條件中使用了索引欄位,是否使用索引來檢索資料是由MySQL通過判斷不同的執行計劃的代價來決定的,如果MySQL認為全表掃描效率更高,比如對一些很小的表,它就不會使用索引,這種情況將會使用表鎖而不是行鎖。因此在分析鎖衝突時,需要檢查SQL執行計劃以確認是否真正使用了索引。 對於InnoDB表,在絕大多數情況下都應該使用行級鎖,因為事務和行鎖往往是我們之所以選擇InnoDB表的理由,但個別特殊事務中,也可以考慮使用表級鎖:
  • 當事務需要更新大部分或全部資料,表又比較大,如果使用預設的行鎖,不僅該事務執行效率低,而且可能造成其他事務長時間鎖等待和鎖衝突;
  • 事務涉及到多個表,比較複雜,很可能引起死鎖,造成大量事務回滾,這種情況也可以考慮一次性鎖定事務涉及表,從而避免死鎖,減少資料庫因事務回滾帶來的開銷。
MyISAM表鎖是deadlock free的,因為MyISAM總是一次獲得所需的全部鎖,要麼全部滿足要麼等待,因此不會出現死鎖,但在InnoDB中,除了單個SQL組成事務外,鎖是逐步獲得的,這就決定了在InnoDB中發生死鎖是有可能的。 發生死鎖後,InnoDB一般都能夠自動檢測到,並使一個事務釋放鎖並回退,另一個事務獲得鎖繼續完成事務。但在涉及外部鎖或涉及表鎖的情況下,InnoDB並不能完全檢測到死鎖,這需要通過設定鎖等待超時引數innodb_lock_wait_timeout來解決。需要說明的是,這個引數並不是只用來解決死鎖問題,在併發訪問比較高的情況下,如果大量事務因無法立即獲得所需的鎖而掛起,會佔用大量計算機資源,造成嚴重效能問題,甚至拖跨資料庫。我們通過設定合適的鎖等待超時閾值,可以避免這種情況發生。 通常來說,死鎖都是應用設計的問題,通過調整業務流程、資料庫物件設計、事務大小,以及訪問資料庫的SQL語句,絕大部分死鎖都可以避免。下面就通過例項來介紹幾種避免死鎖的常用方法。