Mysql數據庫鎖與事務

Mysql數據庫鎖與事務

 

鎖的類型:

對數據的操作只有兩種,讀和寫。數據庫層面在實現鎖時,也會對這兩種操作使用不同的鎖。

InnoDB實現了標準的行級鎖,即共享鎖(Shared Lock)和互斥鎖(Exclusive Lock)。

 

共享鎖:可以理解爲讀鎖,允許事務讀數據。

排他鎖:可以理解爲寫鎖,允許事務刪除或更新一行數據。

從名字上看,共享鎖是可以共同擁有的,而排他鎖,顧名思義只能一個擁有鎖,排斥其他者爭奪鎖。

https://img-blog.csdn.net/20180820060458567?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0JydWNlTGVlTnVtYmVyT25l/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70

 

鎖的粒度:

鎖根據粒度主要分爲表鎖、頁鎖和行鎖。不同的存儲引擎擁有鎖的粒度不一樣,最常見的InnoDB就支持表鎖和行鎖,InnoDB默認使用行級鎖。

 

表級鎖:是MySQL各存儲引擎中最大粒度的鎖定機制,該鎖機制實現邏輯簡單,獲取和釋放鎖的速度很快。表級鎖一次性將整個表鎖定,能夠很好的避免了死鎖的問題。但有個不好的地方是鎖的粒度大,出現爭奪鎖定資源的概率也高,會使併發度大打折扣。

 

表鎖的語法:

# 獲取表鎖

LOCK TABLES

    tbl_name [[AS] alias] lock_type

    [, tbl_name [[AS] alias] lock_type] ...

 

lock_type:

    READ [LOCAL] | [LOW_PRIORITY] WRITE

# 釋放表鎖

UNLOCK TABLES

 

行鎖:最小的鎖定粒度,在數據行級加鎖。由於鎖定粒度小,發生鎖定資源爭用的概率小,所以能夠給與應用程序儘可能大的併發處理能力,提高併發性能。但行級鎖因此帶來了弊端,鎖的粒度小,獲取鎖和釋放鎖的操作更加頻繁,帶來的消耗就更大,此外,行級鎖也最容易發生死鎖。

 

意向鎖(Intention Lock):是InnoDB爲支持多粒度鎖而引入的。

意向鎖也分兩種:

意向共享鎖:事務想要獲得表中某些記錄的共享鎖,需要在表上先加意向共享鎖。

意向互斥鎖:事務想要獲得表中某些記錄的互斥鎖,需要在表上先加意向互斥鎖。

爲什麼要引入意向鎖?可以思考下,當已有事務使用行級鎖對錶中某一行進行修改時,如果另一個事務要對全表進行修改,那麼就需要對所有的行是否被鎖定進行掃描,這樣效率非常低。如果引入意向鎖,當有人使用行鎖對錶中某一行進行修改時,會先爲表添加意向互斥鎖(IX),再爲行記錄添加互斥鎖(X),在這時如果有人嘗試對全表進行修改就不需要判斷表中每一行數據是否被加鎖了,只需要等待意向互斥鎖被釋放就可以了。

 

這時鎖類型的兼容性如下:

https://img-blog.csdn.net/20180820061258861?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0JydWNlTGVlTnVtYmVyT25l/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70

可以看出,意向鎖是爲了表示是否有人請求鎖定表中某一行數據,它並不會阻塞全表掃描之外的任何請求。

 

InnoDB行鎖的算法:

Record Lock:單個行記錄上的鎖。

Gap Lock: 間隙鎖,鎖定一個範圍,但不包含記錄本身

Next-Key Lock: 就是Gap Lock+Record Lock, 鎖定一個範圍,並鎖定記錄本身。InnoDB對於行的查詢都是採用這種鎖定算法,不過如果查詢列是唯一索引(包含主鍵索引)的情況下,Next-key Lock會降級爲Record Lock。

(爲什麼要採用Next-Key Lock呢?是爲了解決幻讀問題,阻止多個事務將記錄插入到同一個範圍內)

InnoDB行鎖是通過索引上的索引項來實現的,這一點MySQLOracle不同,後者是通過在數據中對相應數據行加鎖來實現的。InnoDB這種行鎖實現特點意味者:只有通過索引條件檢索數據,InnoDB纔會使用行級鎖,否則,InnoDB將使用表鎖!

 

舉例說明下:

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 ,索引鎖定如下:

https://img-blog.csdn.net/20180820061653399?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0JydWNlTGVlTnVtYmVyT25l/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70

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

關閉Gap Lock:

將事務的隔離級別設置爲 READ COMMITED

將參數Innodb_locks_unsafe_for_binlog設置爲1。

 

樂觀鎖與悲觀鎖是另一種對鎖的分類:

樂觀鎖:

從名字上看,就是想法很樂觀,默認不會產生衝突。對數據庫進行操作時,不進行任何其他的處理(包括加鎖),只有在更新操作時,再判斷是否有衝突,所以不需要依賴數據庫底層的鎖機制。典型的實現就是MVCC(多版本控制)。

具體實現:在數據庫表中添加字段version,每次更新前先查詢出version值,然後在更新時判斷之前查詢出來的version值是否等於當前表中的version值,若相等,則表示在此期間沒有其他程序對該條數據進行更改,則可以執行更新操作,同時將version值加1。若不相等,則說明在此期間有程序對這條數據進行操作,則不進行更新操作。

 

悲觀鎖:

