MySQL鎖機制


1、前言
 
Mysql為了解決並發、數據安全的問題,使用了鎖機制。可以按照鎖的粒度把數據庫鎖分為表級鎖和行級鎖。
 
表級鎖:Mysql中鎖定 粒度最大 的一種鎖,對當前操作的整張表加鎖,實現簡單 ,資源消耗也比較少,加鎖快,不會出現死鎖 。其鎖定粒度最大,觸發鎖沖突的概率最高,並發度最低,MyISAM和 InnoDB引擎都支持表級鎖。
 
行級鎖:Mysql中鎖中粒度最小的一種鎖,只針對當前操作的行進行加鎖。 行級鎖能大大減少數據庫操作的沖突。其加鎖粒度最小,並發度高,但加鎖的開銷也最大,加鎖慢,會出現死鎖。 InnoDB支持的行級鎖,包括如下幾種算法。
  • Record Lock: 對索引項加鎖,鎖定符合條件的行。其他事務不能修改和刪除加鎖項;Record Lock總是會去鎖住索引記錄,如果InnoDB存儲引擎表在建立的時候沒有設置任何一個索引,那么這時InnoDB存儲引擎會使用隱式的主鍵來進行鎖定。理解成單個行記錄上的鎖。
  • Gap Lock: 對索引項之間的“間隙”加鎖,鎖定記錄的范圍(對第一條記錄前的間隙或最后一條將記錄后的間隙加鎖),不包含索引項本身。其他事務不能在鎖范圍內插入數據,這樣就防止了別的事務新增幻影行。GAP鎖的目的,是為了防止同一事務的兩次當前讀,出現幻讀的情況。
  • Next-key Lock: 鎖定索引項本身和索引范圍。 鎖定一個范圍,並且鎖定記錄本身。對於行的查詢,都是采用該方法,主要目的是解決幻讀的問題。即Record Lock和Gap Lock的結合。可解決數據庫幻讀問題。Next-Key Lock是結合了Gap Lock和Record Lock的一種鎖定算法,在Next-Key Lock算法下,InnoDB對於行的查詢都是采用這種鎖定算法。例如有一個索引有10,11,13和20這4個值,那么該索引可能被Next-Key Locking的區間為:
 
 
雖然使用行級索具有粒度小、並發度高等特點,但是表級鎖有時候也是非常必要的:
  • 事務更新大表中的大部分數據直接使用表級鎖效率更高;
  • 事務比較復雜,使用行級索很可能引起死鎖導致回滾。
 
2、鎖分類
 
無論是表級鎖和行級鎖可以進一步划分為共享鎖(S)和排他鎖(X)。
 
共享鎖(s):共享鎖(Share Locks,簡記為S)又被稱為讀鎖,其他用戶可以並發讀取數據,但任何事務都不能獲取數據上的排他鎖,直到已釋放所有共享鎖。
 
共享鎖(S鎖)又稱為讀鎖,若事務T對數據對象A加上S鎖,則事務T只能讀A;其他事務只能再對A加S鎖,而不能加X鎖,直到T釋放A上的S鎖。這就保證了其他事務可以讀A,但在T釋放A上的S鎖之前不能對A做任何修改。
 
排他鎖(X):排它鎖((Exclusive lock,簡記為X鎖))又稱為寫鎖,若事務T對數據對象A加上X鎖,則只允許T讀取和修改A,其它任何事務都不能再對A加任何類型的鎖,直到T釋放A上的鎖。它防止任何其它事務獲取資源上的鎖,直到在事務的末尾將資源上的原始鎖釋放為止。在更新操作(INSERT、UPDATE 或 DELETE)過程中始終應用排它鎖。
 
兩者之間的區別:
  • 共享鎖(S鎖):如果事務T對數據A加上共享鎖后,則其他事務只能對A再加共享鎖,不能加排他鎖。獲取共享鎖的事務只能讀數據,不能修改數據。
  • 排他鎖(X鎖):如果事務T對數據A加上排他鎖后,則其他事務不能再對A加任任何類型的封鎖。獲取排他鎖的事務既能讀數據,又能修改數據。
S 鎖之間不沖突,X 鎖則為獨占鎖,所以 X 之間會沖突, X 和 S 也會沖突。
 
