MySQL 鎖機制

鎖,在現實生活中是爲咱們想要隱藏於外界所使用的一種工具。在計算機中,是協調多個進程或縣城併發訪問某一資源的一種機制。在數據庫當中,除了傳統的計算資源(CPU、RAM、I/O等等)的爭用以外,數據也是一種供許多用戶共享訪問的資源。如何保證數據併發訪問的一致性、有效性,是全部數據庫必須解決的一個問題,鎖的衝突也是影響數據庫併發訪問性能的一個重要因素。從這一角度來講,鎖對於數據庫而言就顯得尤其重要。html

一、MySQL中的鎖

MySQL中有着Lock和Latch的概念,在數據庫中,這二者均可以被稱爲「鎖」,可是二者有着大相徑庭的含義。算法

MySQL-Lock1

Latch通常稱爲閂鎖(輕量級的鎖),由於其要求鎖定的時間必須很是短。若持續的時間長,則應用的性能會很是差,在InnoDB引擎中,Latch又能夠分爲mutex(互斥量)和rwlock(讀寫鎖)。其目的是用來保證併發線程操做臨界資源的正確性,而且一般沒有死鎖檢測的機制。數據庫

Lock的對象是事務,用來鎖定的是數據庫中的對象,如表、頁、行。而且通常lock的對象僅在事務commit或rollback後進行釋放(不一樣事務隔離級別釋放的時間可能不一樣)。安全

關於Latch更詳細的講解能夠參考:關於MySQL latch爭用深刻分析與判斷,本文主要關注的是Lock鎖。併發

1.一、鎖的類型

對數據的操做其實只有兩種,也就是讀和寫,而數據庫在實現鎖時,也會對這兩種操做使用不一樣的鎖;InnoDB 實現了標準的行級鎖,也就是共享鎖(Shared Lock)和互斥鎖(Exclusive Lock)。高併發

  • 共享鎖(讀鎖),容許事務讀一行數據。
  • 排他鎖(寫鎖),容許事務刪除或更新一行數據。

而它們的名字也暗示着各自的另一個特性,共享鎖之間是兼容的,而互斥鎖與其餘任意鎖都不兼容:工具

MySQL-Lock2

稍微對它們的使用進行思考就能想明白它們爲何要這麼設計,由於共享鎖表明了讀操做、互斥鎖表明了寫操做,因此咱們能夠在數據庫中並行讀,可是隻能串行寫,只有這樣才能保證不會發生線程競爭,實現線程安全。性能

1.二、鎖的粒度

Lock鎖根據粒度主要分爲表鎖、頁鎖和行鎖。不一樣的存儲引擎擁有的鎖粒度都不一樣。大數據

MySQL-Lock3

表鎖

表級別的鎖定是MySQL各存儲引擎中最大顆粒度的鎖定機制。該鎖定機制最大的特色是實現邏輯很是簡單,帶來的系統負面影響最小。因此獲取鎖和釋放鎖的速度很快。因爲表級鎖一次會將整個表鎖定,因此能夠很好的避免困擾咱們的死鎖問題。spa

固然,鎖定顆粒度大所帶來最大的負面影響就是出現鎖定資源爭用的機率也會最高,導致並大度大打折扣。
使用表級鎖定的主要是MyISAM,MEMORY,CSV等一些非事務性存儲引擎。

表鎖的語法很簡單:

# 獲取表鎖
LOCK TABLES
    tbl_name [[AS] alias] lock_type
    [, tbl_name [[AS] alias] lock_type] ...

lock_type:
    READ [LOCAL]
  | [LOW_PRIORITY] WRITE

# 釋放表鎖
UNLOCK TABLES

 

MyISAM在執行查詢前,會自動執行表的加鎖、解鎖操做,通常狀況下不須要用戶手動加、解鎖,可是有的時候也須要顯示加鎖。好比:檢索某一個時刻t1,t2表中數據數量。

LOCK TABLE t1 read, t2 read;
select count(t1.id1) as 'sum' from t1;
select count(t2.id1) as 'sum' from t2;
UNLOCK TABLES;

 

頁鎖

頁級鎖定是MySQL中比較獨特的一種鎖定級別,在其餘數據庫管理軟件中也並非太常見。頁級鎖定的特色是鎖定顆粒度介於行級鎖定與表級鎖之間,因此獲取鎖定所須要的資源開銷,以及所能提供的併發處理能力也一樣是介於上面兩者之間。另外,頁級鎖定和行級鎖定同樣,會發生死鎖。
在數據庫實現資源鎖定的過程當中,隨着鎖定資源顆粒度的減少,鎖定相同數據量的數據所須要消耗的內存數量是愈來愈多的,實現算法也會愈來愈複雜。不過,隨着鎖定資源顆粒度的減少,應用程序的訪問請求遇到鎖等待的可能性也會隨之下降,系統總體併發度也隨之提高。
使用頁級鎖定的主要是BerkeleyDB存儲引擎。

