1,介紹
鎖是計算機協調多個程序或執行緒併發訪問某一資源的機制。在資料庫中,除傳統的計算資源(CPU、RAM、I/O)的爭用以外,資料也是一種供許多使用者共享的資源。如何保證資料併發訪問的一致性、有效性是所有資料庫必須解決的一個問題,鎖衝突也是影響資料庫併發訪問效能的一個重要因素。從這個角度來說,鎖對資料庫而言顯得尤其重要,也更加複雜。
MySQL中的鎖,按照鎖的粒度分,分為以下三類:
-
全域性鎖:鎖定資料庫中的所有表。
-
表級鎖:每次操作鎖住整張表。
-
行級鎖:每次操作鎖住對應的行資料。
2. 全域性鎖
2.1 介紹
全域性鎖就是對整個資料庫例項加鎖,加鎖後整個例項就處於只讀狀態,後續的 DML 的寫語句,DDL 語句,已經更新操作的事務提交語句都將被阻塞。
其典型的使用場景是做全庫的邏輯備份,對所有的表進行鎖定,從而獲取一致性檢視,保證資料的完整性。
為什麼全庫邏輯備份,就需要加全就鎖呢?
- 我們一起先來分析一下不加全域性鎖,可能存在的問題。
假設在資料庫中存在這樣三張表: tb_stock 庫存表,tb_order 訂單表,tb_orderlog 訂單日誌表。
-
在進行資料備份時,先備份了tb_stock庫存表。
-
然後接下來,在業務系統中,執行了下單操作,扣減庫存,生成訂單(更新tb_stock表,插入tb_order表)。
-
然後再執行備份 tb_order表的邏輯。
-
業務中執行插入訂單日誌操作。
-
最後,又備份了tb_orderlog表。
此時備份出來的資料,是存在問題的。因為備份出來的資料,tb_stock表與tb_order表的資料不一致(有最新操作的訂單資訊,但是庫存數沒減)。
那如何來規避這種問題呢? 此時就可以藉助於MySQL的全域性鎖來解決。
- 再來分析一下加了全域性鎖後的情況
對資料庫進行進行邏輯備份之前,先對整個資料庫加上全域性鎖,一旦加了全域性鎖之後,其他的 DDL、DML 全部都處於阻塞狀態,但是可以執行 DQL 語句,也就是處於只讀狀態,而資料備份就是查詢操作。那麼資料在進行邏輯備份的過程中,資料庫中的資料就是不會發生變化的,這樣就保證了資料的一致性和完整性。
2.2 語法
- 加全域性鎖
flush tables with read lock ;
- 資料備份
mysqldump -uroot –p1234 itcast > itcast.sql
資料備份的相關指令, 在後面MySQL管理章節, 還會詳細講解.
- 釋放鎖
unlock tables ;
2.3 特點
資料庫中加全域性鎖,是一個比較重的操作,存在以下問題:
-
如果在主庫上備份,那麼在備份期間都不能執行更新,業務基本上就得停擺。
-
如果在從庫上備份,那麼在備份期間從庫不能執行主庫同步過來的二進位制日誌(binlog),會導致主從延遲。
在 InnoDB 引擎中,我們可以在備份時加上引數 --single-transaction 引數來完成不加鎖的一致性資料備份。
mysqldump --single-transaction -uroot –p123456 itcast > itcast.sql
3. 表級鎖
3.1 介紹
表級鎖,每次操作鎖住整張表。鎖定力度大,發生鎖衝突的機率最高,併發度最低。應用在MyISAM、InnoDB、BDB等儲存引擎中。
對於表級鎖,主要分為以下三類:
-
表鎖
-
後設資料鎖(meta data lock,MDL)
-
意向鎖
3.2 表鎖
對於表鎖,分為兩類:
-
表共享讀鎖(read lock)
-
表獨佔寫鎖(write lock)
語法:
-
加鎖:lock tables 表名... read/write。
-
釋放鎖:unlock tables / 客戶端斷開連線 。
特點
- 讀鎖
左側為客戶端一,對指定表加了讀鎖,不會影響右側客戶端二的讀,但是會阻塞右側客戶端的寫。
2. 寫鎖
左側為客戶端一,對指定表加了寫鎖,會阻塞右側客戶端的讀和寫。
結論: 讀鎖不會阻塞其他客戶端的讀,但是會阻塞寫。寫鎖既會阻塞其他客戶端的讀,又會阻塞其他客戶端的寫。
3.3 後設資料鎖
meta data lock , 後設資料鎖,簡寫MDL。
MDL 加鎖過程是系統自動控制,無需顯式使用,在訪問一張表的時候會自動加上。MDL鎖主要作用是維護表後設資料的資料一致性,在表上有活動事務的時候,不可以對後設資料進行寫入操作。為了避免 DML與 DDL衝突,保證讀寫的正確性。
這裡的後設資料,大家可以簡單理解為就是一張表的表結構。 也就是說,某一張表涉及到未提交的事務時,是不能夠修改這張表的表結構的。
在 MySQL5.5 中引入了 MDL,當對一張表進行增刪改查的時候,加 MDL 讀鎖(共享);當對錶結構進行變更操作的時候,加 MDL 寫鎖(排他)。
常見的SQL操作時,所新增的後設資料鎖:
3.4 意向鎖
為了避免 DML 在執行時,加的行鎖與表鎖的衝突,在 InnoDB 中引入了意向鎖,使得表鎖不用檢查每行資料是否加鎖,使用意向鎖來減少表鎖的檢查。
假如沒有意向鎖,客戶端一對錶加了行鎖後,客戶端二如何給表加表鎖呢,來透過示意圖簡單分析一下:
首先客戶端一,開啟一個事務,然後執行DML操作,在執行DML語句時,會對涉及到的行加行鎖。
當客戶端二,想對這張表加表鎖時,會檢查當前表是否有對應的行鎖,如果沒有,則新增表鎖,此時就會從第一行資料,檢查到最後一行資料,效率較低。
有了意向鎖之後 :
客戶端一,在執行 DML 操作時,會對涉及的行加行鎖,同時也會對該表加上意向鎖。
而其他客戶端,在對這張表加表鎖的時候,會根據該表上所加的意向鎖來判定是否可以成功加表鎖,而不用逐行判斷行鎖情況了。
分類
-
意向共享鎖(IS): 由語句 select ... lock in share mode 新增 。 與 表鎖共享鎖
(read)相容,與表鎖排他鎖(write)互斥。 -
意向排他鎖(IX): 由 insert、update、delete、select...for update 新增 。與表鎖共享鎖(read)及排他鎖(write)都互斥,意向鎖之間不會互斥。
一旦事務提交了,意向共享鎖、意向排他鎖,都會自動釋放。
可以透過以下SQL,檢視意向鎖及行鎖的加鎖情況:
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from
performance_schema.data_locks;
4. 行級鎖
4.1 介紹
行級鎖,每次操作鎖住對應的行資料。鎖定粒度最小,發生鎖衝突的機率最低,併發度最高。應用在 InnoDB 儲存引擎中。
InnoDB 的資料是基於索引組織的,行鎖是透過對索引上的索引項加鎖來實現的,而不是對記錄加的鎖。對於行級鎖,主要分為以下三類:
- 行鎖(Record Lock):鎖定單個行記錄的鎖,防止其他事務對此行進行 update 和 delete 。在 RC、RR 隔離級別下都支援。
- 間隙鎖(Gap Lock):鎖定索引記錄間隙(不含該記錄),確保索引記錄間隙不變,防止其他事務在這個間隙進行 insert,產生幻讀。在 RR 隔離級別下都支援。
- 臨鍵鎖(Next-Key Lock):行鎖和間隙鎖組合,同時鎖住資料,並鎖住資料前面的間隙Gap。在 RR 隔離級別下支援。
4.2 行鎖
InnoDB實現了以下兩種型別的行鎖:
-
共享鎖(S):允許一個事務去讀一行,阻止其他事務獲得相同資料集的排它鎖。
-
排他鎖(X):允許獲取排他鎖的事務更新資料,阻止其他事務獲得相同資料集的共享鎖和排他鎖。
兩種行鎖的相容情況如下:
常見的SQL語句,在執行時,所加的行鎖如下:
演示
預設情況下,InnoDB 在 REPEATABLE READ事務隔離級別執行,InnoDB 使用 next-key 鎖進行搜尋和索引掃描,以防止幻讀。
-
針對唯一索引進行檢索時,對已存在的記錄進行等值匹配時,將會自動最佳化為行鎖。
-
InnoDB 的行鎖是針對於索引加的鎖,不透過索引條件檢索資料,那麼 InnoDB 將對錶中的所有記錄加鎖,此時 就會升級為表鎖。
可以透過以下SQL,檢視意向鎖及行鎖的加鎖情況:
select object_schema,object_name,index_name,lock_type,lock_mode,lock_data from
performance_schema.data_locks;
4.3 間隙鎖 & 臨鍵鎖
預設情況下,InnoDB 在 REPEATABLE READ 事務隔離級別執行,InnoDB使用 next-key 鎖進行搜尋和索引掃描,以防止幻讀。
-
索引上的等值查詢(唯一索引),給不存在的記錄加鎖時, 最佳化為間隙鎖 。
-
索引上的等值查詢(非唯一普通索引),向右遍歷時最後一個值不滿足查詢需求時,next-key lock 退化為間隙鎖。
-
索引上的範圍查詢(唯一索引)--會訪問到不滿足條件的第一個值為止。
注意:間隙鎖唯一目的是防止其他事務插入間隙。間隙鎖可以共存,一個事務採用的間隙鎖不會阻止另一個事務在同一間隙上採用間隙鎖。