這是我總結的一個表格,是本文中涉及到的鎖(因為篇幅有限就沒有包括自增鎖)
加鎖范圍 | 名稱 | 用法 |
---|---|---|
數據庫級 | 全局讀鎖 | 執行Flush tables with read lock 命令各整個庫接加一個讀鎖,處於只讀狀態。 |
數據庫級 | 讓全局只讀 | 執行set global readonly=true 命令可以讓全庫只能讀 |
表級別 | 表鎖 | lock tables test read 讓test表處於只讀狀態,所有事務都不能改表。lock tables test read 讓test表處於只讀狀態,所有事務都不能讀表,只有當前事務可以更新表。 |
表級別 | 元數據鎖(metadata lock,簡寫為MDL) | 在MySQL 5.5版本中引入了元數據鎖,當對一個表做增刪改查操作的時候,加MDL讀鎖;當要對表做結構變更操作的時候,加MDL寫鎖。主要是為了保證讀寫的正確性,不能一邊讀取表數據,一邊修改表結構。 |
表級別 | 意向鎖 | 意向鎖的作用主要是表明當前表是否存在數據行加了行鎖,在申請行級別的S鎖之前會主動申請表級別的共享意向鎖IS鎖。在申請行級別的X鎖之前會申請表級別的意向鎖IX鎖 |
行級別 | 記錄鎖 | 就是鎖單個索引 |
行級別 | 間隙鎖 | 就是鎖索引之間的間隙,防止其他事務往這個間隙中插入新數據。 |
行級別 | 下一鍵鎖(next key lock) | 就是記錄鎖+間隙鎖,就是既鎖索引,也鎖索引之間的間隙。 |
1.數據庫級別的鎖
數據庫級別的鎖有以下兩種:
1.1.全局讀鎖
對數據庫執行Flush tables with read lock
命令讓整個庫處於只讀狀態。
1.2.讓全局只讀
執行set global readonly=true
這個命令也可以讓全庫只能讀,但是第一有些系統會使用readonly來做一個操作,例如根據readonly是否為true判斷數據庫是否是從庫,第二是如果執行這個命令后,客戶端斷開連接后,數據庫會一直處於只讀狀態,如果是FTWRL命令發送異常會釋放全局鎖。(如果是從庫,設置read-only對super user權限無效)
使用場景:
最常用的場景是對數據庫備份。對數據庫加鎖,讓整個數據庫處於只讀狀態,所有更新操作停止(如果是主庫就不能執行更新語句,從庫也不能執行同步過來的bin log),然后對整個數據庫做邏輯備份(就是將所有數據生成SQL寫入備份文件。)
補充資料:
更好的進行數據庫備份的一種方法
就是通過官方自帶的邏輯備份工具mysqldump來進行邏輯備份時,設置一個參數-single-transaction,這樣導數據的時候就會開啟一個事務,這樣利用innodb的mvcc機制可以保證在事務執行過程中,讀到的數據都跟事務開始時的一致,並且執行過程中,其他事務可以執行更新操作, 不會對他造成影響(因為它就跟普通SELECT查詢一樣是讀取的快照數據),這種方法必須要求數據庫所有表的引擎都是innodb才行。
2.表級別的鎖
表級別的鎖有兩種,一種是表鎖,一種是元數據鎖MDL。
2.1表鎖 lock table
就是使用lock table user_table read/write命令來對表進行加讀鎖或者寫鎖。
-
加讀鎖(也就是表級別共享鎖X鎖)后,表對所有線程都是只能讀,即便是當前線程也只能讀表,不然會數據不一致。
-
加寫鎖后,表是對當前線程寫,其他線程不能讀,不能回數據不一致。
可以通過unlock tables
來解鎖,客戶端斷開時也會自動釋放鎖,但是影響所有線程,影響面太大了。這種鎖我們一般也不會主動去調用,但是我們去更新一些數據時,如果查詢條件是根據一些沒有索引的字段去查詢的,那樣更新時會主動申請表鎖中的寫鎖,獲取成功后才能修改數據,事務提交成功之后,才會釋放鎖。(這也是為什么我們一般強調對於常用的查詢字段加索引,就是為了提高更新和讀取效率。)
2.2元數據鎖MDL(MetaData Lock)
分為讀鎖和寫鎖,加讀鎖時,所有的線程都可以讀表,加寫鎖時,只能一個線程寫,其他的不能讀。
鎖不用顯式使用,是訪問一個表時,自動加上的。
對表進行增刪改查時,會加讀鎖。
對表結構做修改時,會加寫鎖。
目的是為了在增刪改查時不能修改表結構,修改表結構時不能去增刪改查。
2.3 意向鎖
意向鎖的作用主要是表明當前表是否存在數據行加了行鎖。這樣事務可以根據當前表是否有意向鎖來快速判斷當前表是否存在數據行加了行鎖,這樣再加表級別的排斥鎖X,共享鎖S時,避免了去查詢每一行數據,判斷是否加了行鎖,減小了性能開銷。
意向共享鎖(IS鎖)
事務讓一行數據只能讀,需要申請對這行數據加行級別的共享鎖S鎖,在申請行級別的S鎖之前會主動申請表級別的共享意向鎖IS鎖。
意向排斥鎖(IX鎖)
事務在更新某一行數據時,需要申請對這行數據加行級別的排斥鎖X鎖,在申請行級別的X鎖之前會申請表級別的意向鎖IX鎖。
意向鎖之間是兼容的,IS鎖和IX是兼容,因為可能我們對第一行數據加S鎖,那么會申請IS鎖,對第二行數據加X鎖,此時跟第一行的數據的S鎖不沖突,所以也會先申請IX鎖,由此可見,IS鎖和IX之間不沖突,IS鎖,IX鎖與行級別的S,行級別的X之間也不沖突。
意向鎖只是跟表級別的S,X鎖可能會沖突。
表級別的S鎖 | 表級別的X鎖 | |
---|---|---|
意向共享鎖IS | 兼容 | 不兼容 |
意向排斥鎖IX | 不兼容 | 不兼容 |
行級別的鎖
行鎖是innodb引擎特有的鎖,也是分為共享鎖(也就是通常說的讀鎖)和互斥鎖(也就是通常說的寫鎖)
-
共享鎖 S鎖,就是讀鎖,允許事務讀一行數據,不能被修改。所以讀鎖之間不排斥
-
互斥鎖 X鎖,就是寫鎖,就是讓當前事務可以修改這行數據,其他事務不能修改這行數據
如果是從加鎖的范圍來區分,行鎖主要分為記錄鎖(鎖單個索引),間隙鎖(鎖索引之間的間隙),下一鍵鎖(等於記錄鎖+間隙鎖)
記錄鎖 record lock
記錄鎖鎖定的是單條索引記錄。例如 SELECT c1 FROM t WHERE c1 = 10 FOR UPDATE;
,如果c是主鍵或者是一個唯一性索引的字段,由於在表內唯一,所以只需要對c=10這個索引進行加鎖,可以防止其他事務插入,更新或刪除這個數據行。
間隙鎖 gap lock
間隙鎖就會對記錄之間的間隙加鎖,防止數據插入。
下一鍵鎖 next-key lock
next-key lock是 record lock 和 gap lock的組合,就是會對索引記錄加記錄鎖 + 索引記錄前面間隙上的鎖”,就是對要更新的數據的左右兩個端點加間隙鎖。
具體案例:
因為innodb默認的隔離級別是可重復讀,我們在執行更新語句和使用當前讀語句(SELECT…FOR UPDATE)時,都是需要加一些行鎖的,來防止其他事務插入或者刪除數據,導致在事務內多次讀取到的數據行不同。針對行鎖的加鎖規則,極客時間中丁奇老師總結了以下四條規則:
- 原則1:加鎖的基本單位是next-key lock。希望你還記得,next-key lock是前開后閉區間。
- 原則2:查找過程中訪問到的對象才會加鎖。
- 優化1:索引上的等值查詢,給唯一索引加鎖的時候,next-key lock退化為記錄鎖。
- 優化2:索引上的等值查詢,向右遍歷時且最后一個值不滿足等值條件的時候,next-key lock退化為間隙鎖。
- 一個bug:唯一索引上的范圍查詢會訪問到不滿足條件的第一個值為止。
簡單的來說,我認為就是next-key lock就是加鎖的基本單位,只不過innodb做了很多優化,在不需要對那么大范圍的數據行加鎖時,會進行降級,降級為間隙鎖,或者是記錄鎖。
下面就來看一個具體的例子
例如:
a是一個普通字段,對它建了索引,在數據庫中對應a這個字段存在的值已經有1,5,10,20,30
那么根據next-key lock來划分區間,next-key lock是根據已有數據行來划分區間,並且是左開右閉區間,所以可以鎖定的區間是
(負無窮,1]
(1,5]
(5,10]
(10,20]
(20,30]
(30,正無窮)
//更新操作
update table set b = '1' where a = 10;
在innodb中執行更新操作,
- 如果a是唯一性索引,根據原則3那么只需要對a為10的這條索引加記錄鎖就行了,因為不用擔心其他事務再插入一條a為10的數據,因為插入時會有唯一性判斷。
- 但是如果a是非唯一性索引,如果只是對a=10這個索引加鎖,可能會有其他事務插入a=10的數據行,所以會對(5,10]和(10,20]這兩個區間加鎖,並且根據上面的原則4,會將(10,20]降級為間隙鎖,也就是只對(10,20)加鎖,因為a=20這個索引是否加鎖都不影響當前的事務。
- 如果a沒有索引,需要插入時會先申請表級別的互斥鎖X鎖,然后進行插入。