- 髒讀(側重點在讀取了未提交的數據)
指一個事務讀取到了另一個事務未提交的數據,造成了與數據庫中的數據不一致的情況。
比如事務A修改了一條數據,但沒有提交,此時事務B卻讀取了該條數據,事務A由於出錯發生了回滾,這時事務B就形成了髒讀。
2. 不可重複讀(虛讀)(側重點在讀取了已經提交的修改的數據,數據本身對比)
指一個線程中的事務讀取到了另外一個線程中的事務已提交的update的數據。即一個事務A兩次或多次讀取數據,在此期間,事務B讀取同一數據,並修改了此數據,然後提交了,造成了事務A兩次或多次讀取數據出現了數據不一致的情況。
不可重複讀可能造成數據丟失:
第一種丟失更新:
第一類丟失更新就是兩個事務同時更新一個數據,一個事務更新完畢並提交後,另一個事務回滾,造成提交的更新丟失。
事務A|事務B
-|-
開始事務|開始事務
讀取N=5|讀取N=5
修改N=8|修改N=9
|提交,結束事務
回滾|
結束事務(N=5)|N=5,N本應爲提交的9
第二類丟失更新:
第二類丟失更新就是兩個事務同時更新一個數據,先更新的事務提交的數據會被後更新的事務提交的數據覆蓋,即先更新的事務提交的數據丟失。
事務A|事務B
-|-
開始事務|開始事務
讀取M=8|讀取M=8
更新M=1|
提交事務,M=1,結束|
|更新M=7
|提交事務,M=7,事務A的更新丟失,結束
3. 幻讀(側重點在讀取到了新增或刪除的數據,數據條數對比)
指一個線程中的事務讀取到了另外一個線程中事務已提交的insert的數據。即一個事務A兩次或多次讀取數據,在此期間,事務B新增了N條數據,然後提交了,造成了事務A兩次或多次讀取數據出現了數據條數不一致的情況。
4.隔離級別-------級別越高,數據越安全,但性能越低
讀數據一致性及允許的並 發副作用 隔離級別 |
讀數據一致性 |
髒讀 |
不可重複讀 |
幻讀 |
未提交讀(Read uncommitted) |
最低級別,只能保證不讀取物理上損壞的數據 |
可能 |
可能 |
可能 |
已提交讀(Read committed) |
語句級 |
不可能 |
可能 |
可能 |
可重複讀(Repeatable read) |
事務級 |
不可能 |
不可能 |
可能 |
可串行化(Serializable) |
最高級別,事務級 |
不可能 |
不可能 |
不可能 |
MySQL 默認的級別是:Repeatable read 可重複讀,其他主流數據庫,如Oracle;SQLServer默認級別爲:Read committed
1) 未提交讀:寫事務阻止其他寫事務,避免了更新遺失。但是沒有阻止其他讀事務。
存在的問題:髒讀。即讀取到不一致的數據,因爲另一個事務可能還沒提交最終數據,這個讀事務就讀取了中途的數據,這個數據可能是不正確的。
解決辦法:已提交讀
2) 已提交讀:寫事務會阻止其他讀寫事務。讀事務不會阻止其他任何事務。
存在的問題:不可重複讀。即在一次事務之間,進行了兩次讀取,但是結果不一致,可能第一次id爲1的人叫“張三”,第二次讀id爲1的人就叫“王五”了。因爲讀取操作不會阻止其他事務。
解決辦法:可重複讀
3) 可重複讀
讀事務會阻止其他寫事務,但是不會阻止其他讀事務。
存在的問題:幻讀。可重複讀阻止的寫事務包括update和delete(只給存在的行加上了鎖),但是不包括insert(新行不存在,所以沒有辦法加鎖),所以一個事務第一次讀取可能讀取到了10條記錄,但是第二次可能讀取到11條,這就是幻讀。
解決辦法:串行化
4) 可串行化
可避免幻讀。讀加共享鎖,寫加排他鎖。這樣讀取事務可以併發,但是讀寫,寫寫事務之間都是互斥的,基本上就是一個個執行事務,所以叫串行化。
5. 封鎖協議
封鎖協議就是在用X鎖或S鎖時制定的一些規則,比如鎖的持續時間,鎖的加鎖時間等。不同的封鎖協議對應不同的隔離級別。事務的隔離級別一共有4種,由低到高分別是Read uncommitted、Read committed、Repeatable read、Serializable,分別對應的相應的封鎖協議等級。
1) 一級封鎖協議
一級封鎖協議對應的是Read uncommitted隔離級別,Read uncommitted讀未提交,一個事務可以讀取另一個事務未提交的數據,這是最低的級別。一級封鎖協議本質上是在事務修改數據之前加上X鎖,直到事務結束後才釋放,事務結束包括正常結束(commit)與非正常結束(rollback)。
2) 二級封鎖協議
二級封鎖協議本質上在一級協議的基礎上(在修改數據時加X鎖),在讀數據時加上S鎖,讀完後立即釋放S鎖,可以避免髒讀。但有可能出現不可重複讀與幻讀。二級封鎖協議對應的是Read committed與Repeatable Read隔離級別。
3) 三級封鎖協議
三級封鎖協議,在一級封鎖協議的基礎上(修改時加X鎖),讀數據時加上S鎖(與二級類似),但是直到事務結束後才釋放S鎖,可以避免幻讀,髒讀與不可重複讀。三級封鎖協議對應的隔離級別是Serializable。
6. 鎖機制
1) 共享/排它鎖(Shared and Exclusive Locks)
共享鎖(Share Locks,記爲S鎖),讀取數據時加S鎖,共享鎖之間不互斥,即讀讀可以並行。
排他鎖(exclusive Locks,記爲X鎖),修改數據時加X鎖,排他鎖與任何鎖互斥,即寫讀,寫寫不可以並行。
一旦寫數據的任務沒有完成,數據是不能被其他任務讀取的,這對併發度有較大的影響。對應到數據庫,可以理解爲,寫事務沒有提交,讀相關數據的select也會被阻塞,這裏的select是指加了鎖的,普通的select仍然可以讀到數據(快照讀)。
2) 意向鎖(Intention Locks)
InnoDB爲了支持多粒度鎖機制(multiple granularity locking),即允許行級鎖與表級鎖共存,而引入了意向鎖(intention locks)。意向鎖是指,未來的某個時刻,事務可能要加共享/排它鎖了,先提前聲明一個意向。
意向鎖是一個表級別的鎖(table-level locking);
意向鎖又分爲:
意向共享鎖(intention shared lock, IS),它預示着,事務有意向對錶中的某些行加共享S鎖;
意向排它鎖(intention exclusive lock, IX),它預示着,事務有意向對錶中的某些行加排它X鎖;
加鎖的語法爲:
select ... lock in share mode; 要設置IS鎖;
select ... for update; 要設置IX鎖;
事務要獲得某些行的S/X鎖,必須先獲得表對應的IS/IX鎖,意向鎖僅僅表明意向,意向鎖之間相互兼容,兼容互斥表如下:
|
IS |
IX |
IS |
兼 容 |
兼 容 |
IX |
兼 容 |
兼 容 |
雖然意向鎖之間互相兼容,但是它與共享鎖/排它鎖互斥,其兼容互斥表如下:
|
S |
X |
IS |
兼 容 |
互 斥 |
IX |
互 斥 |
互 斥 |
排它鎖是很強的鎖,不與其他類型的鎖兼容。這其實很好理解,修改和刪除某一行的時候,必須獲得強鎖,禁止這一行上的其他併發,以保障數據的一致性。
3) 記錄鎖(Record Locks)-----鎖定某行
對單條索引記錄進行加鎖,鎖住的是索引記錄而非記錄本身,即使表中沒有任何索引,MySQL會自動創建一個隱式的row_id作爲聚集索引來進行加鎖。記錄鎖就是爲某行記錄加鎖,它封鎖該行的索引記錄。查詢條件必須爲主鍵列或唯一索引,否則鎖會變成臨建鎖。同時查詢語句爲精準匹配(=),不能爲>、<、like等,否則也會退化成臨建鎖。事務隔離級別是RR。
4) 間隙鎖(Gap Locks) -----鎖定某個區間
間隙鎖,它封鎖索引記錄中的一段間隔範圍,或者第一條索引記錄之前的範圍,又或者最後一條索引記錄之後的範圍。
存儲引擎是InnoDB,事務隔離級別是RR。
間隙鎖的主要目的,就是爲了防止其他事務在間隔中插入數據,以導致“不可重複讀”。如果把事務的隔離級別降級爲已提交讀(Read Committed, RC),間隙鎖則會自動失效。例如:
Select * from test where id between 1 and 8 for update;
SQL語句會封鎖區間(1,8),以阻止其他事務插入id位於該區間的記錄。
5) 臨鍵鎖(Next-key Locks) -----鎖定左開右閉的一段區間
臨鍵鎖,是記錄鎖與間隙鎖的組合,它的封鎖範圍,既包含索引記錄,又包含索引區間。
臨鍵鎖的主要目的,也是爲了避免幻讀。
如果把事務的隔離級別降級爲RC,臨鍵鎖就也會失效。
每個數據行上的非唯一索引列上都會存在一把臨鍵鎖,當某個事務持有該數據行的臨鍵鎖時,會鎖住一段左開右閉區間的數據。需要強調的一點是,InnoDB 中行級鎖是基於索引實現的,臨鍵鎖只與非唯一索引列有關,在唯一索引列(包括主鍵列)上不存在臨鍵鎖。
假設有如下表:
MySql,InnoDB,Repeatable-Read:table(id PK, age KEY, name)
id |
age |
name |
1 |
10 |
Lee |
3 |
24 |
Soraka |
5 |
32 |
Zed |
7 |
45 |
Talon |
該表中 age 列潛在的臨鍵鎖有:
(-∞, 10],
(10, 24],
(24, 32],
(32, 45],
(45, +∞],
在根據非唯一索引 對記錄行進行 UPDATE \ FOR UPDATE \ LOCK IN SHARE MODE 操作時,InnoDB 會獲取該記錄行的 臨鍵鎖 ,並同時獲取該記錄行下一個區間的間隙鎖。
InnoDB 中的行鎖的實現依賴於索引,一旦某個加鎖操作沒有使用到索引,那麼該鎖就會退化爲表鎖。
記錄鎖存在於包括主鍵索引在內的唯一索引中,鎖定單條索引記錄。
間隙鎖存在於非唯一索引中,鎖定開區間範圍內的一段間隔,它是基於臨鍵鎖實現的。
臨鍵鎖存在於非唯一索引中,該類型的每條記錄的索引上都存在這種鎖,它是一種特殊的間隙鎖,鎖定一段左開右閉的索引區間。
6) 插入意向鎖(Insert Intention Locks)
插入意向鎖,是間隙鎖(Gap Locks)的一種(所以,也是實施在索引上的),它是專門針對insert操作的。多個事務,在同一個索引,同一個範圍區間插入記錄時,如果插入的位置不衝突,不會阻塞彼此。
目的是提高插入併發。
7) 自增鎖(Auto-inc Locks)
自增鎖是一種特殊的表級別鎖(table-level lock),專門針對事務插入AUTO_INCREMENT類型的列。如果一個事務正在往表中插入記錄,所有其他事務的插入必須等待,以便第一個事務插入的行,是連續的主鍵值。