目錄
MySQL系列:
鎖是計算機用以協調多個進程間併發存取同一共用資源的一種機制 機製。MySQL中爲了保證數據存取的一致性與有效性等功能,實現了鎖機制 機製,MySQL中的鎖是在伺服器層或者儲存引擎層實現的。
MySQL引擎預設的鎖級別:
(在後面的文章中會詳細對比MySQL的常用引擎,下面 下麪對MySQL支援的鎖進行介紹)
對整個數據庫範例加鎖,加鎖後整個庫處於只讀狀態的時候,之後其他執行緒的以下語句會被阻塞:DML(增刪改)、DDL、事務提交。不會影響DQL(查詢請求),從而獲取一致性檢視,保證數據的完整性。
全域性鎖:https://dev.mysql.com/doc/refman/8.0/en/lock-instance-for-backup.html
全域性鎖命令:
-- 加全域性鎖 (FTWRL)
flush tables with read lock;
-- 釋放全域性鎖
unlock tables;
或斷開加全域性鎖的對談連線
錯誤資訊:[Error Code: 1223, SQL State: HY000] Can't execute the query because you have a conflicting read lock
全域性鎖的場景是全庫備份,但前面也介紹了,全域性鎖的時候庫是隻讀狀態。此時如果在主庫加全域性鎖進行備份,業務就會停擺,如果在從庫加全域性鎖,就無法同步主庫的操作導致主從延遲。其實在實際應用中我們可以對不同的儲存引擎使用不同的策略備份。
使用mysqldump工具備份:
表級鎖(Table-level Locking):表示對當前操作的整張表加鎖,它實現簡單,資源消耗較少,被大部分 MySQL 引擎支援;
表級鎖: https://dev.mysql.com/doc/refman/8.0/en/lock-tables.html
表鎖命令:
-- 加表鎖
lock tables … read/write;
-- 釋放表鎖
unlock tables;
或斷開加表鎖的對談連線
錯誤資訊:[Error Code: 1099, SQL State: HY000] Table 'csdn' was locked with a READ lock and can't be updated
表讀鎖:
表寫鎖:
元數據鎖(Metadata Locking):MySQL5.5之後新增,MDL也是表級別的鎖,不需要顯示的使用,當對一個表做增刪改查操作的時候,加 MDL 讀鎖;當要對錶做結構變更操作的時候,加 MDL 寫鎖。
頁級鎖(Page-level Locking):鎖定表中某些行集合(稱做頁),被鎖定的行只對鎖定最初的執行緒是可行。如果另外一個執行緒想要向這些行寫數據,它必須等到鎖被釋放。不過其他頁的行仍然可以使用。在MySQL5.1之前BDB引擎預設頁級鎖,之後被棄用。
行級鎖(Row-level Locking):基於索引數據結構實現,是 MySQL 中鎖定粒度最細的一種鎖,只有執行緒當前使用的行被鎖定,其他行對於其他執行緒都是可用的;InnoDB引擎預設的鎖級別。
行級鎖包括共用鎖、排他鎖和更新鎖。在下面 下麪的內容中具體介紹。
InnoDB行級鎖是通過鎖索引記錄實現的,如果更新的列沒建索引會鎖住整個表。
InnoDB鎖:https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html
共用鎖(Shared Locks):簡稱S鎖,也被稱爲讀鎖。讀鎖允許多個連線可以同一時刻併發的讀取同一資源,互不幹 不乾擾。
語法:
SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
排他鎖(Exclusive Locks):簡稱X鎖,也叫獨佔鎖、寫鎖。一個獨佔鎖會阻塞其他的寫鎖或讀鎖,保證同一時刻只有一個連線可以寫入數據,同時防止其他使用者對這個數據的讀寫。執行數據更新命令,即INSERT、UPDATE 或DELETE 命令時,MySQL 會自動使用獨佔鎖。但當物件上有其它鎖存在時,無法對其加獨佔鎖。獨佔鎖一直到事務結束才能 纔能被釋放。
語法:
SELECT * FROM table_name WHERE ... FOR UPDATE
意向鎖(Intention Locks):提前宣告一個意向,並獲取表級別的意向鎖(共用意向鎖 IS 或排他意向鎖 IX),如果獲取成功,則稍後將要或正在(才被允許),對該表的某些行加鎖(S或X)了。(除了 LOCK TABLES ... WRITE,會鎖住表中所有行,其他場景意向鎖實際不鎖住任何行)。
意向鎖包含:
意向鎖協定:在事務能夠獲取表中行上的共用鎖之前,它必須首先獲取表上的IS鎖或更強的鎖。 在事務能夠獲取表中的行上的獨佔鎖之前,它必須首先獲取表上的IX鎖。
InnoDB鎖相容性列表
|
X | IX | S | IS |
---|---|---|---|---|
X | ||||
IX | 相容 | 相容 | ||
S | 相容 | 相容 | ||
IS | 相容 | 相容 | 相容 |
共用鎖和排他鎖都是標準的行級鎖,意向鎖是表級鎖。
意向鎖是比X、S更弱的鎖,存在一種預判的意義!先獲取更弱的IX、IS鎖,如果獲取失敗就不必再花費更大的開銷獲取更強的X、S鎖。
記錄鎖(Record Locks):最簡單的行鎖,行鎖是加在索引上的,如果查詢命令不走索引時,這條語句會鎖住所有記錄也就是鎖表,如果語句的執行能夠執行某一個欄位的索引,那麼僅會鎖住滿足 where 的行(Record Lock)。
間隙鎖(Gap Locks):鎖定索引記錄之間的間隙([2]),或者鎖定一個索引記錄之前的間隙([1]),或者鎖定一個索引記錄之後的間隙([3])。
例:
如圖[1]、[2]、[3]部分。一般作用於我們的範圍篩選查詢> 、< 、between......
例如, SELECT userId FROM t1 WHERE userId BETWEEN 1 and 4 FOR UPDATE; 阻止其他事務將值3插入到列 userId 中。因爲該範圍內所有現有值之間的間隙都是鎖定的。
對於使用唯一索引來搜尋唯一行的語句 select a from ,不產生間隙鎖定。(不包含組合唯一索引,也就是說 gapLock 不作用於單列唯一索引)。
例如,如果id列有唯一的索引,下面 下麪的語句只對id值爲100的行使用索引記錄鎖,其他對談是否在前一個間隙中插入行並不重要:
SELECT * FROM t1 WHERE id = 100; 如果id沒有索引或具有非唯一索引,則語句將鎖定前面的間隙。
間隙可以跨越單個索引值、多個索引值(如上圖2,3),甚至是空的;
間隙鎖是效能和併發性之間權衡的一種折衷,用於某些特定的事務隔離級別,如RC級別(RC級別:REPEATABLE READ,我司爲了減少死鎖,關閉了gap鎖,使用RR級別);
在重疊的間隙中(或者說重疊的行記錄)中允許gap共存;
例如,同一個 gap 中,允許一個事務持有 gap X-Lock(gap 寫鎖\排他鎖),同時另一個事務在這個 gap 中持有(gap 寫鎖\排他鎖);
記錄鎖與與間隙鎖的結合。
例:存在一個查詢匹配 b=3 的行(b上有個非唯一索引),那麼所謂 NextLock 就是:在b=3 的行加了 RecordLock 並且使用 GapLock 鎖定了 b=3 之前(「之前」:索引排序)的所有行記錄。
innodb 中預設隔離級別(RR)下,next key Lock 自動開啓;
插入意向鎖(Insert Intention Locks):是一種間隙鎖,在行執行 INSERT 之前的插入操作設定。如果多個事務 INSERT 到同一個索引間隙之間,但沒有在同一位置上插入,則不會產生任何的衝突。假設有值爲4和7的索引記錄,現在有兩事務分別嘗試插入值爲 5 和 6 的記錄,在獲得插入行的排他鎖之前,都使用插入意向鎖鎖住 4 和 7 之間的間隙,但兩者之間並不會相互阻塞,因爲這兩行並不衝突。
插入意向鎖只會和 間隙或者 Next-key 鎖衝突,正如上面所說,間隙鎖作用就是防止其他事務插入記錄造成幻讀,正是由於在執行 INSERT 語句時需要加插入意向鎖,而插入意向鎖和間隙鎖衝突,從而阻止了插入操作的執行。
不同類型的鎖之間的相容如下表所示:
RECORED | GAP | NEXT-KEY | II GAP(插入意向鎖) | |
---|---|---|---|---|
RECORED | 相容 | 相容 | ||
GAP | 相容 | 相容 | 相容 | 相容 |
NEXT-KEY | 相容 | 相容 | ||
II GAP | 相容 | 相容 |
自增鎖(Auto-inc Locks):是一種特殊的表級別鎖,專門針對事務插入AUTO_INCREMENT型別的列。如果一個事務正在往表中插入記錄,所有其他事務的插入必須等待,以便第一個事務插入的行,是連續的主鍵值。
站內摘來的一張時序圖,幫助理解MySQL的事務和鎖:
死鎖(Dead Lock):當併發系統中不同線程出現回圈資源依賴,涉及的執行緒都在等待別的執行緒釋放資源時,就會導致這幾個執行緒都進入無限等待的狀態,稱爲死鎖。
產生死鎖的4個必要條件:
死鎖檢測:
對於死鎖,我們可以通過參數 innodb_lock_wait_timeout 根據實際業務場景來設定超時時間,InnoDB引擎預設值是50s。
發起死鎖檢測,發現死鎖後,主動回滾死鎖鏈條中的某一個事務,讓其他事務得以繼續執行。將參數 innodb_deadlock_detect 設定爲 on,表示開啓這個邏輯(預設是開啓狀態)。
wait-for graph 演算法來主動進行死鎖檢測:innodb 還提供了 wait-for graph 演算法來主動進行死鎖檢測,每當加鎖請求無法立即滿足需要並進入等待時,wait-for graph 演算法都會被觸發。
避免死鎖的方法:
如何解決熱點行更新導致的效能問題?
MyISAM 中是不會產生死鎖的,因爲 MyISAM 總是一次性獲得所需的全部鎖,要麼全部滿足,要麼全部等待。
活鎖、死鎖本質上是一樣的:都是多個執行緒任務沒有任何進展。原因是在獲取共用資源時,併發多執行緒或多進程宣告資源佔用(即加鎖)的順序有衝突,死鎖是加不上就死等(多執行緒都處於阻塞狀態);活鎖是加不上就放開已獲得的資源重試,這種情況下執行緒並沒有阻塞所以是活的狀態,但是隻是在做無用功(每次重試都是失敗的),其實多核活鎖不太常見(資源分配充足)。
例:資源A和B,進程P1和P2
start:
P1 lock A
P2 lock B
P1 lock B fail context switch
P2 lock A fail context switch
P1 release A
P2 release B
goto start #活鎖,不斷嘗試,做無用功
顧名思義,就是很悲觀,它對於數據被外界修改持保守態度,認爲數據隨時會修改,所以整個數據處理中需要將數據加鎖。悲觀鎖一般都是依靠關係數據庫提供的鎖機制 機製,事實上關係數據庫中的行鎖,表鎖,讀寫鎖都是悲觀鎖。
樂觀鎖一般是指使用者自己實現的一種鎖機制 機製。樂觀鎖是指操作數據庫時(更新操作),想法很樂觀,認爲這次的操作不會導致衝突,在操作數據時,並不進行任何其他的特殊處理(也就是不加鎖),而在進行更新後,再去判斷是否有衝突了。樂觀鎖適用於多讀的應用型別,這樣可以提高吞吐量。
常見樂觀鎖實現方式:
當且僅當 V 的值等於 A時,CAS通過原子方式用新值B來更新V的值,否則不會執行任何操作(比較和替換是一個原子操作)。一般情況下是一個自旋操作,即不斷的重試。
注意:樂觀鎖只能用於本系 本係統控制,無法阻止外系統更新;
MVCC (multiple-version-concurrency-control):行級鎖的變種, 在普通讀情況下避免了加鎖操作,因此開銷更低。雖然實現不同,但通常都是實現非阻塞讀,對於寫操作只鎖定必要的行。
一致性讀 (就是讀取快照)select * from table ....
當前讀(就是讀取實際的持久化的數據),特殊的讀操作,插入/更新/刪除操作,屬於當前讀,處理的都是當前的數據,需要加鎖。 select * from table where ... lock in share mode; select * from table where ... for update; insert; update ; delete;
注意:select ...... from table ...... (沒有額外加鎖後綴)使用MVCC,保證了讀快照(MySQL 稱爲 consistent read),所謂一致性讀或者讀快照就是讀取當前事務開始之前的數據快照,在這個事務開始之後的更新不會被讀到。
InnoDB的 MVCC 通常是通過在每行數據後邊儲存兩個隱藏的列來實現(其實是三列,第三列是用於事務回滾),一個儲存了行的建立版本號,另一個儲存了行的更新版本號(上一次被更新數據的版本號) 這個版本號是每個事務的版本號,遞增的。這樣保證了 InnoDB對讀操作不需要加鎖也能保證正確讀取數據。
在MySQL 預設的 Repeatable Read 隔離級別下, MVCC 的具體操作:
Select(快照讀,所謂讀快照就是讀取當前事務之前的數據):
a.InnoDB 只 select 查詢版本號早於當前版本號的數據行,這樣保證了讀取的數據要麼是在這個事務開始之前就已經 commit 了的(早於當前版本號),要麼是在這個事務自身中執行建立操作的數據(等於當前版本號)。
b.查詢行的更新版本號要麼未定義,要麼大於當前的版本號(爲了保證事務可以讀到老數據),這樣保證了事務讀取到在當前事務開始之後未被更新的數據。
注意: 這裏的 select 不能有 for update、lock in share 語句。 總之要只返回滿足以下條件的行數據,達到了快照讀的效果:
(行建立版本號< =當前版本號 && (行更新版本號==null or 行更新版本號>當前版本號 ) )
當前版本號—寫—>新數據行建立版本號 && 當前版本號—寫—>老數據更新版本號();
希望本文對你有幫助,請點個贊鼓勵一下作者吧~ 謝謝!