談?wù)凪ySQL的鎖

鎖,在現(xiàn)實(shí)生活中是為我們想要隱藏于外界所使用的一種工具。在計(jì)算機(jī)中,是協(xié)調(diào)多個(gè)進(jìn)程或縣城并發(fā)訪問(wèn)某一資源的一種機(jī)制。在數(shù)據(jù)庫(kù)當(dāng)中,除了傳統(tǒng)的計(jì)算資源(CPU、RAM、I/O等等)的爭(zhēng)用之外,數(shù)據(jù)也是一種供許多用戶共享訪問(wèn)的資源。如何保證數(shù)據(jù)并發(fā)訪問(wèn)的一致性、有效性,是所有數(shù)據(jù)庫(kù)必須解決的一個(gè)問(wèn)題,鎖的沖突也是影響數(shù)據(jù)庫(kù)并發(fā)訪問(wèn)性能的一個(gè)重要因素。從這一角度來(lái)說(shuō),鎖對(duì)于數(shù)據(jù)庫(kù)而言就顯得尤為重要。

1、MySQL中的鎖

MySQL中有著Lock和Latch的概念,在數(shù)據(jù)庫(kù)中,這兩者都可以被稱為“鎖”,但是兩者有著截然不同的含義。

MySQL-Lock1

Latch一般稱為閂鎖(輕量級(jí)的鎖),因?yàn)槠湟箧i定的時(shí)間必須非常短。若持續(xù)的時(shí)間長(zhǎng),則應(yīng)用的性能會(huì)非常差,在InnoDB引擎中,Latch又可以分為mutex(互斥量)和rwlock(讀寫鎖)。其目的是用來(lái)保證并發(fā)線程操作臨界資源的正確性,并且通常沒(méi)有死鎖檢測(cè)的機(jī)制。

Lock的對(duì)象是事務(wù),用來(lái)鎖定的是數(shù)據(jù)庫(kù)中的對(duì)象,如表、頁(yè)、行。并且一般lock的對(duì)象僅在事務(wù)commit或rollback后進(jìn)行釋放(不同事務(wù)隔離級(jí)別釋放的時(shí)間可能不同)。

關(guān)于Latch更詳細(xì)的講解可以參考:關(guān)于MySQL latch爭(zhēng)用深入分析與判斷,本文主要關(guān)注的是Lock鎖。

1.1、鎖的類型

對(duì)數(shù)據(jù)的操作其實(shí)只有兩種,也就是讀和寫,而數(shù)據(jù)庫(kù)在實(shí)現(xiàn)鎖時(shí),也會(huì)對(duì)這兩種操作使用不同的鎖;InnoDB 實(shí)現(xiàn)了標(biāo)準(zhǔn)的行級(jí)鎖,也就是共享鎖(Shared Lock)和互斥鎖(Exclusive Lock)。

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

而它們的名字也暗示著各自的另外一個(gè)特性,共享鎖之間是兼容的,而互斥鎖與其他任意鎖都不兼容:

[圖片上傳失敗...(image-22f0b4-1525442928057)]

稍微對(duì)它們的使用進(jìn)行思考就能想明白它們?yōu)槭裁匆@么設(shè)計(jì),因?yàn)楣蚕礞i代表了讀操作、互斥鎖代表了寫操作,所以我們可以在數(shù)據(jù)庫(kù)中并行讀,但是只能串行寫,只有這樣才能保證不會(huì)發(fā)生線程競(jìng)爭(zhēng),實(shí)現(xiàn)線程安全。

1.2、鎖的粒度

Lock鎖根據(jù)粒度主要分為表鎖、頁(yè)鎖和行鎖。不同的存儲(chǔ)引擎擁有的鎖粒度都不同。

MySQL-Lock3

表鎖

表級(jí)別的鎖定是MySQL各存儲(chǔ)引擎中最大顆粒度的鎖定機(jī)制。該鎖定機(jī)制最大的特點(diǎn)是實(shí)現(xiàn)邏輯非常簡(jiǎn)單,帶來(lái)的系統(tǒng)負(fù)面影響最小。所以獲取鎖和釋放鎖的速度很快。由于表級(jí)鎖一次會(huì)將整個(gè)表鎖定,所以可以很好的避免困擾我們的死鎖問(wèn)題。

