正在奮力寫"BUG"中,同事說出現死鎖了,讓幫忙看一下,so 放下手中工做,去瞅瞅, 一段很簡單的代碼,就是mysql數據庫下使用多線程先刪除數據再插入數據,形成了死鎖html
順便騙個贊,以爲寫的還能夠的,不要吝嗇你的贊喲mysql
第一張圖,同事採用發令槍,多線程批量處理一批數據算法
第二張圖是批量處理數據的邏輯, 在一個事務中,先刪除一條數據而後再插入一批數據sql
堆棧異常: Deadlock found when trying to get lock; try restarting transaction; 意思很明瞭,就是嘗試獲取鎖時發現死鎖.數據庫
這段代碼作刪除時,使用的索引列,憑直觀理解,這應該只是個行級鎖, 不該該會出現死鎖的,看代碼很清楚的理解到,每一個線程持有一個事務操做,若是是行級鎖確定不會出現死鎖,因此刪除的時候確定不止鎖了一條數據安全
在生產環境中mysql的存儲引擎絕大部分是 InnoDB ,爲何使用 InnoDB 呢? 點擊查看詳細答案, 就憑事務安全這一條,相信足以讓選擇InnoDb了。多線程
代碼中delete操做的是非惟一索引列在innoDB引擎下會觸發 next-key lock(間隙鎖)。併發
舉例: 表t中有非惟一索引列 test_id爲 1, 10, 18, 22, 26的5條數據,此時模擬操做:spa
事務A 刪除一條不存在的數據,數據庫就會去找從左開始找最近的索引值線程
delete from t where test_id= 27;
事務B 刪除一條不存在的數據,數據庫就會去找從左開始找最近的索引值
delete from t where test_id= 28;
此時事務A和B就會分別產生一個(26,正無窮)間隙鎖,而後繼續操做
事務A
INSERT INTO t VALUES(27);
此時事務A阻塞,由於事務B在刪除操做時擁有了區間鎖
事務B
INSERT INTO t VALUES(28);
此時事務B就會死鎖,由於事務A在刪除操做時擁有了區間鎖
一、刪除時先判斷數據是否存在
二、刪除和插入分兩個事務處理
三、將事務隔離級別設置爲讀已提交
關於InndDb的Lock機制解釋此問題,詳見下章
共享和排他(獨佔)鎖
共享鎖容許持有鎖的事務讀取一行數據
排他(獨佔)鎖 容許持有鎖的事務更新或者刪除一行數據
意圖鎖
innodb支持多重粒度鎖,即行鎖和表鎖共存
意圖共享鎖(IS)代表一個事務對錶中個別數據設置一個共享鎖
意圖排他鎖(IX)代表一個事務對錶中個別數據設置一個排他鎖
舉例:
SELECT ... LOCK IN SHARE MODE
將會對查詢的表設置意圖共享(IS)鎖, SELECT ... FOR UPDATE
將會對查詢的表設置意圖排他鎖
記錄鎖
記錄鎖是一個索引記錄的鎖。索引記錄不必定只是一條數據喲。
示例: SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE
會對錶t中c1=10的記錄們(此處能夠是多條數據)加記錄鎖,以避免其餘事務對c1=10的數據們作插入、更新、刪除操做。即便表t沒有索引,InnoDb也會建立一個隱藏的索引羣組。
Gap Locks(區間鎖)
區間鎖是一種索引記錄之間或者某索引記錄以前或者 某索引記錄以後的鎖。區間鎖能夠只跨越一個索引值,也能夠是多個索引值,也能夠只是個空區間。當事務隔離級別爲 讀已提交或者啓用配置 innodb_locks_unsafe_for_binlog
(此配置現已禁用)時,在搜索或者索引掃描是 區間鎖是禁用的。
舉例0: SELECT c1 FROM t WHERE c1 BETWEEN 10 and 20 FOR UPDATE;
阻止其餘事務插入t.c1=15這樣的值,不管是否有這樣的記錄,由於鎖定了t.c1=10到t.c1=20的區間。
舉例1:
SELECT * FROM child WHERE id = 100;
若是id是一個惟一索引列,以上語句只會產生一個id=100 的鎖,若是id沒有索引或者不是惟一索引,這條語句將會鎖定 100之前的區間
Next-Key Locks (間隙鎖)
間隙鎖是一種記錄鎖和區間(索引以前)鎖的組合鎖。本文例子的問題根源就是這個
在搜索或者掃描索引時,InnoDb經過這種方式提供了行級鎖,它在找到的索引記錄上設置共享鎖或者排他鎖。行級鎖就是索引記錄鎖。一個索引記錄的間隙鎖也會是個索引以前的區間鎖。若是一個會話在索引中有共享或者排他鎖,其餘會話就不能在這個索引順序以前的區間插入新的索引記錄。
舉例:加入一個索引包含了10,11,13,20 。間隙鎖包含如下場景:
(負無窮, 10] (10, 11] (11, 13] (13, 20] (20, 正無窮)
默認狀況下,InnoDb在可重複讀 事務隔離級別下使用此鎖,經過 SHOW ENGINE INNODB STATUS
命令能夠查看事務處理數據的間隙鎖狀態。
插入意圖鎖
插入意圖鎖是在執行插入操做時的一種區間鎖。此鎖是爲了多個事務在同一個索引區間鎖之間插入時沒必要等待。
示例:
若是有兩個索引記錄爲90和120
在客戶端A 開啓事務執行命令
SELECT * FROM child WHERE id > 100 FOR UPDATE;
此語句會產生一個在120以前的區間鎖
在客戶端B 開啓事務執行命令
INSERT INTO child (id) VALUES (101);
此條命令在120以前的區間鎖之間插入一條數據,該事務在獲取排他鎖的同時,產生一個插入意圖鎖
自增鎖
自增鎖是一個事務在自增列中插入數據時產生的一種特殊的表級鎖。簡單的狀況下,一個事務在表裏插入數據,其餘事務都必須等待往表裏插本身的數據, 因此第一個事務接收連續的主鍵值插入到數據行。
innodb_autoinc_lock_mode
配置項能夠控制自增鎖的算法,它容許在作插入操做時如何權衡自動增量值序列和插入操做的最大併發性。點擊查看此配置詳細信息
在查詢、更新、刪除語句執行過程當中一般會對每一個掃描過的索引設置記錄鎖。在where語句中是否含有排除數據的條件並不重要,InnoDb不會記錄具體的where條件,只知道掃描了哪些索引。這鎖一般是間隙鎖( next-key locks ),它會阻塞在記錄以前的區間插入新數據,然而區間鎖是能夠被明確的禁用,這將使間隙鎖失效。事務隔離級別也會影響鎖。
若是二級索引被用於搜索和索引記錄就會設置一個排他鎖,InnoDB也會檢索相應的羣集索引記錄,並對其設置鎖。
若是sql語句沒有合適的索引,mysql就會整張表,表的每一行都會被鎖,就會阻塞其餘用戶往此表插入數據,因此創建一個適當的索引很重要。
InnoDb引擎中不一樣sql語句設置鎖以下:
SELECT ... FROM
一個讀語句,讀取數據庫的快照過程當中不會設置鎖,除非將事務隔離級別設置爲SERIALIZABLE。SELECT ... FOR UPDATE
或者 SELECT ... LOCK IN SHARE MODE
, 會爲每一個掃描到的行設置鎖,而且爲不符合條件的數據釋放鎖,有時候,不會馬上釋放鎖,由於在查詢的時候丟失告終果數據和源數據的關聯,好比在一個 Union語句中,從一張表中掃描(和鎖定)行被插入到一個臨時表中,而後再評估它們是否符合結果集。 在這種狀況下,在臨時表中的行與原始表中的行之間的關係丟失,然後者直到查詢執行結束時纔會解鎖。SELECT ... LOCK IN SHARE MODE
語句在全部搜索到的索引設置共享間隙鎖,然而,經過一個惟一索引搜索到惟一行就會產生一個索引記錄鎖。SELECT ... FOR UPDATE
, 語句在全部搜索到的索引設置排他(獨佔)間隙鎖 。然而,經過一個惟一索引搜索到惟一行就會產生一個索引記錄鎖。 對於每一個搜索到的索引, SELECT ... FOR UPDATE
阻塞了 SELECT ... LOCK IN SHARE MODE
或者在某一事務隔離級別讀的其餘會話,通常讀語句都會忽略讀視圖中存在記錄的任何鎖。
DELETE FROM ... WHERE ...
和 UPDATE ... WHERE ...
在搜索遇到的每條記錄上設置一個獨佔的間隙鎖。 然而,經過一個惟一索引搜索到惟一行就會產生一個索引記錄鎖。INSERT
語句對插入的行設置排他(獨佔)鎖。這個鎖是一個索引記錄鎖,而不是間隙鎖,而且不會阻止其餘會話在插入的行以前區間中插入數據。順便騙個贊,以爲寫的還能夠的,不要吝嗇你的贊喲