前言:整理歸納,僅供個人溫習之用,請支持正版極客時間
1、行鎖概念
*MySQL 的行鎖是在引擎層由各個引擎自己實現的,並不是所有的引擎都支持行鎖(比如 MyISAM 引擎)。不支持行鎖意味着併發控制只能使用表鎖,對於這種引擎的表,同一張表上任何時刻只能有一個更新在執行,這就會影響到業務併發度。
*行鎖就是針對數據表中行記錄的鎖。比如事務 A 更新了一行,而這時候事務 B 也要更新同一行,則必須等事務 A 的操作完成後才能進行更新。
2、兩階段鎖
*兩階段鎖協議:在 InnoDB 事務中,行鎖在需要的時候加上的,在事務結束時釋放。
*問題:在下面的操作序列中,事務 B 的 update 語句執行時會是什麼現象呢?假設字段 id 是表 t 的主鍵。
答:事務 B 的 update 語句會被阻塞,直到事務 A 執行 commit 之後,事務 B 才能繼續執行。
*意義:事務中需要鎖多個行,要把最可能造成鎖衝突、最可能影響併發度的鎖儘量往後放
3、死鎖和死鎖檢測
*死鎖:併發系統中不同線程出現循環資源依賴,涉及的線程都在等待別的線程釋放資源時,就會導致這幾個線程都進入無限等待的狀態
*死鎖解決策略:
-
直接進入等待,直到超時。這個超時時間可以通過參數 innodb_lock_wait_timeout (默認50S)來設置
-
發起死鎖檢測,發現死鎖後,主動回滾死鎖鏈條中的某一個事務,讓其他事務得以繼續執行。將參數 innodb_deadlock_detect (默認值爲on)設置爲 on,表示開啓這個邏輯。
Ps:innodb_lock_wait_timeout時間不能設置太短,比如1s,如果不是死鎖而是鎖等待,容易誤傷;
主動死鎖檢測在發生死鎖的時候,是能夠快速發現並進行處理的,但是它也是有額外負擔的。
*問題:所有事務都要更新同一行會發生什麼?
每個新來的被堵住的線程,都要判斷會不會由於自己的加入導致了死鎖,這是一個時間複雜度是 O(n) 的操作。假設有 1000 個併發線程要同時更新同一行,那麼死鎖檢測操作就是 100 萬這個量級的。雖然最終檢測的結果是沒有死鎖,但是這期間要消耗大量的 CPU 資源。
怎麼解決由這種熱點行更新導致的性能問題?
答:1、如果能確保這個業務一定不會出現死鎖,可以臨時把死鎖檢測關掉。這種操作帶有一定的風險,因爲業務設計的時候一般不會把死鎖當做一個嚴重錯誤,畢竟出現死鎖了,就回滾,然後通過業務重試一般就沒問題了,這是業務無損的。而關掉死鎖檢測意味着可能會出現大量的超時,這是業務有損的。
2、控制併發度。併發控制要做在數據庫服務端。如果有中間件,可以考慮在中間件實現;如果團隊有能修改 MySQL 源碼的人,也可以做在 MySQL 裏面。基本思路就是,對於相同行的更新,在進入引擎之前排隊。這樣在 InnoDB 內部就不會有大量的死鎖檢測工作了。
3、設計優化:通過將一行改成邏輯上的多行來減少鎖衝突。還是以影院賬戶爲例,可以考慮放在多條記錄上,比如 10 個記錄,影院的賬戶總額等於這 10 個記錄的值的總和。這樣每次要給影院賬戶加金額的時候,隨機選其中一條記錄來加。這樣每次衝突概率變成原來的 1/10,可以減少鎖等待個數,也就減少了死鎖檢測的 CPU 消耗。
4、總結
*並不是每條事務執行前都會檢測死鎖,只有當所訪問的行上有鎖纔會檢測。
*一致性讀不會加鎖,不需要做死鎖檢測
*並不是每次死鎖檢測都都要掃所有事務。比如某個時刻,事務等待狀態是這樣的:B在等A,D在等C,現在來了一個E,發現E需要等D,那麼E就判斷跟D、C是否會形成死鎖,這個檢測不用管B和A
*如果有多種鎖,必須得“全部不互斥”才能並行,只要有一個互斥,就得等
*innodb行級鎖是通過鎖索引記錄實現的,如果更新的列沒建索引是會鎖住整個表的。
*在myisam 表上更新一行,那麼會加MDL讀鎖和表的寫鎖(先查詢後更新);然後同時另外一個線程要更新這個表上另外一行,也要加MDL讀鎖和表寫鎖。第二個線程的MDL讀鎖是能成功加上的,但是被表寫鎖堵住了。從語句現象上看,就是第二個線程要等第一個線程執行完成。
*如果你要刪除一個表裏面的前 10000 行數據,有以下三種方法可以做到:
第一種,直接執行 delete from T limit 10000;
第二種,在一個連接中循環執行 20 次 delete from T limit 500;
第三種,在 20 個連接中同時執行 delete from T limit 500。
你會選擇哪一種方法呢?爲什麼呢?
-
第一種方式(即:直接執行 delete from T limit 10000)裏面,單個語句佔用時間長,鎖的時間也比較長;而且大事務還會導致主從延遲。
-
第二種方式(即:在一個連接中循環執行 20 次 delete from T limit 500)比較好
-
第三種方式(即:在 20 個連接中同時執行 delete from T limit 500),會人爲造成鎖衝突。