InnoDB 的表鎖很雞肋:
  • LOCK TABLES yes READ 是對 yes 這個表上 S 鎖。
  • LOCK TABLES yes WRITE 是對 yes 這個表上 X 鎖。
但是基本上沒用。平日的 update 、select 要用也是用行鎖了,不可能用粒度粗的表鎖。唯一能想到用上表鎖的就是 DDL 語句了,比如 ALTER TABLE 的時候,應該鎖定整個表,防止查詢和修改。但是這個 server 已經提供了一個叫 MDL 的東西,即 Metadata Locks(元數據鎖),所以已經用 MDL 來阻塞了,表鎖也就排不上用場了。真要用表鎖,估計也就是數據恢復的時候,手動鎖表還原數據了。
 
但是如果真要到用表鎖的時候,那表鎖和行鎖之間不是會沖突的嗎?如果表里面已經加了行鎖怎么辦?得一條記錄一條記錄遍歷過去找行鎖嗎?這確實是一種實現方式,但是性能太差了,假設數據庫里有上千萬的數據,這加個表鎖得找死。所以有了個叫意向鎖(Intention Locks)的東西。
  • IS(Intention Shared Lock),共享意向鎖
  • IX(Intention Exclusive Lock),獨占意向鎖。
 
3、InnoDB中的鎖
 
3.1、意向鎖
InnoDB 支持多種粒度的鎖,也就是行鎖和表鎖。為了支持多粒度鎖定,InnoDB 存儲引擎引入了意向鎖(Intention Lock)。
 
那什么是意向鎖呢?我們在這里可以舉一個例子:如果沒有意向鎖,當已經有人使用行鎖對表中的某一行進行修改時,如果另外一個請求要對全表進行修改,那么就需要對所有的行是否被鎖定進行掃描,在這種情況下,效率是非常低的;不過,在引入意向鎖之后,當有人使用行鎖對表中的某一行進行修改之前,會先為表添加意向排他鎖(IX),再為行記錄添加排他鎖(X),在這時如果有人嘗試對全表進行修改就不需要判斷表中的每一行數據是否被加鎖了,只需要通過等待意向互斥鎖被釋放就可以了。意向共享鎖可以同時並存多個,但是意向排他鎖同時只能有一個存在。
 
與上一節中提到的兩種鎖的種類相似的是,意向鎖也分為兩種:
 
意向共享鎖(IS):事務想要在獲得表中某些記錄的共享鎖,需要在表上先加意向共享鎖。
意向互斥鎖(IX):事務想要在獲得表中某些記錄的排他鎖,需要在表上先加意向排他鎖。
 
注意:
 
這里的意向鎖是表級鎖,表示的是一種意向,僅僅表示事務正在讀或寫某一行記錄,在真正加行鎖時才會判斷是否沖突。意向鎖是InnoDB自動加的,不需要用戶干預。意向鎖其實不會阻塞全表掃描之外的任何請求,它們的主要目的是為了表示是否有人請求鎖定表中的某一行數據。
 

3.2、自增長鎖AUTO-INC Locks

自增長鎖是一種特殊的表鎖機制,提升並發插入性能。對於這個鎖有幾個特點:
  • 在sql執行完就釋放鎖,並不是事務執行完。
  • 對於Insert...select大數據量插入會影響插入性能,因為會阻塞另外一個事務執行。
  • 自增算法可以配置
 
4、InnoDB鎖算法
 
InnoDB 有幾類行鎖:
4.1、記錄鎖(Record-Lock)
記錄鎖是鎖住記錄的,這里要說明的是這里鎖住的是索引記錄,而不是真正的數據記錄。
  • 如果鎖的是非主鍵索引,會在自己的索引上面加鎖之后然后再去主鍵上面加鎖鎖住.
  • 如果沒有表上沒有索引(包括沒有主鍵),則會使用隱藏的主鍵索引進行加鎖。
  • 如果要鎖的列沒有索引,則會進行全表記錄加鎖。
前面也提到,普通的 select 語句是不會對記錄加鎖的,如果要在查詢時對記錄加行鎖,可以使用下面這兩個方式:
//對讀取的記錄加共享鎖
select ... lock in share mode;
//對讀取的記錄加獨占鎖
select ... for update;

上面這兩條語句必須再一個事務中,當事務提交了,鎖就會被釋放,因此在使用這兩條語句的時候,要加上 begin、start transaction 或者 set autocommit = 0。