當(dāng)然,鎖定顆粒度大所帶來(lái)最大的負(fù)面影響就是出現(xiàn)鎖定資源爭(zhēng)用的概率也會(huì)最高,致使并大度大打折扣。
使用表級(jí)鎖定的主要是MyISAM,MEMORY,CSV等一些非事務(wù)性存儲(chǔ)引擎。

表鎖的語(yǔ)法很簡(jiǎn)單:

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

lock_type:
    READ [LOCAL]
  | [LOW_PRIORITY] WRITE

# 釋放表鎖
UNLOCK TABLES

MyISAM在執(zhí)行查詢前,會(huì)自動(dòng)執(zhí)行表的加鎖、解鎖操作,一般情況下不需要用戶手動(dòng)加、解鎖,但是有的時(shí)候也需要顯示加鎖。比如:檢索某一個(gè)時(shí)刻t1,t2表中數(shù)據(jù)數(shù)量。

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

頁(yè)鎖

頁(yè)級(jí)鎖定是MySQL中比較獨(dú)特的一種鎖定級(jí)別,在其他數(shù)據(jù)庫(kù)管理軟件中也并不是太常見。頁(yè)級(jí)鎖定的特點(diǎn)是鎖定顆粒度介于行級(jí)鎖定與表級(jí)鎖之間,所以獲取鎖定所需要的資源開銷,以及所能提供的并發(fā)處理能力也同樣是介于上面二者之間。另外,頁(yè)級(jí)鎖定和行級(jí)鎖定一樣,會(huì)發(fā)生死鎖。
在數(shù)據(jù)庫(kù)實(shí)現(xiàn)資源鎖定的過(guò)程中,隨著鎖定資源顆粒度的減小,鎖定相同數(shù)據(jù)量的數(shù)據(jù)所需要消耗的內(nèi)存數(shù)量是越來(lái)越多的,實(shí)現(xiàn)算法也會(huì)越來(lái)越復(fù)雜。不過(guò),隨著鎖定資源顆粒度的減小,應(yīng)用程序的訪問(wèn)請(qǐng)求遇到鎖等待的可能性也會(huì)隨之降低,系統(tǒng)整體并發(fā)度也隨之提升。
使用頁(yè)級(jí)鎖定的主要是BerkeleyDB存儲(chǔ)引擎。

行鎖

行級(jí)鎖定最大的特點(diǎn)就是鎖定對(duì)象的粒度很小,也是目前各大數(shù)據(jù)庫(kù)管理軟件所實(shí)現(xiàn)的鎖定顆粒度最小的。由于鎖定顆粒度很小,所以發(fā)生鎖定資源爭(zhēng)用的概率也最小,能夠給予應(yīng)用程序盡可能大的并發(fā)處理能力而提高一些需要高并發(fā)應(yīng)用系統(tǒng)的整體性能。
雖然能夠在并發(fā)處理能力上面有較大的優(yōu)勢(shì),但是行級(jí)鎖定也因此帶來(lái)了不少弊端。由于鎖定資源的顆粒度很小,所以每次獲取鎖和釋放鎖需要做的事情也更多,帶來(lái)的消耗自然也就更大了。此外,行級(jí)鎖定也最容易發(fā)生死鎖。
使用行級(jí)鎖定的主要是InnoDB存儲(chǔ)引擎。

總結(jié)

  • 表級(jí)鎖:開銷小,加鎖快;不會(huì)出現(xiàn)死鎖;鎖定粒度大,發(fā)生鎖沖突的概率最高,并發(fā)度最低。
  • 行級(jí)鎖:開銷大,加鎖慢;會(huì)出現(xiàn)死鎖;鎖定粒度最小,發(fā)生鎖沖突的概率最低,并發(fā)度也最高。
  • 頁(yè)面鎖:開銷和加鎖時(shí)間界于表鎖和行鎖之間;會(huì)出現(xiàn)死鎖;鎖定粒度界于表鎖和行鎖之間,并發(fā)度一般。

