Mysql心路歷程:Mysql各類鎖機制(入門篇)

這一篇文章是本人數據庫的第二篇,也是對數據庫學習的階段性總結。對於數據庫鎖的瞭解,是區分程序員,尤爲是Java程序員,中高級的一個重要標誌。也是平常,咱們開發中,常常碰到坑的地方。每每,咱們無腦的CURD過程當中,其實已經出現問題了,鎖問題,可是咱們並無發現,你那是沒有被大訪問量衝擊。一旦一朝咱們衝擊到了,那損失和鍋,是要本身承擔下來的。很少說,咱們接下來就來一步步看看Mysql的鎖機制。程序員

1、Mysql鎖的類型

總體上,Mysql鎖的類型,從全局的到細節的,包含以下幾個:sql

  • 全局鎖
  • 表級鎖
    • 普通的表鎖
    • MDL (元數據鎖)
  • 行鎖
    • 讀鎖(共享鎖)
    • 寫鎖(排他鎖、叉鎖)
  • 間隙鎖
    • Next-key lock

大概上,咱們平常生產學習中,所能接觸到的就這幾大種類了(我的的腦容量也就能掌握這麼多了,(⊙﹏⊙)b),接下來,咱們一個個的說說。數據庫

2、 全局鎖

顧名思義,全局鎖就是對整個數據庫實例加鎖。Mysql提供了一個加鎖的語句:Flush tables with read lock (FTWRL)。它能使整個實例上面,只讀,全部的寫和更新,都會被阻塞。全局鎖的使用經典的使用場景是作全局的數據備份使用,具體的操做,可能平時咱們碰到很少,不過要了解幾點:安全

  • 每次全局備份過程當中,若是是InnoDB引擎徹底能夠經過MVCC建立一致性視圖,來保證不受備份中,其餘操做的影響,問題是不必定全部的數據引擎都是InnoDB
  • 咱們一樣能夠經過set global readonly=true 來進行只讀性的設置,可是和FTWRL的區別以下:
    • 有些數據庫把第一種模式用做,設置成備庫的只讀限制,而不是用來作備份的,影響面比較大
    • 異常處理機制不同:FTWRL這種機制,若是設置以後,客戶端異常斷開鏈接了,數據庫會主動釋放全局的鎖,恢復正常;而set這種方式,客戶端異常斷開了,就不會恢復原狀,數據庫會一直只讀,影響很大。
  • 具體對數據庫的全局鎖,不只僅阻塞增刪改操做(DML),對數據庫表的增刪改字段(DDL)也會被阻塞

3、表級鎖

咱們首先要知道的是,每次進行select操做或者DML的時候,對錶加的都是MDL的讀鎖,而進行DDL的時候,對錶加的是MDL的寫鎖,讓咱們首先來個印象。接下來來看看普通的表鎖與MDL(元數據鎖meta data lock)的區別。session

一、普通的表鎖

普通的表鎖也是分讀鎖與寫鎖,數據庫提供語句操做:lock tables … read/write,使用unlock tables進行釋放鎖。具體注意的點是:加了普通的表鎖以後,對當前加鎖線程接下來的數據庫操做,都是有影響的。併發

舉個例子:若是A線程使用語句lock tables t1 read, t2 write; 這個語句,那麼,其餘線程寫t1和讀寫t2都會被阻塞;同時線程A再進行unlock以前,也只能讀t1和讀寫t2,連寫t1都是不被容許的。天然也不能訪問其餘的表高併發

在沒有出現行鎖以前,都是經過表鎖進行併發控制的,上面例子可見,影響面仍是太大,限制太嚴格了。學習

二、元數據鎖(MDL)

MDL不須要主動加鎖,每當咱們訪問一個數據表的時候,會自動被加上,做用是防止在咱們進行表的操做的時候,進行了表結構的變動。再5.5這個版本中被引入了Mysql中:線程

  • 當對一個表進行增刪改查的時候,加MDL的讀鎖
  • 當進行一個表的結構變動的時候,加MDL的寫鎖

讀寫鎖的MDL之間的互斥關係是:orm

  • 讀鎖與讀鎖不互斥
  • 讀鎖與寫鎖互斥
  • 寫鎖與寫鎖互斥

具體有個經典的例子:常常發生的是,咱們給一個表加了個一個字段或者幾個字段,很當心了,可是加的過程當中,直接整個表掛了,接下來的操做都失敗了或者不返回。接下來咱們就看看具體的操做過程:

sessionA sessionB sessionC sessionD
begin;
select * from t limit1
select * from t limit1
alter table t add f int (block)
select * from t limit1 (block)

可見,咱們sessionC操做以後,因爲sessionA是沒有結束事務的,咱們MDL會隨着事務的開啓而加鎖,事務的結束而釋放鎖,因此,sessionA這時候保持住了MDL的讀鎖。而後sessionC想要獲取MDL的寫的時候,因爲讀寫互斥,sessionC就被阻塞了。接下來的語句,也都執行不了了,由於接下倆的語句要申請MDL的讀鎖,而有寫鎖已經在阻塞狀態,讀鎖又要排隊等這個寫鎖執行釋放,那接下來的現象可想而知。

咱們如何安全的對一個表進行加字段的操做呢:

  • 在information_schema庫裏面的innodb_tx表中,能夠查到當前正在執行的事務,若是是一個長事務,咱們能夠先考慮kill掉這個事務
  • 若是是頻繁訪問的斷事務比較多的狀況,咱們可使用alter table tablename wait N add col這種類型的操做,若是拿不到MDL寫鎖,一段時間會釋放阻塞,不長期影響數據庫。

4、行鎖