4.2、間隙鎖gap lock(解決幻讀)

間隙鎖顧名思義鎖間隙,不鎖記錄。鎖間隙的意思就是鎖定某一個范圍,間隙鎖又叫gap鎖,其不會阻塞其他的gap鎖,但是會阻塞插入間隙鎖,這也是用來防止幻讀的關鍵。
 
比如此時有 1、3、5、10 這四條記錄,之前的文章分析過,數據頁中還有兩條虛擬的記錄,分別是 Infimum 和 Supremum。
可以看到,記錄之前都有間隙,那間隙鎖呢,鎖的就是這個間隙!比如我把 3 和 5 之間的間隙鎖了,此時要插入 id = 4 的記錄,就會被這個間隙鎖給阻塞了,這樣就避免了幻讀的產生!也就實現了鎖定未插入的記錄的需求!
 
還有個 Next-Key Locks 就是記錄鎖+間隙鎖,像上面間隙鎖的舉例,只能鎖定(3,5) 這個區間,而 Next-Key Locks 是一個前開后閉的區間(3,5],這樣能防止查詢 id=5 的這個幻讀。
 
問:那間隙鎖之間會不會沖突?
不會,間隙鎖的唯一目的就是防止其他事務插入數據到間隙中 ,所以即使兩個間隙鎖要鎖住相同的間隙也沒有關系,因為它們的目的是一致的,所以不沖突。
 
那間隙鎖可以顯式禁用嗎?
 
可以的。間隙鎖是在事務隔離級別為可重復讀的時候生效的,如果將事務隔離級別更改為READ COMMITTED,就會禁用了,此時,間隙鎖對於搜索和索引掃描是禁用的,僅用於外鍵約束檢查和重復鍵檢查。
 
說到間隙鎖,那你知道什么是插入意向鎖嗎?
 
插入意向鎖,即 Insert Intention Locks,它也是一類間隙鎖,但是它不是鎖定間隙,而是等待某個間隙。比如上面舉例的 id = 4 的那個事務 C ,由於被間隙鎖給阻塞了,所以事務 C 會生成一個插入意向鎖,表明等待這個間隙鎖的釋放。並且插入意向鎖之間不會阻塞,因為它們的目的也是只等待這個間隙被釋放,所以插入意向鎖之間沒有沖突。
 
所以這個插入意向鎖其實沒什么用的?
 
我:確實,它的目的不在於鎖定資源防止別人訪問,我個人覺得更像是為了遵循 MySQL 的鎖代碼實現而為之。
鎖其實就是內存里面的一個結構,每個事務為某個記錄或者間隙上鎖就是創建一個鎖對象來爭搶資源。
如果某個事務沒有搶到資源,那也會生成一個鎖對象,只是狀態是等待的,而當擁有資源的事務釋放鎖之后,就會尋找正在等待當前資源的鎖結構,然后選一個讓它獲得資源並喚醒對應的事務使之得以執行。

4.3、next-key鎖

這個鎖本質是記錄鎖加上gap鎖。在RR隔離級別下(InnoDB默認),Innodb對於行的掃描鎖定都是使用此算法,但是如果查詢掃描中有唯一索引會退化成只使用記錄鎖。為什么呢? 因為唯一索引能確定行數,而其他索引不能確定行數,有可能在其他事務中會再次添加這個索引的數據會造成幻讀。
 
5、鎖帶來的問題
 
通過鎖定機制可以實現事務隔離性要求,使得事務可以並發的工作。鎖提高了並發,但是卻會帶來潛在的問題。
 
5.1、 臟讀
 
在不同的事務下,當前事務可以讀到另外事務未提交的數據。另外我們需要注意的是默認的MySQL隔離級別是REPEATABLE READ是不會發生臟讀的,臟讀發生的條件是需要事務的隔離級別為READ UNCOMMITTED,所以如果出現臟讀,可能就是這種隔離級別導致的。
 
5.2、 不可重復讀
 
指在一個事務內多次讀取同一集合的數據,但是多次讀到的數據是不一樣的,這就違反了數據庫事務的一致性的原則。但是,這跟臟讀還是有區別的,臟讀的數據是沒有提交的,但是不可重復讀的數據是已經提交的數據。
從上面的例子可以看出,在A的一次會話中,由於會話B插入了數據,導致兩次查詢的結果不一致,所以就出現了不可重復讀的問題。
 