從鎖的角度來(lái)說(shuō),表級(jí)鎖更適合于以查詢?yōu)橹?,只有少量按索引條件更新數(shù)據(jù)的應(yīng)用,如Web應(yīng)用;而行級(jí)鎖則更適合于有大量按索引條件并發(fā)更新少量不同數(shù)據(jù),同時(shí)又有并發(fā)查詢的應(yīng)用,如一些在線事務(wù)處理(OLTP)系統(tǒng)。

2、InnoDB中的鎖

意向鎖

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

那什么是意向鎖呢?我們?cè)谶@里可以舉一個(gè)例子:如果沒(méi)有意向鎖,當(dāng)已經(jīng)有人使用行鎖對(duì)表中的某一行進(jìn)行修改時(shí),如果另外一個(gè)請(qǐng)求要對(duì)全表進(jìn)行修改,那么就需要對(duì)所有的行是否被鎖定進(jìn)行掃描,在這種情況下,效率是非常低的;不過(guò),在引入意向鎖之后,當(dāng)有人使用行鎖對(duì)表中的某一行進(jìn)行修改之前,會(huì)先為表添加意向互斥鎖(IX),再為行記錄添加互斥鎖(X),在這時(shí)如果有人嘗試對(duì)全表進(jìn)行修改就不需要判斷表中的每一行數(shù)據(jù)是否被加鎖了,只需要通過(guò)等待意向互斥鎖被釋放就可以了。

與上一節(jié)中提到的兩種鎖的種類相似的是,意向鎖也分為兩種:

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

隨著意向鎖的加入,鎖類型之間的兼容矩陣也變得愈加復(fù)雜:

MySQL-Lock5

意向鎖其實(shí)不會(huì)阻塞全表掃描之外的任何請(qǐng)求,它們的主要目的是為了表示是否有人請(qǐng)求鎖定表中的某一行數(shù)據(jù)。

行鎖的算法

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

  • Record Lock:?jiǎn)蝹€(gè)行記錄上的鎖。
  • Gap Lock:間隙鎖,鎖定一個(gè)范圍,但不包含記錄本身。
  • Next-Key Lock:Gap Lock+Record Lock,鎖定一個(gè)范圍,并且鎖定記錄本身。

Record Lock總是會(huì)去鎖住索引記錄,如果InnoDB存儲(chǔ)引擎表在建立的時(shí)候沒(méi)有設(shè)置任何一個(gè)索引,那么這時(shí)InnoDB存儲(chǔ)引擎會(huì)使用隱式的主鍵來(lái)進(jìn)行鎖定。

Next-Key Lock是結(jié)合了Gap Lock和Record Lock的一種鎖定算法,在Next-Key Lock算法下,InnoDB對(duì)于行的查詢都是采用這種鎖定算法。例如有一個(gè)索引有10,11,13和20這4個(gè)值,那么該索引可能被Next-Key Locking的區(qū)間為:

[圖片上傳失敗...(image-fc3928-1525442928057)]

除了Next-Key Locking,還有Previous-Key Locking技術(shù)。同樣上述的值,使用Previous-Key Locking技術(shù),那么可鎖定的區(qū)間為:

MySQL-Lock6

但是不是所有索引都會(huì)加上Next-key Lock的,在查詢的列是唯一索引(包含主鍵索引)的情況下,Next-key Lock會(huì)降級(jí)為Record Lock。

接下來(lái),我們來(lái)通過(guò)一個(gè)例子解釋一下。

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;

這時(shí)候在會(huì)話A中執(zhí)行 SELECT * FROM z WHERE b = 3 FOR UPDATE ,索引鎖定如下:

MySQL-Lock7

這時(shí)候會(huì)話B執(zhí)行的語(yǔ)句落在鎖定范圍內(nèi)的都會(huì)進(jìn)行waiting

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