行鎖

行級鎖定最大的特色就是鎖定對象的粒度很小,也是目前各大數據庫管理軟件所實現的鎖定顆粒度最小的。因爲鎖定顆粒度很小,因此發生鎖定資源爭用的機率也最小,可以給予應用程序儘量大的併發處理能力而提升一些須要高併發應用系統的總體性能。
雖然可以在併發處理能力上面有較大的優點,可是行級鎖定也所以帶來了很多弊端。因爲鎖定資源的顆粒度很小,因此每次獲取鎖和釋放鎖須要作的事情也更多,帶來的消耗天然也就更大了。此外,行級鎖定也最容易發生死鎖。
使用行級鎖定的主要是InnoDB存儲引擎。

總結

  • 表級鎖:開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖衝突的機率最高,併發度最低。
  • 行級鎖:開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖衝突的機率最低,併發度也最高。
  • 頁面鎖:開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定粒度界於表鎖和行鎖之間,併發度通常。

從鎖的角度來講,表級鎖更適合於以查詢爲主,只有少許按索引條件更新數據的應用,如Web應用;而行級鎖則更適合於有大量按索引條件併發更新少許不一樣數據,同時又有併發查詢的應用,如一些在線事務處理(OLTP)系統。

二、InnoDB中的鎖

意向鎖

上節提到InnoDB 支持多種粒度的鎖,也就是行鎖和表鎖。爲了支持多粒度鎖定,InnoDB 存儲引擎引入了意向鎖(Intention Lock)。

那什麼是意向鎖呢?咱們在這裏能夠舉一個例子:若是沒有意向鎖,當已經有人使用行鎖對錶中的某一行進行修改時,若是另一個請求要對全表進行修改,那麼就須要對全部的行是否被鎖定進行掃描,在這種狀況下,效率是很是低的;不過,在引入意向鎖以後,當有人使用行鎖對錶中的某一行進行修改以前,會先爲表添加意向互斥鎖(IX),再爲行記錄添加互斥鎖(X),在這時若是有人嘗試對全表進行修改就不須要判斷表中的每一行數據是否被加鎖了,只須要經過等待意向互斥鎖被釋放就能夠了。

與上一節中提到的兩種鎖的種類類似的是,意向鎖也分爲兩種:

  • 意向共享鎖(IS):事務想要在得到表中某些記錄的共享鎖,須要在表上先加意向共享鎖。
  • 意向互斥鎖(IX):事務想要在得到表中某些記錄的互斥鎖,須要在表上先加意向互斥鎖。

隨着意向鎖的加入,鎖類型之間的兼容矩陣也變得越發複雜:

MySQL-Lock5

意向鎖其實不會阻塞全表掃描以外的任何請求,它們的主要目的是爲了表示是否有人請求鎖定表中的某一行數據

行鎖的算法

InnoDB存儲引擎有3種行鎖的算法,其分別是:

  • Record Lock:單個行記錄上的鎖。
  • Gap Lock:間隙鎖,鎖定一個範圍,但不包含記錄自己。
  • Next-Key Lock:Gap Lock+Record Lock,鎖定一個範圍,而且鎖定記錄自己。

Record Lock老是會去鎖住索引記錄,若是InnoDB存儲引擎表在創建的時候沒有設置任何一個索引,那麼這時InnoDB存儲引擎會使用隱式的主鍵來進行鎖定。

Next-Key Lock是結合了Gap Lock和Record Lock的一種鎖定算法,在Next-Key Lock算法下,InnoDB對於行的查詢都是採用這種鎖定算法。例若有一個索引有10,11,13和20這4個值,那麼該索引可能被Next-Key Locking的區間爲:

MySQL-Lock4

除了Next-Key Locking,還有Previous-Key Locking技術。一樣上述的值,使用Previous-Key Locking技術,那麼可鎖定的區間爲:

MySQL-Lock6

可是不是全部索引都會加上Next-key Lock的,在查詢的列是惟一索引(包含主鍵索引)的狀況下,Next-key Lock會降級爲Record Lock。

接下來,咱們來經過一個例子解釋一下。

CREATE TABLE z (
    a INT,
    b INT,
    PRIMARY KEY(a),    // a是主鍵索引
    KEY(b)    // b是普通索引
);
INSERT INTO z select 1, 1;
INSERT INTO z select 3, 1;
INSERT INTO z select 5, 3;
INSERT INTO z select 7, 6;
INSERT INTO z select 10, 8;

 

這時候在會話A中執行 SELECT * FROM z WHERE b = 3 FOR UPDATE ,索引鎖定以下:

MySQL-Lock7

這時候會話B執行的語句落在鎖定範圍內的都會進行waiting