我們需要注意的是不可重復讀讀取的數據是已經提交的數據,事務的隔離級別為READ COMMITTED,這種問題我們是可以接受的。
 
如果我們需要避免不可重復讀的問題的發生,那么我們可以使用Next-Key Lock算法(設置事務的隔離級別為READ REPEATABLE)來避免,在MySQL中,不可重復讀問題就是幻讀問題。
 
5.3、幻讀(Phantom read)
 
幻讀與不可重復讀類似。它發生在一個事務(T1)讀取了幾行數據,接着另一個並發事務(T2)插入了一些數據時。在隨后的查詢中,第一個事務(T1)就會發現多了一些原本不存在的記錄,就好像發生了幻覺一樣,所以稱為幻讀。 注意幻讀僅專指“新插入的行”,中途通過 update 更新數據而出現同一個事務前后兩次查詢的「結果集合」不一樣,這種不算幻讀。
 
在可重復讀隔離級別下,普通的查詢是快照讀(read view),是不會看到別的事務插入的數據的。
  • 可重復讀隔離級是由 MVCC(多版本並發控制)實現的,實現的方式是啟動事務后,在執行第一個查詢語句后,會創建一個視圖,然后后續的查詢語句都用這個視圖,「快照讀」讀的就是這個視圖的數據,視圖你可以理解為版本數據,這樣就使得每次查詢的數據都是一樣的。
 
MySQL 里除了普通查詢是快照讀,其他都是當前讀,比如update、insert、delete,這些語句執行前都會查詢最新版本的數據,然后再做進一步的操作。 假設要 update 一個記錄,另一個事務已經 delete 這條記錄並且 提交事務了,這樣不是會產生沖突嗎,所以 update 的時候肯定要知道最新的數據。
 
另外,select ... for update 這種查詢語句是當前讀,每次執行的時候都是讀取最新的數據。
 
因此,要討論「可重復讀」隔離級別的幻讀現象,是要建立在「當前讀」的情況下。Innodb 引擎為了解決「可重復讀」隔離級別使用「當前讀」而造成的幻讀問題,就引出了 next-key 鎖,就是記錄鎖和間隙鎖的組合。間隙鎖,鎖的就是兩個值之間的空隙,以防止其他事務在這個空隙間插入新的數據,從而避免幻讀現象。
 
注意:next-key lock 鎖的是索引,而不是數據本身,所以如果 update 語句的 where 條件沒有用到索引列,那么就會全表掃描,在一行行掃描的過程中,不僅給行加上了行鎖,還給行兩邊的空隙也加上了間隙鎖,相當於鎖住整個表,然后直到事務結束才會釋放鎖。所以在線上千萬不要執行沒有帶索引條件的 update 語句,不然會造成業務停滯。
 
5.4、丟失更新
 
指的是一個事務的更新操作會被另外一個事務的更新操作所覆蓋,從而導致數據的不一致。在當前數據庫的任何隔離級別下都不會導致丟失更新問題,要出現這個問題,在多用戶計算機系統環境下有可能出現這種問題。
 
如何避免丟失更新的問題呢,我們只需要讓事務的操作變成串行化,不要並行執行就可以。
 
我們一般使用SELECT ... FOR UPDATE語句,給操作加上一個排他X鎖。
 
5.5、for update
for update是一種行級鎖,又叫排它鎖,一旦用戶對某個行施加了行級加鎖,則該用戶可以查詢也可以更新被加鎖的數據行,其它用戶只能查詢但不能更新被加鎖的數據行.如果其它用戶想更新該表中的數據行,則也必須對該表施加行級鎖。即使多個用戶對一個表均使用了共享更新,但也不允許兩個事務同時對一個表進行更新,真正對表進行更新時,是以獨占方式鎖表,一直到提交或復原該事務為止。行鎖永遠是獨占方式鎖。
 
只有當出現如下之一的條件,才會釋放共享更新鎖:
1、執行提交(COMMIT)語句
2、退出數據庫(LOG OFF)
3、程序停止運行
 
由於InnoDB預設是Row-Level Lock,所以只有「明確」的指定主鍵,MySQL才會執行Row lock (只鎖住被選取的資料例) ,否則MySQL將會執行Table Lock (將整個資料表單給鎖住)。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM