MySQL-鎖總結

鎖機制用於管理對共享資源的併發訪問。mysql

lock和latch

在數據庫中,lock和Latch都稱爲鎖,可是二者意義不一樣。sql

latch稱爲閂鎖(shuang suo),其要求鎖定的時間必須很是短。若持續的時間長,則應用的性能會很是差。在InnoDB存儲引擎中,latch又分爲mutex互斥鎖 和 rwLock讀寫鎖。其目的是爲了保證併發線程操做臨界資源的正確性。一般沒有死鎖的檢測機制。數據庫

lock的對象是事務,用來鎖定的是數據庫中的對象,如表、頁、行。而且通常lock的對象僅在事務commit或者rollback後進行釋放。有死鎖檢測機制。併發

006tNc79gy1g4cmob7jx6j31eu0fg475.jpg

經過show engine innodb mutex能夠查看InnoDB存儲引擎的中latch,具體字段詳情以下表:高併發

![image-20190624224824680]()

鎖的類型

有幾個索引,須要分別向索引加鎖。性能

共享鎖、排他鎖

InnoDB存儲引擎實現了以下兩種標準的行級鎖:優化

共享鎖(S Lock):容許事務讀一行數據spa

排他鎖(X Lock):容許事務刪除 或 更新一行數據.net

若是一個事務T1已經獲取了行r的共享鎖,那麼另外的事務T2能夠當即得到行r的共享鎖。由於讀取並不會改變行的數據,因此能夠多個事務同時獲取共享鎖,稱這種狀況爲鎖兼容。但如有其餘的事務T3想得到行R的排他鎖,則其必須等待事務T一、T2釋放行r上面的共享鎖,稱這種狀況爲鎖不兼容。下面顯示了共享鎖和排他鎖的兼容性:線程

image-20190624225847173

從表6-3能夠看出X鎖與任何鎖都不兼容,而S鎖僅和S鎖兼容。S鎖和X鎖都是行鎖,兼容是指對同一行記錄鎖的兼容狀況。

普通 select 語句默認不加鎖,而CUD操做默認加排他鎖。

記錄鎖

Record Lock,僅鎖定一行記錄(如共享鎖、排他鎖)

  • 記錄鎖老是會去鎖定索引記錄,若是表在創建的時候,沒有設置任何一個索引,那麼InnoDB會使用隱式的主鍵來進行鎖定。
  • 查詢條件的列是惟一索引的狀況下,臨建鎖退化爲記錄鎖

間隙鎖

Gap Lock,鎖定一個範圍,但不包含記錄自己。

關閉間隙鎖的2種方式:

(1)將事務隔離級別變爲read committed

(2)將參數innodb_locks_unsafe_for_binlog設置爲1

在上述配置下,除了外鍵和惟一性檢查依然須要間隙鎖,其他狀況僅適用行鎖進行鎖定。

臨鍵鎖

Next-Key Lock,等於記錄鎖 + 臨鍵鎖,鎖定一個範圍,而且鎖定記錄自己。主要是阻止多個事務將記錄插入到同一個範圍內,從而避免幻讀。

假如一個索引有十、十一、1三、20這四個值,那麼該索引可能被鎖定的區間爲:

image-20190728200240969

若事務T1已經經過臨鍵鎖鎖定了以下範圍:
image-20190728200422526

當插入新的記錄12時,則鎖定的範圍變成:

image-20190728200453193

當查詢的索引是惟一索引的時候,InnoDB會將臨鍵鎖優化成記錄鎖,從而提升併發。這時候,將再也不由間隙鎖避免幻讀的問題,可是試驗了下,即便優化成記錄鎖,也不會有幻讀的問題,實際上是由於MVCC,在可重複讀的狀況下,SELECT操做只會查找行版本號小於當前事務版本號的記錄,其餘事務(事務開啓時間比當前事務晚)新插入的記錄版本號不知足條件,就不會查出來。

