《MySQL必懂系列》全域性鎖 表級鎖 行鎖

2021-10-10 17:21:40 字數 4590 閱讀 9000

mysql提供了不同等級的鎖,按限制能力的劃分,分為全域性鎖、表鎖、行鎖。本文會描述不同鎖的應用場景與實現原理。

全域性鎖就是對整個mysql資料庫加鎖,mysql中的命令是 flush tables with read lock (ftwrl)。在執行這個命令之後,mysql進入全域性鎖的狀態,整個資料庫會拒絕掉增刪改這些請求。

全域性鎖的目標是為我們維護乙個資料庫的邏輯一致性。如下場景中:在進行邏輯備份(即備份的資料是sql語句)的時候,沒有開啟全域性鎖,那麼很可能會導致出現資料庫的邏輯一致性錯誤,例如兩個表,乙個餘額表、乙個訂單表,在購物時(減餘額、生成訂單)如果邏輯備份在這兩個操作之間,也就是說減完餘額之後,邏輯備份,拒絕生成訂單,那麼這個時候,我們進行的邏輯備份就是乙個錯誤的邏輯一致性狀態。以後使用這個邏輯備份進行資料恢復的時候,就會出現使用者餘額已經減少,但並沒有訂單這種問題。

全域性鎖解決的就是上面的問題,我們可以結合資料庫中事務的隔離級別,使用可重複讀(各個事務之間沒有相互影響,基於mvcc)的隔離級別,獲取資料庫的邏輯一致性檢視。mysql官方自帶的邏輯備份工具mysqldump,在備份資料之前,會啟動乙個事務,以此來獲得乙個邏輯一致性檢視。

但需要注意的是,雖然事務的可重複能解決ftwrl影響效能的問題,但事務並不是萬能的,因為並不是所有的引擎都支援這個隔離級別,myisam這種不支援事務的引擎,如果備份過程中有更新,總是只能取到最新的資料,那麼就破壞了備份的邏輯一致性。

我們的目的是實現資料庫的邏輯一致性,那麼為什麼不建議直接把資料庫設定成唯讀狀態呢?(set global readonly=true)

主要有一下原因:

ftwrl與readonly的異常機制不太一樣。客戶端(相對於mysql)發生異常,ftwrl命令下會自動釋放mysql的全域性鎖。而readonly會一直停留在readonly狀態,資料庫長期處於不可寫狀態。

readonly會被一些邏輯判斷使用,例如使用readonly判斷是主庫或者備庫。

表級鎖也分為兩類:表鎖元資料鎖(meta data lock,mdl)

業務的更新不只是增刪改資料(dml),還有可能是加字段等修改表結構的操作(ddl)。

使用場景

在還沒有更細粒度的行鎖的時候,表鎖是最長用的處理併發的解決方式。但是對於當前支援行鎖的引擎例如innodb,都優先使用行鎖來控制併發,以此來避免因為鎖住整個表的影響。

表鎖的語法

加鎖lock tables … read/write、主動釋放鎖unlock tables。同時表鎖也可以在客戶端斷開連線的時候自動釋放。

讀鎖(共享鎖)

事務a對資料d加上共享鎖s,那麼事務a只能對d進行讀操作,並且後面的事務b、c、d都可以加鎖s進行唯讀操作。在釋放完s鎖之前不能對資料d進行修改。

寫鎖(排它鎖)

事務a對資料d加上排它鎖x,那麼事務a可以對資料d進行訪問、修改,並且拒絕其他事務對資料d的讀、寫。

表鎖需要注意的地方

lock tables語法不僅會限制別的執行緒(事務)讀寫操作,也限定了本執行緒(事務)的操作物件以及操作方式。即本執行緒只能按照加鎖語句中規定的方式(讀或者寫)訪問特定的資源(table1、table2)。例如:執行緒 thread1 中執行lock tables table1 write, table2 read;其他執行緒讀、寫 table1寫 table2的語句都會被阻塞。同時,執行緒 thread1 在執行unlock tables之前,也只能執行讀、寫 table1、讀 table2 的操作。連寫 table2 都不允許,並且也不能訪問其他表。

元資料在這裡其實指的就是表結構,mdl鎖定的也就是我們表結構。防止出現乙個執行緒a在執行表查詢操作時候,執行緒b刪除了乙個字段,導致查詢的結果與表結構不符合這種情況的出現。

所以為了解決上述問題,mdl分為了讀鎖寫鎖

表鎖並不是現在優先考慮使用的鎖,應該盡量的使用行鎖,如果在專案中遇到lock table1這樣的sql語句時,應該思考一下:

行鎖顧名思義就是對每一行的資料加鎖,這是mysql資料庫中最細粒度的鎖,右innodb引擎支援。對於不能支援行鎖的引擎,對於併發操作的處理只能使用表鎖鎖定整個表,這也是myisam被innodb所替代的重要原因之一。

使用行鎖過程中,若乙個事務a正在更新某一行資料d,這時候如果事務b也想對d進行更新操作,那麼只能等a更新完畢然後再加自己的行鎖對d進行更新操作。這其中就涉及到乙個兩階段鎖這個概念。

兩階段鎖協議:在 innodb事務中,行鎖是在需要的時候才加上的,但並不是不需要了就立刻釋放,而是要等到事務結束時才釋放。

其實就是規定了加鎖與解鎖的時機,兩階段鎖協議不僅侷限在行鎖中。

事務a事務b

begin

update t1 set k=k+1 where id=1;

update t1 set k=k+1 where id=2;

begin

update t1 set k=k+1 where id=1;

commit