這個過程比較複雜,首先,咱們來看看,Mysql加行鎖,是使用兩階段加鎖策略的,咱們看看什麼叫作兩階段加鎖:

兩階段鎖協議,整個事務分爲兩個階段,前一個階段爲加鎖,後一個階段爲解鎖。在加鎖階段,事務只能加鎖,也能夠操做數據,但不能解鎖,直到事務釋放第一個鎖,就進入解鎖階段,此過程當中事務只能解鎖,也能夠操做數據,不能再加鎖。兩階段鎖協議使得事務具備較高的併發度,由於解鎖沒必要發生在事務結尾。它的不足是沒有解決死鎖的問題,由於它在加鎖階段沒有順序要求。如兩個事務分別申請了A, B鎖,接着又申請對方的鎖,此時進入死鎖狀態。

一、何時加行鎖

正常,咱們select語句時候,是不會添加行鎖的,只會加上MDL的讀鎖,即便這條語句是全表掃描,也不會加行鎖,只不過全表掃描,查詢較慢罷了,並不會由於鎖的問題而對其餘操做進行阻塞。下面是我總結的一些加行鎖的場景:

  • select * from t where id = 1 in share model 對主鍵爲1的這一行,加行鎖,共享鎖
  • select * from t where id = 1 for update 對主鍵爲1的這一行,加行鎖,排它鎖,叉鎖
  • update t set col1 = 1 where id =1 對主鍵爲1的這一行加行鎖,排它鎖,叉鎖
  • update t set col1 = 1 where col2 =1 若是col2沒有索引,那麼是加普通表鎖;若是col2是非惟一索引,對全部col2爲1的行,加行鎖;若是col2是惟一索引,對col2爲1的這一行,加行鎖。行鎖都是排它鎖

p.s.:固然上面全部所列取的操做,都是首先加了MDL的讀鎖的

二、加行鎖的影響

行鎖,之因此存在,就是提升併發度的。取代之前,咱們要整表進行加鎖,而引發同一時刻,只能有一個線程對數據表進行增刪改的操做,下面咱們看一個具體的數據庫操做:

事務A 事務B
begin;
update t set k = k+1 where id = 1;
update t set k = k+2 where id = 2;
begin;
update t set k = k+3 where id = 1;
commit;
  • 事務B的update會被阻塞,由於id爲1的這行行鎖被事務A所持有
  • begin的時候,沒有任何行鎖被持有,只有當具體操做進行是,依次請求MDL的讀鎖,這一行的排它行鎖
  • 全部,當前事務持有的行鎖,語句執行完都不會釋放,知道commit以後才釋放

因此,按照這種邏輯,越是併發度高的數據表,越要靠事務的後面寫,由於持有行鎖時間短,影響併發度的時間越短。

三、這裏咱們引出死鎖

首先咱們看接下來的這個模擬操做:

事務A 事務B
begin;
update t set k = k+1 where id = 1; begin;
update t set k = k+3 where id = 2;
update t set k = k+2 where id = 2;
update t set k = k+3 where id = 1;

這就是一個經典的死鎖場景,咱們來分析下:

  • 事務A的update t set k = k+1 where id = 1;獲取了id爲1這一行的行鎖(排它鎖)

  • 事務B的update t set k = k+3 where id = 2;獲取了id爲2這一行的行鎖

  • 事務A的update t set k = k+2 where id = 2;要獲取id爲2的行鎖,而獲取不到,阻塞

  • 事務B的update t set k = k+3 where id = 1;要獲取id爲1的行鎖,獲取不到,阻塞

對於這種,Mysql有兩種機制進行處理:

  • innodb_lock_wait_timeout能夠經過這個參數,進行設置鎖等待時間,超過這個時間,阻塞的進程釋放全部持有的鎖,回滾。
  • innodb_deadlock_detect經過設置爲on,能主動監測死鎖,經過回滾死鎖聯調中的一個事物,來解決死鎖

第一種狀況雖然能控制,死鎖,可是時間很差設置,例如咱們設置一個10s,若是一個線程被鎖住,要等待10s才能進行回滾,併發度天然不高,若是我設置低了,1s,那麼一個正常等待的,並不是死鎖,也會被回滾。如此一來得不償失。下面重點說說主動死鎖檢測

四、主動死鎖檢測

每當吧innodb_deadlock_detect設置成on,MySQL會主動檢測死鎖:

  • 一個線程加入
  • 即將被等待其餘線程的鎖而堵住
  • 判斷當前線程持有的鎖,是否堵住了其餘系統中正在運行的線程
  • 若是是,將回滾當前線程的事務

看起來很好,而後會有代價:每次對比是否當前線程堵住了其餘線程這一步,會對比全部系統正在執行的線程,時間複雜度是O(n)。當前執行的線程數少不成問題,若是是1000個正在執行的線程,那麼這就是100w次的對比,這個過程極度消耗CPU資源。結果可能檢測出沒有死鎖,而後會發現:最終CPU飈的老高,然而執行的條數沒幾個!解決辦法有下面幾個:

  • 臨時關掉innodb_deadlock_detect,可是這樣會有不少超時,不實用
  • 控制數據庫的併發度:能夠從中間件這層控制(Java這裏),或者有能力的從數據庫這一層進行控制
  • 業務字段拆分:例如咱們將一行記錄拆分紅多行,讓一行的併發度降低(例如併發扣減id爲1這一行的金額,咱們能夠拆分紅id爲100,id爲200,id爲300這三行的金額,最後要查詢的時候,將這三行相加)

5、結束

下面一篇文章,會重點講間隙鎖,這個內容最爲複雜,涉及到了所謂數據庫解決幻讀的機制問題,特別單獨抽出一章來說解。

相關文章
相關標籤/搜索