對於輔助索引,當執行相似select * from z where b = 3 for update;加鎖語句時,會加上臨鍵鎖,而且下一個鍵值的範圍也會加上間隙鎖。

值得注意的是,對於惟一鍵值的鎖定,由臨鍵鎖優化爲記錄鎖,僅存在於查詢全部的惟一索引。若惟一索引由多列組成,而查詢僅是查找多個惟一索引中的一個,那麼查詢實際上是range類型查詢,而不是point類型查詢,故InnoDB存儲引擎仍是繼續使用臨鍵鎖。

image-20190803232019608

image-20190803231957959

在InnoDB存儲引擎中,經過使用臨鍵鎖來避免不可重複讀的問題(即幻讀)。在使用臨鍵鎖的狀況下,對於索引的掃描,不只僅鎖住掃描的到索引,並且還鎖住這些索引覆蓋的範圍。所以,在這些範圍內插入都是不容許的。這樣子就避免了其餘事務在這些範圍內插入數據致使不可重複讀的問題。

意向鎖

概念:將來的某個時刻,事務可能要加共享/排它鎖了,先提早聲明一個意向

意向鎖有這樣一些特色:

(1)意向鎖是表級別的鎖

(2)意向鎖分爲:

  • 意向共享鎖(intention shared lock, IS),它預示着,事務有意向對錶中的某些行加共享S鎖
  • 意向排它鎖(intention exclusive lock, IX),它預示着,事務有意向對錶中的某些行加排它X鎖

(3)意向鎖協議:

  • 事務要得到某些行的共享鎖,必須先得到的意向共享鎖IS
  • 事務要獲取某些行的排他鎖,必須先得到的意向排他鎖IX

(4)因爲意向鎖僅僅代表意向,它實際上是比較弱的鎖,意向鎖之間並不相互互斥,而是能夠並行,其兼容互斥表以下:

​ IS IX

IS 兼容 兼容

IX 兼容 兼容

(5)既然意向鎖之間都相互兼容,那其意義在哪裏呢?它會與共享鎖/排它鎖互斥,其兼容互斥表以下:

​ S X

IS 兼容 互斥

IX 互斥 互斥

(排它鎖是很強的鎖,不與其餘類型的鎖兼容。這也很好理解,修改和刪除某一行的時候,必須得到強鎖,禁止這一行上的其餘併發,以保障數據的一致性。)

InnoDB支持多粒度鎖定,這種鎖定容許事務在行級上的鎖和表級上的鎖同時存在。爲了支持不一樣粒度上進行加鎖操做,InnoDB存儲引擎支持一種額外的鎖方式,稱之爲意向鎖。意向鎖是將鎖定的對象分爲多個層次,意向鎖意味着事務但願在更細的粒度上進行加鎖。如圖6-3所示:

006tNc79gy1g4cndxw7kpj31690u07ou.jpg

若將上鎖的對象當作一棵樹,那麼對最下層的對象上鎖,也就是對最細粒度的對象上鎖,那麼首先須要對粗粒度的對象進行上鎖。如上圖,若是須要對頁上的記錄上X鎖,那麼須要分別對數據庫A、表、頁 上意向鎖IX,最後對記錄r上排他鎖X。

若其中任何一部分致使等待,那麼該操做須要等待粗粒度鎖的完成。舉例來講,事務T1在對記錄r加X鎖以前,已有事務T2對錶1進行了S表鎖,那麼表1上面已經存在S鎖,以後事務T1試圖在表1上加IX鎖(獲取記錄r的X鎖必須先獲取表1的IX鎖),因爲不兼容,因此事務T1須要等待事務T2釋放表鎖。

![image-20190624233058036]()

插入意向鎖

對已有數據行的修改與刪除,必須增強互斥鎖X鎖,那對於數據的插入,是否還須要加這麼強的鎖,來實施互斥呢?插入意向鎖,孕育而生。

插入意向鎖,是間隙鎖(Gap Locks)的一種(因此,也是實施在索引上的),它是專門針對insert操做的。