上面的兩個事務ab執行時候就會使用到兩段鎖協議:事務a先開始執行,id=1時加鎖這一行,id=2時加鎖這一行,事務a的兩條語句執行完了但是還沒有commit,事務b開始執行,但是這個時候事務b的update id=1會被阻塞,因為id=1還被事務a加著行鎖,雖然事務a的update執行完了,但是事務a還沒有commit,意味著事務a所佔據著的行鎖都沒有釋放,只有等a執行commit之後,事務b才能繼續獲得id=1的行鎖進行update。

所以我們應該記住兩段鎖的特點:

在行鎖的引擎中,行鎖是執行到具體某一行才加上的。

行鎖在本本事務commit之後才會被釋放。

所以根據兩段鎖協議的特點,我們在開發過程中,應該在事務中把併發大的表放到後面執行,讓它被行鎖鎖定的時間最短。

例如在減庫存,生成訂單這樣的場景中,我們應該先在事務中生成訂單,在減庫存。因為庫存的update併發量會大於訂單insert的併發量,update需要使用行鎖,如果先update庫存,會使庫存中的這一行一直被行鎖鎖定,在事務提交時候才能被釋放,增加了許多無用的庫存行鎖鎖定時間。

資料庫中死鎖的概念很清晰,和我們作業系統中的一致:

資源必須互斥訪問

請求並保持

不可搶占資源

形成乙個環

如果乙個專案要新上線乙個新功能,如果新功能剛開始的時候mysql 就掛了。登上伺服器一看,cpu 消耗接近 100%,但整個資料庫每秒就執行不到 100 個事務。原因很可能就是死鎖。

出現死鎖以後,有兩種解決策略:

設定等待的超時時間。innodb_lock_wait_timeout主動發起死鎖檢測,發現死鎖後,主動回滾死鎖鏈條中的某乙個事務,讓其他事務得以繼續執行。innodb_deadlock_detect = on,表示開啟死鎖檢測。

所以通常情況下會採用主動死鎖檢測的策略,innodb_deadlock_detect預設值就是on的狀態。主動死鎖檢測能及時發現並解決死鎖,但主動死鎖檢測會消耗硬體資源。

主動死鎖檢測流程:每當乙個事務被鎖的時候,就要看看它所依賴的執行緒有沒有被 別人鎖住,如此迴圈,最後判斷是否出現了迴圈等待,也就是死鎖。

主動死鎖檢測在熱點行更新時產生的問題

上面我們提到更推薦使用主動死鎖檢測去解決死鎖問題,但在這樣的場景中:所有的事務都需要更新同一行的資料。使用主動死鎖檢測肯定能得出未死鎖,但是這期間要消耗大量的cpu,導致雖然占用了大量cpu卻實際沒能執行幾個事務。

這種由這種熱點行更新導致的效能問題的原因在於:主動死鎖檢測要耗費大量的 cpu 資源。

熱點行更新導致的效能問題的解決思路:

如果能保證某個業務不會出現死鎖,可以臨時關閉死鎖檢測,但本身可能存在風險,如果發生死鎖,會發生事務等待超時時間。

控制併發度。例如一行資料只能允許20個事務進行同時更新,那麼可以極大的減緩死鎖檢測的壓力。如何去控制併發度,大體也有兩個思路一是通過業務**在客戶端進行訪問mysql的控制,但是mysql不一定只有這乙個客戶端,所以這個思路優缺點;二是考慮使用中間間或者是修改mysql原始碼,對於相同行的update,在進入引擎之前排隊,裡面只允許存在20個事務進行update,這樣update時候就不會有太大的死鎖檢測壓力。(死鎖檢測時間複雜度為o(n平方))。 但是這個需要資料庫方面的專家。。。

可以考慮在業務層面減少對某一行的併發度。例如在收款這個場景中,我們把熱點的某一行拆分出來,保證拆分出來的幾行最後在收款的總數一致就可以了。如果分為20個,那麼死鎖的肯能性就變為了原來的20粉之一,與此同時由於不是同一行也減少了主動死鎖檢測cpu的消耗。這種方式需要在**裡做詳細、嚴謹的邏輯分析。

綜上:減少死鎖的主要方向,就是控制訪問相同資源的併發事務量。

151 全域性鎖 表級鎖

對整個庫例項加鎖。讓整個庫處於唯讀狀態的時候,可以使用這個命令,之後其他執行緒的以下語句會被阻塞 增刪改 建表 修改表結 更新類事務的提交 mysql 提供了乙個加全域性讀鎖的方法,命令是flush tables with read lock ftwrl 用unlock tables主動釋放鎖,也可...

mysql 4 1 全域性鎖 表鎖 行級鎖

描述 兩階段鎖協議 行鎖是在需要的時候才加上的,但並不是不需要了就立刻釋放,而是要等到事務結束時才釋放 行鎖針對資料表中行記錄的鎖 死鎖當併發系統中不同執行緒出現迴圈資源依賴,涉及的執行緒都在等待別的執行緒釋放資源時,就會導致這幾個執行緒都進入無限等待的狀態 根據加鎖的範圍,mysql 裡面的鎖大致...

mysql筆記系列 五 全域性鎖,表鎖,行鎖

資料庫的鎖 分為 全域性鎖,表級鎖,行鎖 1.1 全域性鎖 就是給整個資料庫加鎖,mysql提供了乙個命令為 flush tables with read lock 使用後 整個庫都將處於唯讀狀態 其他執行緒的 資料更新操作 增刪改 資料定義語句 建表,修改表結構 和更新類事務的提交語句 都阻塞。全...