所在文集:數(shù)據(jù)庫(kù)
本文的內(nèi)容參考了:
本文會(huì)涉及到數(shù)據(jù)庫(kù)的鎖,請(qǐng)先參見:MySQL InnoDB 鎖 學(xué)習(xí)筆記
數(shù)據(jù)庫(kù)隔離級(jí)別
用于處理 多事務(wù)并發(fā) 的情況。
InnoDB 對(duì)四種類型都支持:
- READ_UNCOMMITTED:讀取未提交。這是并發(fā)最高,一致性最差的隔離級(jí)別。
- READ_COMMITTED(RC):讀提交。這是互聯(lián)網(wǎng)最常用的隔離級(jí)別。
- REPEATABLE_READ(RR):重復(fù)讀。
- SERIALIZABLE:串行化。這是一致性最好的,但并發(fā)性最差的隔離級(jí)別。
不同事務(wù)的隔離級(jí)別,實(shí)際上是一致性與并發(fā)性的一個(gè)權(quán)衡與折衷。
InnoDB 使用不同的鎖策略來(lái)實(shí)現(xiàn)不同的隔離級(jí)別。
Read UnCommitted 讀取未提交內(nèi)容
InnoDB 實(shí)現(xiàn)方式:select
語(yǔ)句不加鎖。
導(dǎo)致出現(xiàn)臟讀:
A 事務(wù)讀取了 B 事務(wù) 尚未提交 的數(shù)據(jù)并在此基礎(chǔ)上操作,而 B 事務(wù)回滾,則 A 事務(wù)讀到的數(shù)據(jù)為臟數(shù)據(jù)。例如:
A: start transaction
B: start transaction
B: update account set balance = 50 where id = 1
A: select balance from account where id = 1
B: rollback
A: update account set balance = 50 + 100 where id = 1
A: commit
- B 事務(wù)讀取賬戶余額 100 塊,取錢,將余額修改為 50 塊,但并沒(méi)有提交。
- A 事務(wù)讀取了 尚未提交 的數(shù)據(jù),認(rèn)為余額是 50 塊。
- B 事務(wù)回滾。
- A 事務(wù)存錢 100 塊,將余額計(jì)算為 50 + 100 = 150 塊,此為臟數(shù)據(jù),實(shí)際余額應(yīng)該為 200 塊。
Read Committed(RC)讀取提交內(nèi)容
- 這是大多數(shù)數(shù)據(jù)庫(kù)系統(tǒng)的默認(rèn)隔離級(jí)別(但不是 MySQL 默認(rèn)的)
- 它滿足了隔離的簡(jiǎn)單定義:一個(gè)事務(wù)只能看見已經(jīng)提交事務(wù)所做的改變
InnoDB 實(shí)現(xiàn)方式:
- 普通的
select
使用快照讀(snapshot read),這是一種不加鎖的一致性讀。 - 加鎖的
select
,update
,delete
等語(yǔ)句,除了在外鍵約束檢查以及重復(fù)鍵檢查時(shí)會(huì)封鎖區(qū)間,其他時(shí)刻都只使用記錄鎖;
導(dǎo)致出現(xiàn)不可重復(fù)讀:
意味著我們?cè)谕粋€(gè)事務(wù)中執(zhí)行完全相同的 select
語(yǔ)句時(shí)可能看到不一樣的結(jié)果。
A 事務(wù)重復(fù)讀取前面讀取過(guò)的數(shù)據(jù),發(fā)現(xiàn)數(shù)據(jù)變化了,即被其他事務(wù) B 修改并提交過(guò)了。例如:
A: start transaction
B: start transaction
A: select balance from account where id = 1
B: update account set balance = 50 where id = 1
B: commit
A: select balance from account where id = 1
A: commit
- A 事務(wù)第一次讀取余額 100 塊。
- B 事務(wù)讀取賬戶余額 100 塊,取錢,將余額修改為 50 塊,并提交。
- A 事務(wù)第二次讀取余額,變成了 50 塊。與前一次讀取的結(jié)果不同。
Repeatable Read(RR)可重讀
- 這是 MySQL InnoDB 的默認(rèn)事務(wù)隔離級(jí)別
- 它確保同一事務(wù)的多個(gè)實(shí)例在并發(fā)讀取數(shù)據(jù)時(shí),會(huì)看到同樣的數(shù)據(jù)行
- 此級(jí)別可能出現(xiàn)的問(wèn)題——幻讀(Phantom Read):當(dāng)用戶讀取某一范圍的數(shù)據(jù)行時(shí),另一個(gè)事務(wù)又在該范圍內(nèi)插入了新行,當(dāng)用戶再讀取該范圍的數(shù)據(jù)行時(shí),會(huì)發(fā)現(xiàn)有新的“幻影” 行,例如:
A: start transaction
B: start transaction
A: select * from account // 只有id = 1
B: insert into account(id, balance) values(2, 0)
B: commit
A: select * from account // 發(fā)現(xiàn)有id = 1 和 id = 2
A: commit
- 很多人容易搞混不可重復(fù)讀和幻讀,確實(shí)這兩者有些相似。但不可重復(fù)讀重點(diǎn)在于
update
和delete
,而幻讀的重點(diǎn)在于insert
。 - InnoDB 存儲(chǔ)引擎通過(guò)多版本并發(fā)控制(MVCC,Multiversion Concurrency Control)機(jī)制解決了該問(wèn)題
InnoDB 實(shí)現(xiàn)方式:
- 普通的
select
使用快照讀(snapshot read),這是一種不加鎖的一致性讀。 - 加鎖的
select
,update
,delete
等語(yǔ)句,它們的鎖,依賴于它們是否在唯一索引上使用了唯一的查詢條件,或者范圍查詢條件:- 在唯一索引上使用唯一的查詢條件,會(huì)使用記錄鎖(record lock),而不會(huì)封鎖記錄之間的間隔,即不會(huì)使用間隙鎖(gap lock)與臨鍵鎖(next-key lock)。
- 范圍查詢條件,會(huì)使用間隙鎖與臨鍵鎖,鎖住索引記錄之間的范圍,避免范圍間插入記錄,以避免產(chǎn)生幻影行記錄,以及避免不可重復(fù)的讀。
Serializable 可串行化
- 這是最高的隔離級(jí)別
- 它通過(guò)強(qiáng)制事務(wù)排序,使之不可能相互沖突,從而解決幻讀問(wèn)題。簡(jiǎn)言之,它是在每個(gè)讀的數(shù)據(jù)行上加上共享鎖。
- 在這個(gè)級(jí)別,可能導(dǎo)致大量的超時(shí)現(xiàn)象和鎖競(jìng)爭(zhēng)
InnoDB 實(shí)現(xiàn)方式:這種事務(wù)的隔離級(jí)別下,所有 select
語(yǔ)句都會(huì)被隱式的轉(zhuǎn)化為 select ... in share mode
.
總結(jié)
InnoDB 實(shí)現(xiàn)了SQL92標(biāo)準(zhǔn)中的四種隔離級(jí)別
- 讀未提交:
select
不加鎖,可能出現(xiàn)讀臟; - 讀提交(RC):普通
select
快照讀,帶鎖的select
/update
/delete
會(huì)使用記錄鎖,可能出現(xiàn)不可重復(fù)讀; - 可重復(fù)讀(RR):普通
select
快照讀,帶鎖的select
/update
/delete
根據(jù)查詢條件情況,會(huì)選擇記錄鎖,或者間隙鎖/臨鍵鎖,以防止讀取到幻影記錄; - 串行化:
select
隱式轉(zhuǎn)化為select ... in share mode
,會(huì)被update
與delete
互斥; - InnoDB默認(rèn)的隔離級(jí)別是RR,用得最多的隔離級(jí)別是RC
InnoDB,快照讀,在RR和RC下有何差異?
MySQL 數(shù)據(jù)庫(kù),InnoDB 存儲(chǔ)引擎,為了提高并發(fā),使用 MVCC 機(jī)制,在并發(fā)事務(wù)時(shí),通過(guò)讀取數(shù)據(jù)行的歷史數(shù)據(jù)版本,不加鎖,來(lái)提高并發(fā)的一種不加鎖一致性讀(Consistent Nonlocking Read)。
- 事務(wù)總能夠讀取到,自己寫入(
update
/insert
/delete
)的行記錄 - 在 Read Committed(RC)下,快照讀總是能讀到最新的行數(shù)據(jù)快照,當(dāng)然,必須是已提交事務(wù)寫入的
- 在 Repeatable Read(RR)下,假設(shè)某個(gè)事務(wù)首次 read 記錄的時(shí)間為 T,則未來(lái)不會(huì)讀取到 T 時(shí)間之后已提交事務(wù)寫入的記錄,以保證連續(xù)相同的 read 讀到相同的結(jié)果集
例如:數(shù)據(jù)表中的數(shù)據(jù)為 {1, 2, 3}
,現(xiàn)在兩個(gè)并發(fā)事務(wù) A,B 執(zhí)行的時(shí)間序列如下:
A1: start transaction;
B1: start transaction;
A2: select * from t;
B2: insert into t values (4, wangwu);
A3: select * from t;
B3: commit;
A4: select * from t;
在 Read Committed(RC) 模式下:
- A2 讀到的結(jié)果集是
{1, 2, 3}
; - A3 讀到的結(jié)果集也是
{1, 2, 3}
,因?yàn)?B 還沒(méi)有提交; - A4 讀到的結(jié)果集還是
{1, 2, 3, 4}
,因?yàn)槭聞?wù) B 已經(jīng)提交;
在 Repeatable Read(RR) 模式下:
- A2 讀到的結(jié)果集肯定是
{1, 2, 3}
,這是事務(wù)A的第一個(gè) read,假設(shè)為時(shí)間T; - A3 讀到的結(jié)果集也是
{1, 2, 3}
,因?yàn)?B 還沒(méi)有提交; - A4 讀到的結(jié)果集還是
{1, 2, 3}
,因?yàn)槭聞?wù) B 是在時(shí)間 T 之后提交的,A4 得讀到和 A2 一樣的記錄;