它的用處是:多個事務,在同一個索引,同一個範圍區間插入記錄時,若是插入的位置不衝突,不會阻塞彼此。

示例

在MySQL,InnoDB,RR下:

t(id unique PK, name);

 

數據表中有數據:

10, shenjian

20, zhangsan

30, lisi

 

事務A先執行,在10與20兩條記錄中插入了一行,還未提交:

insert into t values(11, xxx);

 

事務B後執行,也在10與20兩條記錄中插入了一行:

insert into t values(12, ooo);

 

(1)會使用什麼鎖?

(2)事務B會不會被阻塞呢?

 

回答:雖然事務隔離級別是RR,雖然是同一個索引,雖然是同一個區間,但插入的記錄並不衝突,故這裏:

使用的是插入意向鎖

並不會阻塞事務B

自增鎖

自增鎖是MySQL一種特殊的鎖,若是表中存在自增字段,MySQL便會自動維護一個自增鎖。

在InnoDB存儲引擎的內存結構中,對每一個含有自增加值的表都有一個自增加計數器。當對含有自增加計數器的表進行插入操做時,這個這個計數器會被初始化,執行以下操做來獲得計數器的值:

select max(auto_inc_col) from t for update

插入操做會依據這個自增加的計數器值加1賦予自增加列。這個實現方式成爲Auto-Inc Locking。這種鎖實際上是採用一種表鎖的機制,爲了提升插入的性能,鎖不是在一個事務完成之後才釋放,而是在完成對自增加值插入的SQL語句後當即釋放。

雖然Auto-Inc Locking從必定程度上提升了併發插入的效率,但仍是存在一些性能上的問題。對於有自增加值的列的併發插入性能較差,事務必須等待前一個插入的完成(雖然不用等待事務的完成)。

從MySQL5.12版本開始,InnoDB存儲引擎提供了一種輕量級互斥量的自增加實現方式。這種方式大大提升了自增加值插入的性能。而且從該版本開始,InnoDB存儲引發提供了一個參數innodb_innodb_autoinc_lock_mode來控制自增加模式,該參數的默認值爲1。首先看下自增加的插入分類,以下圖:

image-20190722221544377

