淺析mysql事務與鎖機制


先回顧一下概念性問題——

什麼是事務?

官方點說,事務就是數據庫管理系統執行過程中的一個邏輯單位,由一個有限的數據庫操作作序列構成。
通俗點說,事務就是一組操作要麼同時成功要麼同時失敗。

事務的四大特性(ACID):

1.原子性(atomicity):一個事務必須視爲一個不可分割的最小工作單元,整個事務中的所有操作要麼全部提交成功,要麼全部失敗回滾,對於一個事務來說,不可能只執行其中的一部分操作,這就是事務的原子性。

2.一致性(consistency):數據庫總數從一個一致性的狀態轉換到另一個一致性的狀態。

3.隔離性(isolation):一個事務所做的修改在最終提交以前,對其他事務是不可見的。

4.持久性(durability):一旦事務提交,則其所做的修改就會永久保存到數據庫中。此時即使系統崩潰,修改的數據也不會丟失。

事務併發帶來的問題:

1.髒讀:髒讀是指一個事務在處理過程中讀取了另一個事務未提交的數據。
2.不可重複讀:指的是在一個事務處理中讀取到其他事務修改或刪除並提交的數據,導致多次讀取結果不一致。
3.幻讀:指的是在一個事務處理中讀取到其他事務插入並提交的數據,導致多次讀取結果不一致。
事務併發帶來的三大問題其實都是數據庫讀一致性問題,必須由數據庫提供一定的事務隔離機制來解決。

mysql事務的隔離級別:

事務隔離級別 髒讀 不可重複讀 幻讀
讀未提交(read-uncommitted) 可能 可能 可能
不可重複讀(read-committed) 不可能 可能 可能
可重複讀(repeatable-read) 不可能 不可能 在InnoDB中不可能
串行化(serializable) 不可能 不可能 不可能

爲什麼InnoDB中在rr級別的時候就解決了幻讀這個問題?

MVCC與LBCC

如果要解決讀一致性問題,保證一個事務前後讀取數據結果的一致性,實現事務隔離應該怎麼做?
簡單思考下解決方案:
LBCC(全稱Lock Based Concyrrency Control):在讀取數據前對其加鎖,阻止其他事務對數據進行修改。
MVCC(全稱Multi Version Concurrency Control):生成一個數據請求時間點的一致性數據快照(snapshot),並用這個快照來提供一定級別(語句級或者事務級)的一致性讀取。

InnoDB中怎麼實現的MVCC

其實InnoDB中,自動爲每一行數據添加了三個隱藏字段:
DB_ROW_ID(6字節):行標識,前邊的文章中提到過這個字段。
DB_TRX_ID(6字節):創建版本號,插入或更新行的最後一個事務的id,自動遞增。
DB_ROLL_PIR(7字節):刪除版本號,用於回滾。

主要記住,在InnoDB中,一個事務開始時,會生成一個當前時間點的數據快照(可以理解爲一個副本),只要事務不結束,他就只能在這個數據快照裏查找數據,也就是隻能查找到創建版本號小於當前事務id的數據和刪除版本號大於當前事務id的數據(或者是在創建版本號小於當前事務id的前提下,沒有刪除版本號的數據)

在InnoDB中MVCC和鎖是相互配合使用的,下邊就看看鎖的東西。

表鎖與行鎖的區別:
鎖定粒度:表鎖 > 行鎖
加鎖效率:表鎖 > 行鎖
衝突概率:表鎖 > 行鎖
併發性能:表鎖 < 行鎖
注意:MYISAM引擎只支持表鎖,InnoDB既支持表鎖也支持行鎖

行鎖

共享鎖(Shared Locks):
又稱爲讀鎖,簡稱s鎖,顧名思義共享鎖就是多個事務對於同一數據可以共享一把鎖,都能訪問到數據,但是隻能讀不能寫,不然容易造成死鎖。
加鎖方式:在select語句後加LOCK IN SHARE MODE;
釋鎖方式:commit / rollback

排他鎖(Exclusive Locks):
又稱爲寫鎖,簡稱x鎖,排他鎖不能和其他鎖共存,如一個事務獲取了一個數據行的排他鎖,其他事務就不能獲取該行的鎖(包括共享鎖和排他鎖),只有獲取到該行的排他鎖的事務是可以對數據進行讀取和修改。
加鎖方式:
1.手動加鎖:在語句後加FOR UPDATE;(select時需手動)
2.自動加鎖:在insert / update / delete 語句時或自動加排他鎖
釋鎖方式:commit / rollback
注意:innoDB中獲取鎖超時間默認爲50秒,查看語句爲 show variables like 'innodb_lock_wait_timeout';

表鎖

意向共享鎖(Intention Shared Lock):
簡稱IS鎖,表示事務準備給數據行進行加入共享鎖,也就是說一個數據行加共享鎖之前必須先獲得該表的IS鎖。
意向排他鎖(Intention Exclusive Lock):
簡稱IX鎖,表示事務準備給數據行加入排他鎖,說明事務在一個數據行加排他鎖前必須要獲得該表的IX鎖。
注意:意向鎖是由數據引擎自己維護的,用戶是無法手動操作意向鎖的。