用戶可以通過(guò)以下兩種方式來(lái)顯示的關(guān)閉Gap Lock:

  • 將事務(wù)的隔離級(jí)別設(shè)為 READ COMMITED。
  • 將參數(shù)innodb_locks_unsafe_for_binlog設(shè)置為1。

從上面的例子可以看出來(lái),Gap Lock的作用是為了阻止多個(gè)事務(wù)將記錄插入到同一個(gè)范圍內(nèi),設(shè)計(jì)它的目的是用來(lái)解決Phontom Problem(幻讀問(wèn)題)。在MySQL默認(rèn)的隔離級(jí)別(Repeatable Read)下,InnoDB就是使用它來(lái)解決幻讀問(wèn)題。

幻讀是指在同一事務(wù)下,連續(xù)執(zhí)行兩次同樣的SQL語(yǔ)句可能導(dǎo)致不同的結(jié)果,第二次的SQL可能會(huì)返回之前不存在的行,也就是第一次執(zhí)行和第二次執(zhí)行期間有其他事務(wù)往里插入了新的行。

一致性非鎖定讀

一致性非鎖定讀(consistent nonlocking read)是指InnoDB存儲(chǔ)引擎通過(guò)多版本控制(MVCC)的方式來(lái)讀取當(dāng)前執(zhí)行時(shí)間數(shù)據(jù)庫(kù)中行的數(shù)據(jù)。如果讀取的這行正在執(zhí)行DELETE或UPDATE操作,這時(shí)讀取操作不會(huì)向XS鎖一樣去等待鎖釋放,而是會(huì)去讀一個(gè)快照數(shù)據(jù)。MVCC相關(guān)的知識(shí)我已經(jīng)在另外一篇文章中闡述了,這里就不做過(guò)多原理的分析了。地址:談?wù)凪ySQL InnoDB存儲(chǔ)引擎事務(wù)的ACID特性

MySQL-Lock18

在事務(wù)隔離級(jí)別RC和RR下,InnoDB存儲(chǔ)引擎使用非鎖定的一致性讀。然而對(duì)于快照數(shù)據(jù)的定義卻不同,在RC級(jí)別下,對(duì)于快照數(shù)據(jù),非一致性讀總是讀取被鎖定行的最新一份快照數(shù)據(jù)。而在RR級(jí)別下,對(duì)于快照數(shù)據(jù),非一致性讀總是讀取事務(wù)開始時(shí)的行數(shù)據(jù)版本。

下面我們通過(guò)一個(gè)例子來(lái)看看大家是否對(duì)MVCC理解了。

MySQL-Lock19

可以看到,第1步和第2步是非常容易理解的,而在第3步事務(wù)B插入一條新的數(shù)據(jù)后,在第4步事務(wù)A還是查不到,也就是利用了MVCC的特性來(lái)實(shí)現(xiàn)。當(dāng)事務(wù)B提交后,第5步的查詢?cè)赗C和RR隔離級(jí)別下的輸出是不同的,這個(gè)的原因在另一篇博客中也說(shuō)到了,是因?yàn)樗麄儎?chuàng)建ReadView的時(shí)機(jī)不同。

但是很詭異的是在第6步的時(shí)候,事務(wù)A更新了一條它看不見的記錄,然后查詢就能夠查詢出來(lái)了。這里很多人容易迷惑,不可見不代表記錄不存在,它只是利用了可見性判斷忽略了而已。更新成功之后,事務(wù)A順其自然的記錄了這條記錄的Undo log,在隨后的查詢中,因?yàn)樗軌蚩匆娮约旱母膭?dòng)這一個(gè)可見性的判斷,自然就能夠查詢出來(lái)了。這里很多名詞需要去深入讀一下此文:談?wù)凪ySQL InnoDB存儲(chǔ)引擎事務(wù)的ACID特性

一致性鎖定讀

前面說(shuō)到,在默認(rèn)隔離級(jí)別RR下,InnoDB存儲(chǔ)引擎的SELECT操作使用一致性非鎖定讀。但是在某些情況下,用戶需要顯式地對(duì)數(shù)據(jù)庫(kù)讀取操作進(jìn)行加鎖以保證數(shù)據(jù)邏輯的一致性。InnoDB存儲(chǔ)引擎對(duì)于SELECT語(yǔ)句支持兩種一致性的鎖定讀(locking read)操作。

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