下圖展現了innodb_innodb_autoinc_lock_mode的不一樣值對自增的影響:(值爲一、2的時候,看不懂。。

image-20190722221852110

InnoDB存儲引擎中自增加的實現和MyISAM不一樣。MyISAM存儲引擎是表鎖設計,自增加不用考慮併發插入的問題在InnoDB存儲引擎中,自增加值的列必須是索引,同時必須是索引的第一個列,若是不是第一個列,則MySQL會拋出異常。MyISAM存儲引擎沒有這個問題。

參考:http://blog.itpub.net/15498/v...

外鍵與鎖

若是沒有爲外鍵顯示添加索引,InnoDB自動爲外鍵建立索引,這樣子避免表鎖。

對於外鍵值的插入或更新,首先須要查詢父表中的記錄,即select父表。可是不是使用一致性非鎖定讀,由於這樣子會發生數據不一致的問題。所以這時使用的是select…lock in share mode,即主動對父表加一個共享鎖。若是這時父表已經加了X鎖,子表上面的操做將會被阻塞,以下圖:

image-20190722230035134

image-20190722230121265

MVCC多版本

又稱爲一致性非鎖定讀。指InnoDB經過行多版本控制的方式來讀取當前執行時間數據庫中行的數據。若是讀取的行正在執行delete或者update操做,這時讀操做不會所以去等待行上鎖的釋放。相反的,InnoDB存儲引擎會去讀取行的一個快照數據。

在默認配置下,即事務的隔離界別爲REPEATABLE READ(可重複讀)模式下,InnoDB存儲引擎的SELECT操做使用一致性非鎖定讀。

快照數據是指該行的以前版本的數據,該實現是經過undo段來完成。而undo用來在事務中回滾數據,所以快照數據自己是沒有額外的開銷。此外讀取快照數據是不須要上鎖的,由於沒有事務須要對歷史的數據進行修改操做。

非鎖定度機制極大的提升了數據庫的併發性。這是InnoDB默認的讀取方式,即讀取不會佔用表上的鎖。可是在不一樣事務隔離界別下,讀取的方式不一樣,並非在每一個事務隔離界別下都是採用非鎖定的一致性讀。此外,即便都是使用非鎖定的一致性讀,可是對於快照數據的定義也是各不相同。

快照數據其實就是當前行數據以前的歷史版本,每行記錄可能有多個版本。一個行記錄可能有不止一個快照數據,通常稱這種技術爲行多版本技術,由此帶來的併發控制,稱之爲多版本併發控制 MVCC。

在事務隔離界別read committed 和 repeatable read(InnoDB默認的事務隔離界別)下,InnoDB使用非鎖定一致性讀。然而,對於快照數據的定義卻不相同。對於快照數據,非一致性讀老是讀取被鎖定行的最新一份快照數據(若是沒有被鎖定,則讀取行的最新數據;若是行鎖定了,則讀取該行的最新一個快照)。而在repeatable read事務隔離級別下,對於快照數據,非一致性讀老是讀取事務開始時的行數據版本。

MVCC的優缺點

MVCC在大多數狀況下代替了行鎖,實現了對讀的非阻塞,讀不加鎖,讀寫不衝突。缺點是每行記錄都須要額外的存儲空間,須要作更多的行維護和檢查工做。注意寫寫不能並行

MVCC的實現原理

undo log

undo log是爲回滾而用,具體內容就是copy事務前的數據庫內容(行)到undo buffer,在適合的時間把undo buffer中的內容刷新到磁盤。undo buffer與redo buffer同樣,也是環形緩衝,但當緩衝滿的時候,undo buffer中的內容會也會被刷新到磁盤;與redo log不一樣的是,磁盤上不存在單獨的undo log文件,全部的undo log均存放在主ibd數據文件中(表空間),即便客戶端設置了每表一個數據文件也是如此。在Innodb中,undo log被劃分爲多個段,具體某行的undo log就保存在某個段中,稱爲回滾段。能夠認爲undo log和回滾段是同一意思

爲了便於理解MVCC的實現原理,這裏簡單介紹一下undo log的工做過程

在不考慮redo log 的狀況下利用undo log工做的簡化過程爲:

序號    動做
1       開始事務
2         記錄數據行數據備份到undo log
3         更新數據
4         將undo log寫到磁盤
5         將數據寫到磁盤
6         提交事務

(1)爲了保證數據的持久性,數據要在事務提交以前持久化
(2)undo log的持久化必須在在數據持久化以前,這樣才能保證系統崩潰時,能夠用undo log來回滾事務

(3)Innodb經過undo log保存了已更改行的舊版本的快照。

redo log

redo log就是保存執行的SQL語句到一個指定的Log文件,當MySQL執行recovery(修復)時從新執行redo log記錄的SQL操做便可。當客戶端執行每條SQL(更新語句)時,redo log會被首先寫入log buffer;當客戶端執行COMMIT命令時,log buffer中的內容會被視狀況刷新到磁盤。redo log在磁盤上做爲一個獨立的文件存在,即Innodb的log文件。

Innodb中的隱藏列

InnoDB的內部實現中爲每一行數據增長了三個隱藏列用於實現MVCC。

列名 長度(字節) 做用
DB_TRX_ID 6 插入或更新行的最後一個事務的事務標識符。(刪除視爲更新,將其標記爲已刪除)
DB_ROLL_PTR 7 寫入回滾段的撤消日誌記錄(若行已更新,則撤消日誌記錄包含在更新行以前重建行內容所需的信息)
DB_ROW_ID 6 行標識(隱藏單調自增id)

一行記錄的結構以下:

數據列 .. DB_ROW_ID DB_TRX_ID DB_ROLL_PTR
MVCC工做過程

MVCC只在READ COMMITED 和 REPEATABLE READ 兩個隔離級別下工做。READ UNCOMMITTED老是讀取最新的數據行,而不是符合當前事務版本的數據行。而SERIALIZABLE 則會對全部讀取的行都加鎖。另外事務的版本號是遞增的。

SELECT

InnoDB 會根據兩個條件來檢查每行記錄:

  • InnoDB只查找版本(DB_TRX_ID)早於當前事務版本的數據行(行的系統版本號<=事務的系統版本號,這樣能夠確保數據行要麼是在開始以前已經存在了,要麼是事務自身插入或修改過的)
  • 行的刪除版本號(DB_ROLL_PTR)要麼未定義(未更新過),要麼大於當前事務版本號(在當前事務開始以後更新的)。這樣能夠確保事務讀取到的行,在事務開始以前未被刪除。

INSERT

InnoDB爲新插入的每一行保存當前系統版本號做爲行版本號

DELETE

InnoDB爲刪除的每一行保存當前的系統版本號做爲行刪除標識

UPDATE

innodb爲插入一行新紀錄,保存當前系統版本號做爲行版本號,同時保存當前系統版本號到原來的行做爲行刪除標示。

MVCC插入示例

image-20190803133625261

F1~F6是字段名稱,1~6是對應的數據。後面3個隱藏字段分別對應行ID、事務ID、回滾指針。

初始狀態

假若有一條新增的數據,能夠認爲行ID爲1,其餘兩個字段爲空。

事務1更改該行的值

image-20190803134019783

當事務1更改該行的值時,會進行以下操做:

  • 用排他鎖鎖定該行
  • 記錄redo log
  • 把該行修改前的值複製到undo log,即上圖中下面的行
  • 修改當前的行的值,填寫事務編號,使回滾指針指向undo log中修改的行
  • 釋放鎖

事務2更改該行的值

image-20190803134329790

與事務1相同,此時undo log中有2條記錄,而且經過回滾指針連在一塊兒。

所以,若是undo log一直不刪除,則能夠經過當前記錄的回滾指針回溯到該行建立時的初始內容,所幸的是在InnoDB中存在清理線程,它會查詢比如今最老的事務還早的undo log,並刪除它們,從而保證undo log文件不會無限增加。

事務提交

當事務正常提交時,InnoDB只須要更改事務狀態爲COMMIT便可,不須要作其餘額外的工做,而回滾則複雜一點,須要根據回滾指針找出事務修改前的版本,而且恢復。若是事務影響的行很是多,回滾則可能變得效率不高。

一致性非鎖定讀(見共享鎖、排他鎖)

在某些狀況下,用戶須要顯式的對數據庫讀取操做進行加鎖以保證數據邏輯的一致性。而這要求數據庫支持加鎖語句。InnoDB存儲引擎對於SELECT語句支持兩種一致性的鎖定度操做:

  • select … for update

    共享鎖(S鎖, share locks)。其餘事務能夠讀取數據,但不能對該數據進行修改,直到全部的共享鎖被釋放。

​ 若是事務對某行數據加上共享鎖以後,可進行讀寫操做;其餘事務能夠對該數據加共享鎖,但不能加排他鎖,且只能讀數據,不能修改數據。

  • select … lock in share mode

​ 若是事務對數據加上排他鎖以後,則其餘事務不能對該數據加任何的鎖。獲取排他鎖的事務既能讀取數據,也能修改數據。

select…for update對讀取的行記錄加一個X鎖,其餘事務不能對已鎖定的行加上任何鎖。select…lock in share mode對讀取的行記錄加一個S鎖,其餘事務能夠向被鎖定的行加S鎖,可是若是加X鎖,則會被阻塞。

對於一致性非鎖定讀,即時讀取的行已經被執行了select..for update,也是能夠進行讀取的。

若是不加篩選條件(或者篩選條件不走索引),會升級爲表鎖

索引數據重複率過高會致使全表掃描:當表中索引字段數據重複率過高,則MySQL可能會忽略索引,進行全表掃描,此時使用表鎖。可以使用 force index 強制使用索引。

參考:https://blog.csdn.net/u012099...

鎖問題

髒讀

髒數據:指的是事務對緩衝池中行記錄的修改,而且尚未提交。即事務未提交的數據。

髒讀:指當前事務能夠讀到其餘事務的未提交的數據。若是讀到了髒數據,即一個事務能夠讀到另一個事務中未提交的數據,顯然違反了事務的隔離性。

髒讀的條件:須要事務的隔離級別爲讀未提交

示例image-20190804114458983

不可重複讀

不可重複讀:指在在一個事務內屢次讀取同一個數據集合,在這個事務尚未結束時,另一個事務也訪問了同一個數據集合,而且作了一些DML操做。所以,在第一個事務的兩次讀數據之間,因爲第二個事務的修改,第一個事務兩次讀取到的數據多是不同的(具體看隔離級別)。這種稱爲不可重複讀

示例image-20190804114524677

丟失更新

丟失更新:指一個事務的更新操做被另一個事務的更新操做所覆蓋,從而致使數據的不一致。

丟失更新的實例

image-20190804120327921

解決辦法:對用戶讀取的記錄加上一個排他鎖,這樣子其餘事務就必須等待前一個事務的完成。從而避免併發問題。

解決辦法的示例

image-20190804120415922

阻塞

阻塞:事務由於等待其餘事務釋放鎖而等待

超時:等待其餘事務釋放鎖,超過超時時間,就認爲是超時。

innodb_lock_wait_timeout:用來控制超時時間,默認是50秒。能夠在MYSQL運行時進行設置。

innodb_rollback_on_timeout:用來設定是否在等待超時時對進行中的事務進行回滾操做。默認是OFF,不回滾。不能夠在MySQL啓動時進行修改。用戶在超時的狀況下,必須判斷是是否須要commit或者rollback,以後再進行下一步的操做。

死鎖

概念:死鎖是指兩個或者兩個以上的事務,因爭奪資源而形成的一種互相等待的現象。若無外力做用,全部事務都將沒法推動下去。

解決數據庫死鎖最簡單的方法:設置超時時間。即當兩個事務互相等待時,當一個等待時間超過設置的閾值時,其中一個事務進行回滾,另一個等待的事務就能繼續執行。

超時機制雖然簡單,可是其使用FIFO的方式來選擇超時回滾的事務,假如第一個超時的事務 更新了不少行,遠比第二個事務多,所以佔用了更多的undo log,這時FIFO的方式,就顯得不適用了,由於第一個事務回滾時間明顯比第二個事務回滾時間長不少。

等待圖

由於FIFO處理死鎖可能不適用,因此數據庫廣泛採用了wait-for graph(等待圖)的方式來進行死鎖檢測。和超時機制比較,這是一種更爲主動的死鎖檢測方式,InnoDB也採用了這種方式。

等待圖要求數據庫保存如下兩種信息:

(1)鎖的信息鏈表(見圖6-5)

(2)事務等待鏈表(見圖6-5)

經過上述鏈表能夠構造出一張圖,而在這個圖中存在迴路,則表明存在死鎖。在等待圖中,事務爲圖中的節點。在圖中,事務T1指向事務T2邊的定義爲:

(1)事務T1等待事務T2所佔用的資源

(2)事務之間在等待相同的資源,而事務T1在事務T2以後

image-20190804134158816

image-20190804134632126

發現死鎖後,InnoDB會立刻回滾一個事務。

鎖升級

概念:將當前鎖的粒度下降,好比說把行鎖升級爲表鎖,那樣子會致使併發性能下降。

InnoDB不是根據每一個記錄來產生行鎖的,而是根據每一個事務訪問的每一個頁對鎖進行管理的,採用的是位圖的方式,所以無論一個事務鎖住頁中一條仍是多條記錄,都是用一個鎖,其開銷一般是一致的。

image-20190804140229826

相關文章
相關標籤/搜索