問:爲什麼需要表級別的意向鎖?
答:假如要給一張表加表鎖,那麼這個加表鎖的前提是沒有其他任何事務已經鎖住了這張表的任意一行數據。那麼怎麼來確定這個前提呢?
必然要有一個全表掃描才能確定每一行數據都沒有被加鎖。如果這張表數據比較大呢?有千萬數據,那這個全表掃描的過程就要消耗很多時間才能確定能不能加表鎖,並且如果在掃描的過程中萬一有其他事務來加鎖怎麼辦,所以說,全表掃描加表鎖這種方式既慢並且消耗性能,帶來嚴重的併發問題。爲了解決這一問題,纔有了意向鎖。加表鎖的時候只要看一下這個表上有沒有意向鎖就ok了,因爲你加行鎖的時候都會先獲取到表的意向鎖纔行。意向鎖就是爲了提高加表鎖的效率

問:鎖的作用?
答:和Java中的鎖一樣,都是爲了解決資源競爭的問題,Java中的資源是對象。而數據庫中的資源就是表和行數據,鎖就是爲了解決對於事務併發訪問的問題。

問:鎖到底鎖住了什麼?是一行數據?還是一個字段?

InnoDB行鎖原理

針對上述問題,其實有三種情況:
1.沒有索引的表上加行鎖,會鎖住整張表,出現鎖表的情況。
2.有主鍵索引的表上加行鎖,會鎖住索引。
3.用唯一索引(輔助索引)的字段加行鎖,會鎖住輔助索引和主鍵索引。
在InnoDB中,行鎖就是鎖住索引記錄來實現的,加鎖的時候在mysql自帶的information_schema庫中的INN0DB_LOCKS表中可以看到:
lock_type:鎖類型;lock_table:加鎖的表;lock_index:鎖住的索引
在這裏插入圖片描述
問:沒有索引的表上加行鎖爲什麼會鎖表?
注意:在之前的文章中說過,InnoDB中一張表是不可能沒有索引的,如果用戶沒定義,默認就會用隱藏的字段ROW_ID作爲聚集索引。那麼加行鎖的時候,條件沒有命中索引(因爲用的是隱藏字段作爲索引,那麼肯定不能命中啊),就會走全表掃描,不得不把所有的聚集索引全都鎖住,所以會出現鎖表的情況。

問:爲什麼用唯一索引加行鎖時,會鎖住主鍵索引?
這就又涉及到前邊的知識——回表,因爲InnoDB中,輔助索引的葉子節點上存放的是主鍵索引,索引用輔助索引加行鎖時會先鎖住輔助索引,然後鎖住主鍵索引。本質上其實還是鎖住了主鍵索引。那麼鎖到底鎖住了什麼?相信大家心裏已經有答案了。

InnoDB行鎖算法

主要有三種算法:
1.記錄鎖(Record Lock)
鎖定記錄,在唯一性索引(唯一/主鍵)等值查詢時,精準匹配到一個索引記錄的時候會使用記錄鎖。
(如下圖)比如用where id = 4這個條件時,就會精準匹配到一個索引記錄。
在這裏插入圖片描述
2.間隙鎖(Gap Lock)
鎖定區間,鎖定是的是數據庫不存在的區間範圍,Gap Lock相互之間不會衝突。
比如,用where id = 6這個條件時,發現沒有這個數據記錄,就會鎖住(4,7)這個範圍(注意左開右開不包括記錄)。
在這裏插入圖片描述
3.臨建鎖(Next-key Lock)
鎖定記錄和區間,條件範圍查找時,會鎖住記錄和區間。Next-key Lock = Record Lock + Gap Lock;
比如,用where id > 5 and id <9時,就會鎖住(4,7]和(7,10]這個範圍的區間和記錄,也就是(4,10]這個範圍的記錄和區間(注意是左開右閉,不包括左邊的記錄,包括右邊的記錄)
在這裏插入圖片描述

事務隔離級別的選擇

首先Read Uncommited不加鎖和Serializable串行化這兩種級別基本是不會被使用的。
Read Commited 對於普通的select語句使用mvcc,對於加鎖的select和更新語句使用Record Lock記錄鎖。
Repeatable Read 對於普通的select語句使用mvcc,對於加鎖的select和更新語句會使用Next-key Lock 、 Record Lock 、 Gap Lock。

1.RR的間隙鎖會導致鎖的範圍擴大
2.條件列未使用到索引時,RR鎖表,RC鎖行
3.RC的“半一致性”讀(semi-consistent)可以增加update操作的併發性。
從上述三個問題中,看似RC的級別更有優勢,其實在實際中,合理的加鎖,在有索引的列加鎖並不會出現上邊的情況,通常使用默認的事務隔離級別RR就ok了。

<<上一篇:mysql索引在存儲引擎中的實現及索引的使用原則

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