3、鎖帶來(lái)的問(wèn)題

通過(guò)鎖定機(jī)制可以實(shí)現(xiàn)事務(wù)隔離性要求,使得事務(wù)可以并發(fā)的工作。鎖提高了并發(fā),但是卻會(huì)帶來(lái)潛在的問(wèn)題。不過(guò)好在有事務(wù)隔離性的要求,不同的隔離級(jí)別解決的鎖的問(wèn)題也不同,這里只進(jìn)行簡(jiǎn)單的介紹,不進(jìn)行舉例分析了。

MySQL-Lock20

InnoDB存儲(chǔ)引擎在RR級(jí)別就已經(jīng)解決了所有問(wèn)題,但是它和Serializable的區(qū)別在哪里呢?區(qū)別就在于RR級(jí)別還存在一個(gè)丟失更新問(wèn)題,而SERIALIZABLE無(wú)論對(duì)于查詢還是更新都會(huì)進(jìn)行鎖定操作。

MySQL-Lock21

如圖所示,用戶原始金額為100,如果程序中對(duì)于轉(zhuǎn)賬和存款的判斷是先查詢?cè)俑碌脑捑蜁?huì)出現(xiàn)丟失更新的問(wèn)題,也就是后面的更新覆蓋了前面的更新。如果想避免這種問(wèn)題,只能每次更新的時(shí)候金額基于表里最新的值來(lái)做。如果必須要先查詢?cè)俑?,可以在更新的條件里判斷金額(樂(lè)觀鎖),也可以使用隔離級(jí)別最高的SERIALIZABLE。

4、死鎖

死鎖是指兩個(gè)或兩個(gè)以上的事務(wù)在執(zhí)行過(guò)程中,因爭(zhēng)奪鎖資源而造成的一種互相等待的現(xiàn)象,這里直接放上之前項(xiàng)目中遇到的一個(gè)死鎖問(wèn)題以及深入的分析:由一次線上問(wèn)題帶來(lái)的MySQL死鎖問(wèn)題分析,這里就不再贅述了。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • MySQL技術(shù)內(nèi)幕:InnoDB存儲(chǔ)引擎(第2版) 姜承堯 第1章 MySQL體系結(jié)構(gòu)和存儲(chǔ)引擎 >> 在上述例子...
    沉默劍士閱讀 7,620評(píng)論 0 16
  • 當(dāng)一個(gè)系統(tǒng)訪問(wèn)量上來(lái)的時(shí)候,不只是數(shù)據(jù)庫(kù)性能瓶頸問(wèn)題了,數(shù)據(jù)庫(kù)數(shù)據(jù)安全也會(huì)浮現(xiàn),這時(shí)候合理使用數(shù)據(jù)庫(kù)鎖機(jī)制就顯得異...
    初來(lái)的雨天閱讀 3,687評(píng)論 0 22
  • 一、概述 數(shù)據(jù)庫(kù)鎖定機(jī)制簡(jiǎn)單來(lái)說(shuō),就是數(shù)據(jù)庫(kù)為了保證數(shù)據(jù)的一致性,而使各種共享資源在被并發(fā)訪問(wèn)變得有序所設(shè)計(jì)的一種...
    忘憂谷主閱讀 637評(píng)論 0 3
  • 李紅麗 焦點(diǎn)網(wǎng)絡(luò)初七 堅(jiān)持分享第39天 畫面上一只手拿著杯子,往另一個(gè)杯子里倒水,水的顏色是藍(lán)色的,兩個(gè)杯子里的水...
    麗日晴空閱讀 282評(píng)論 0 0
  • 原材料:面粉,酵母,蔥花,油 制作過(guò)程: 1、酵母用溫水花開,和面,放在溫暖出發(fā)酵至兩倍大(里面都是大蜂窩的時(shí)候就...
    ToBeMself閱讀 510評(píng)論 3 2

友情鏈接更多精彩內(nèi)容