想法很悲觀,認爲每次操作時,都會出現數據衝突,所以每次操作數據時,都需要加鎖來保證數據的一致性。悲觀鎖往往利用到數據庫的鎖機制(可想而知,只有數據庫層提供的鎖機制才能真正保證數據訪問的排他性,如果是在系統層面加鎖的話,則會導致集羣環境中多服務器操作導致數據的不一致)。我們所說的行鎖、表鎖等都是悲觀鎖。

死鎖:

死鎖是指兩個或兩個以上的事務在執行過程中,因爭奪資源而造成的一種互相等待的現象。

死鎖報錯現象:

Deadlock found when trying to get lock

InnoDB會主動探知到死鎖,並回滾某一苦苦等待的事務。那麼這裏就有兩個問題:

InnoDB是怎麼探知死鎖?以及根據怎麼的方案選擇事務回滾?

 

探知死鎖:

我們將每個事務看爲一個節點,當節點1需要等待節點2的資源時,就生成一條有向邊指向節點2,最後形成一個有向圖。我們只要檢測這個有向圖是否出現環路即可,出現環路就是死鎖!這就是wait-for graph算法。

http://benjaminwhx.com/images/sisuo3.png

 

回滾方案:

直觀方法是在兩個事務相互等待時,當一個等待時間超過設置的某一閥值時,對其中一個事務進行回滾,另一個事務就能繼續執行。這種方法簡單有效,在innodb中,參數innodb_lock_wait_timeout用來設置超時時間。選擇回滾事務的依據:看哪個事務的權重最小,事務權重的計算方法:事務加的鎖最少;事務寫的日誌最少;事務開啓的時間最晚。

 

事務:

事務的ACID原則(原子性(atomicity)、一致性(consistency)、隔離性(isolation)、持久性(durability)

原子性:標識事務是否完全執行。一個事務完全執行,如果出錯,事務不能完成它的全部任務,則返回到事務的開始前或保存點的狀態。

一致性:事務執行前後系統處於一致狀態,事務出錯回滾後,系統也是處在一致狀態。

隔離性:如果有兩個事務,運行在相同的時間內,執行相同的功能,事務的隔離性將確保每一事務在系統中認爲只有該事務在使用系統。有時也稱爲串行化,爲了防止事務操作間的混淆,必須串行化或序列化請求,使得在同一時間僅有一個請求用於同一數據。

持久性:一旦事務執行成功,在系統中產生的所有變化將是永久的。

而我們常說的隔離性有對應的隔離級別,Mysql規定的隔離級別有4種:

READ UNCOMMITTED(未提交讀):在此級別,事務的修改,即使沒有提交,對其他事務都是可見的。事務可以讀取未提交的數據,也就是會產生髒讀。

 

READ COMMINTTED(讀已提交):大多數數據庫系統的默認級別就是它,但是Mysql不是。事務能夠在事務開始期間讀到其他事務提交的結果,會導致一個事務裏對同一條數據的多次查詢可能會得到不同的結果,也就是會產生不可重複讀的問題。

 

 

REPEATABLE READ(可重複讀):Mysql默認級別,它能夠解決不可重複讀的問題,但是在一個事務裏對一段數據的多次讀取可能會導致不同的結果,也就是幻讀的問題(主要是其他事務插入數據,導致多次讀取結果不一樣)。但是InnoDB完美解決的這個幻讀的問題,主要採用Next-key Lock算法,對間隙進行加鎖。

 

SERIALIZABLE(可串行化):強制事務串行執行,避免了所有的問題,但是無併發。

爲什麼InnoDB能夠保證原子性?用什麼方式?

在事務裏任何對數據的修改都會寫一個Undo log,然後進行數據的修改,如果出現錯誤或者用戶需要回滾的時候可以利用undo log的備份數據恢復到事務開始之前的狀態。

爲什麼InnoDB能夠保證持久性?用什麼方式?

在一個事務中的每一次SQL操作之後都會寫入一個redo log到buffer中,在最後COMMIT的時候,必須先將該事務的所有日誌寫入到redo log file進行持久化(這裏的寫入是順序寫的),待事務的COMMIT操作完成纔算完成。即使COMMIT後數據庫有任何的問題,在下次重啓後依然能夠通過redo log的checkpoint進行恢復。

爲什麼InnoDB能夠保證一致性?用的什麼方式?

在事務處理的ACID屬性中,一致性是最基本的屬性,其它的三個屬性都爲了保證一致性而存在的。

爲了保證併發情況下的一致性,引入了隔離性,即保證每一個事務能夠看到的數據總是一致的,就好象其它併發事務並不存在一樣。用術語來說,就是多個事務併發執行後的狀態,和它們串行執行後的狀態是等價的。

爲什麼RU級別會發生髒讀,而其他的隔離級別能夠避免?

RU級別的操作其實就是對事務內的每一條更新語句對應的行記錄加上讀寫鎖來操作,而不把一個事務當成一個整體來加鎖,所以會導致髒讀。但是RC和RR能夠通過MVCC來保證記錄只有在最後COMMIT後纔會讓別的事務看到。

爲什麼RC級別不能重複讀,而RR級別能夠避免?

通過MVCC機制來做的,在RC事務隔離級別下,每次語句執行都關閉數據快照,然後重新創建一份數據快照。而在RR下,事務開始後第一個讀操作創建數據快照,一直到事務結束關閉。

爲什麼InnoDB的RR級別能夠防止幻讀?

這個是因爲RR隔離級別使用了Next-key Lock這麼個東東,也就是Gap Lock+Record Lock的方式來進行間隙鎖定,也就是鎖定了一個範圍,避免了幻讀。

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章