MySQL 三種鎖
全局鎖
Flush tables with read lock (FTWRL) 對整個數據庫加鎖,整個庫處於只讀狀態,之后的其他線程例如DML,DDL,TCL等語句將會被阻塞。
全局鎖的使用場景是做全庫邏輯備份,但讓整庫處於只讀狀態,會導致兩個問題。
- 如果你在主庫上備份,那么在備份期間都不能執行更新,業務基本上就得停擺
- 如果你在從庫上備份,那么備份期間從庫不能執行主庫同步過來的 binlog,會導致主從延遲
MySQL官方使用mysql dump -single-transaction,導數據之前會啟動一個事務,來確保拿到一致性視圖,不支持事務隔離的引擎如MyISAM,只能通過 FTWRL.
全庫只讀不使用set global readonly=true的原因:
- 在有些系統中,readonly 的值會被用來做其他邏輯,比如用來判斷一個庫是主庫還是備庫。
- 在異常處理機制上有差異。執行 FTWRL 命令之后由於客戶端發生異常斷開,那么 MySQL 會自動釋放這個全局鎖,整個庫回到可以正常更新的狀態。將整個庫設置為 readonly 之后,如果客戶端發生異常,則數據庫就會一直保持 readonly 狀態,這樣會導致整個庫長時間處於不可寫狀態,風險較高。
表級鎖
1.表鎖
上鎖:lock tables ...read/write 解鎖:unlock tables。
unlock tables可以主動釋放鎖,也可以在客戶端斷開的時候自動釋放鎖。
lock tables,語法除了會限制線程的讀寫外,還會限定該線程接下來的操作對象。
2.元數據鎖(meta data lock)
不需要顯式使用,在訪問表時會自動加上,MDL的作用是保證讀寫的正確性。
當對一張表進行DML操作時會加MDL讀鎖,對表結構做操作時會加DML寫鎖。
- 讀鎖之間不互斥,可以多個線程同時對一張表進行增刪改查
- 讀寫鎖,寫鎖之間相互互斥,用來保證表結構操作的安全性。
事務中的MDL鎖,在語句開始執行時申請,但是語句結束后並不會馬上釋放,而是等整個事務提交后釋放。
如何安全的給小標加字段?
alter table 語句里面設定等待時間,如果在這個指定的等待時間里面能夠拿到 MDL 寫鎖最好,拿不到也不要阻塞后面的業務語句,先放棄。之后開發人員或者 DBA 再通過重試命令重復這個過程。
3.行鎖
MySQL行鎖在引擎層,由各個引擎自己實現,MyIsAM不支持行鎖。行鎖就是在表中行記錄的鎖。
兩階段鎖
在 InnoDB 事務中,行鎖是在需要的時候才加上的,但並不是不需要了就立刻釋放,而是要等到事務結束時才釋放。這個就是兩階段鎖協議。兩階段鎖,鎖的添加與釋放分到兩個階段進行,之間不允許交叉加鎖和釋放鎖。 也就是在事務開始執行后為涉及到的行按照需要加鎖,但執行完不會馬上釋放,而是在事務結束時再統一釋放他們。
如果你的事務中需要鎖多個行,要把最可能造成鎖沖突、最可能影響並發度的鎖盡量往后放
死鎖和死鎖檢測
當並發系統中不同線程出現循環資源依賴,涉及的線程都在等待別的線程釋放資源時,就會導致這幾個線程都進入無限等待的狀態,稱為死鎖。
出現死鎖后的策略
- 直接進入等待,直到超時。這個超時時間可以通過參數 innodb_lock_wait_timeout 來設置。
- 發起死鎖檢測,發現死鎖后,主動回滾死鎖鏈條中的某一個事務,讓其他事務得以繼續執行。將參數 innodb_deadlock_detect 設置為 on,表示開啟這個邏輯。
死鎖檢測的條件:如果要加鎖訪問 的行上 有鎖,他才要進行死鎖檢測。
- 一致性讀不會加鎖,不會進行死鎖檢測
- 並不是每次死鎖檢測都會掃描所有事務,比如現在有B在等A,D在等C,現在來了一個E,發現E需要等D,那么E就判斷跟D、C是否會形成死鎖,這個檢測不用管B和A
如果是我們上面說到的所有事務都要更新同一行的場景呢?
每個新來的被堵住的線程,都要判斷會不會由於自己的加入導致了死鎖,這是一個時間復雜度O(n^2)
怎么解決由這種熱點行更新導致的性能問題呢
- 如果你能確保這個業務一定不會出現死鎖,可以臨時把死鎖檢測關掉。風險太高,關掉死鎖檢測意味着可能會出現大量的超時,這是業務有損的
- 控制並發度:在數據庫服務器端做並發控制。如果有中間件,可以在中間件實現;如果可以修改MySQL源碼,也可以做在MySQL里面,對於相同行的更新,在進入引擎之前排隊。這樣在 InnoDB 內部就不會有大量的死鎖檢測工作了。
- 從邏輯上將一行拆分為多行,