SELECT * FROM z WHERE a = 5 LOCK IN SHARE MODE;
INSERT INTO z SELECT 4, 2;
INSERT INTO z SELECT 6, 5;

 

用戶能夠經過如下兩種方式來顯示的關閉Gap Lock:

  • 將事務的隔離級別設爲 READ COMMITED。
  • 將參數innodb_locks_unsafe_for_binlog設置爲1。

從上面的例子能夠看出來,Gap Lock的做用是爲了阻止多個事務將記錄插入到同一個範圍內,設計它的目的是用來解決Phontom Problem(幻讀問題)。在MySQL默認的隔離級別(Repeatable Read)下,InnoDB就是使用它來解決幻讀問題。

幻讀是指在同一事務下,連續執行兩次一樣的SQL語句可能致使不一樣的結果,第二次的SQL可能會返回以前不存在的行,也就是第一次執行和第二次執行期間有其餘事務往裏插入了新的行。

一致性非鎖定讀

一致性非鎖定讀(consistent nonlocking read)是指InnoDB存儲引擎經過多版本控制(MVCC)的方式來讀取當前執行時間數據庫中行的數據。若是讀取的這行正在執行DELETE或UPDATE操做,這時讀取操做不會向XS鎖同樣去等待鎖釋放,而是會去讀一個快照數據。MVCC相關的知識我已經在另一篇文章中闡述了,這裏就不作過多原理的分析了。地址:談談MySQL InnoDB存儲引擎事務的ACID特性

MySQL-Lock18

在事務隔離級別RC和RR下,InnoDB存儲引擎使用非鎖定的一致性讀。然而對於快照數據的定義卻不一樣,在RC級別下,對於快照數據,非一致性讀老是讀取被鎖定行的最新一份快照數據。而在RR級別下,對於快照數據,非一致性讀老是讀取事務開始時的行數據版本。

下面咱們經過一個例子來看看你們是否對MVCC理解了。

MySQL-Lock19

能夠看到,第1步和第2步是很是容易理解的,而在第3步事務B插入一條新的數據後,在第4步事務A仍是查不到,也就是利用了MVCC的特性來實現。當事務B提交後,第5步的查詢在RC和RR隔離級別下的輸出是不一樣的,這個的緣由在另外一篇博客中也說到了,是由於他們建立ReadView的時機不一樣。

可是很詭異的是在第6步的時候,事務A更新了一條它看不見的記錄,而後查詢就可以查詢出來了。這裏不少人容易迷惑,不可見不表明記錄不存在,它只是利用了可見性判斷忽略了而已。更新成功以後,事務A順其天然的記錄了這條記錄的Undo log,在隨後的查詢中,由於它可以看見本身的改動這一個可見性的判斷,天然就可以查詢出來了。這裏不少名詞須要去深刻讀一下此文:談談MySQL InnoDB存儲引擎事務的ACID特性

一致性鎖定讀

前面說到,在默認隔離級別RR下,InnoDB存儲引擎的SELECT操做使用一致性非鎖定讀。可是在某些狀況下,用戶須要顯式地對數據庫讀取操做進行加鎖以保證數據邏輯的一致性。InnoDB存儲引擎對於SELECT語句支持兩種一致性的鎖定讀(locking read)操做。

  • SELECT … FOR UPDATE (X鎖)
  • SELECT … LOCK IN SHARE MODE (S鎖)

三、鎖帶來的問題

經過鎖定機制能夠實現事務隔離性要求,使得事務能夠併發的工做。鎖提升了併發,可是卻會帶來潛在的問題。不過好在有事務隔離性的要求,不一樣的隔離級別解決的鎖的問題也不一樣,這裏只進行簡單的介紹,不進行舉例分析了。

MySQL-Lock20

InnoDB存儲引擎在RR級別就已經解決了全部問題,可是它和Serializable的區別在哪裏呢?區別就在於RR級別還存在一個丟失更新問題,而SERIALIZABLE不管對於查詢仍是更新都會進行鎖定操做。

MySQL-Lock21

如圖所示,用戶原始金額爲100,若是程序中對於轉帳和存款的判斷是先查詢再更新的話就會出現丟失更新的問題,也就是後面的更新覆蓋了前面的更新。若是想避免這種問題,只能每次更新的時候金額基於表裏最新的值來作。若是必需要先查詢再更新,能夠在更新的條件裏判斷金額(樂觀鎖),也可使用隔離級別最高的SERIALIZABLE。

四、死鎖

死鎖是指兩個或兩個以上的事務在執行過程當中,因爭奪鎖資源而形成的一種互相等待的現象,這裏直接放上以前項目中遇到的一個死鎖問題以及深刻的分析:由一次線上問題帶來的MySQL死鎖問題分析,這裏就再也不贅述了。

原文

相關文章
相關標籤/搜索