数据库隔离级别详解
6.3 隔離級(jí)別
隔離級(jí)別解決的是,多個(gè)事務(wù)訪問同一數(shù)據(jù)時(shí)出現(xiàn)的不一致的一系列問題。
6.3.1 數(shù)據(jù)庫事務(wù)的知識(shí)
數(shù)據(jù)庫事務(wù)具有以下4個(gè)基本特征:也就是著名的 ACID。
Atomic(原子性):事務(wù)中包含的操作被看作一個(gè)整體的業(yè)務(wù)單元,這個(gè)業(yè)務(wù)單元的所有操作要么全部成功,要么全部失敗,不會(huì)出現(xiàn)部分成功、部分失敗的場(chǎng)景。
Consistency(一致性):事務(wù)在完成時(shí),必須使所有的數(shù)據(jù)都保持在一直的狀態(tài),在數(shù)據(jù)庫中所有的修改都是基于事物的,保證了數(shù)據(jù)的完整性。
Isolation(隔離性):多個(gè)應(yīng)用程序同時(shí)訪問同一數(shù)據(jù),這樣數(shù)據(jù)庫同樣的數(shù)據(jù)就會(huì)在不同事務(wù)中被訪問,這樣就會(huì)產(chǎn)生丟失更新。為了壓制丟失更新的產(chǎn)生,數(shù)據(jù)庫定義了隔離級(jí)別的概念,通過它的選擇,可以在不同程度上壓制丟失更新的發(fā)生。因?yàn)榛ヂ?lián)網(wǎng)的應(yīng)用常常是面對(duì)高并發(fā)的場(chǎng)景,所以隔離性是需要掌握的重點(diǎn)內(nèi)容。
Durability(持久性):事務(wù)結(jié)束后,所有數(shù)據(jù)都會(huì)固化到一個(gè)地方,如保存在磁盤中,即使斷電重啟后也可以提供給應(yīng)用程序訪問。
第一類丟失更新
一個(gè)事物回滾另外一個(gè)事務(wù)提交而引發(fā)的數(shù)據(jù)不一致的情況,我們稱之為第一類丟失更新。
發(fā)生原因兩個(gè)事務(wù)是獨(dú)立的,都從相同的數(shù)據(jù)源獲取數(shù)據(jù),彼此之間沒有相互約束,就會(huì)造成訪問修改相同的數(shù)據(jù)出錯(cuò)。如下場(chǎng)景。
| T1 | 初始庫存100 | 初始庫存100 |
| T2 | 扣減庫存,剩余99 | |
| T3 | 扣減庫存,剩余99 | |
| T4 | 提交事務(wù),庫存變?yōu)?9 | |
| T5 | 回滾事務(wù),庫存100 |
不過現(xiàn)今,這種情況已經(jīng)沒有討論價(jià)值了,因?yàn)槟壳爸髁鞯臄?shù)據(jù)庫都已經(jīng)克服了第一類丟失更新問題。
第二類丟失更新
多個(gè)事務(wù)都提交引發(fā)的丟失更新被稱之為第二類丟失更新。
解決第二類丟失更新的原則就是,事務(wù)之間不再是相互獨(dú)立的,而是彼此之間的是有約束的,按約束等級(jí)不同,能解決的更新丟失的級(jí)別也會(huì)越高,最高級(jí)別的約束會(huì)完全防止丟失更新,但是付出的代價(jià)的就是性能的下降,所以要合理使用隔離級(jí)別。
6.3.2 詳解隔離級(jí)別
為了壓制丟失更新,我們需要使用隔離級(jí)別。隔離級(jí)別有四個(gè):未提交讀、讀寫提交、可重復(fù)讀、串行化(按照由低到高的級(jí)別進(jìn)行的排序)。
未提交讀
級(jí)別:1
含義:允許一個(gè)事務(wù)去讀取另一個(gè)事務(wù)未提交的數(shù)據(jù)的數(shù)據(jù),造成的結(jié)果是:臟讀。一個(gè)場(chǎng)景如下。
| T1 | 商品庫存初始化為2 | ||
| T2 | 讀取庫存為2 | ||
| T3 | 扣減庫存 | 庫存為1 | |
| T4 | 扣減庫存 | 庫存為0,讀取事務(wù)一未提交的庫存數(shù)據(jù)1,扣減之后為0 | |
| T5 | 提交事務(wù) | 庫存保存0 | |
| T6 | 回滾事務(wù) | 因?yàn)榈谝活悂G失更新已經(jīng)克服,所以不會(huì)回滾事務(wù)為2,庫存為0,結(jié)果錯(cuò)誤。 |
臟讀是比較危險(xiǎn)的隔離級(jí)別(由上表也可以看到,存儲(chǔ)的數(shù)據(jù)出現(xiàn)了錯(cuò)誤),所以實(shí)際中一般不用這個(gè)隔離級(jí)別。
讀寫提交
級(jí)別:2
含義:一個(gè)事務(wù)只能讀取另外一個(gè)事務(wù)已經(jīng)提交了的數(shù)據(jù),不能讀取未提交的數(shù)據(jù)。
克服臟讀:
| T1 | 商品庫存初始化為2 | ||
| T2 | 讀取庫存為2 | ||
| T3 | 扣減庫存 | 庫存為1 | |
| T4 | 扣減庫存 | 庫存為1,讀取不到事務(wù)1未提交的庫存數(shù)據(jù),只能獲得從數(shù)據(jù)庫讀到的2 | |
| T5 | 提交事務(wù) | 庫存保存為1 | |
| T6 | 回滾事務(wù) | 因?yàn)榈谝活悂G失更新已經(jīng)克服,所以不會(huì)回滾事務(wù)為2,庫存為1(就是事務(wù)二的操作結(jié)果),結(jié)果正確。 |
可以看到上面已經(jīng)克服了臟讀,但是讀寫提交,會(huì)發(fā)生不可重復(fù)讀的情況,如下。
不可重復(fù)讀:
| T1 | 商品庫存初始化為1 | ||
| T2 | 讀取庫存為1 | ||
| T3 | 扣減庫存 | 事務(wù)一未提交 | |
| T4 | 讀取庫存為1 | 事務(wù)二不能讀取事務(wù)一未提交的0,所以只能讀取數(shù)據(jù)庫中的1,認(rèn)為可扣減 | |
| T5 | 提交事務(wù) | 事務(wù)一提交之后庫存變?yōu)?,但是此時(shí)事務(wù)二還以為庫存為1 | |
| T6 | 扣減庫存 | 失敗,因?yàn)榇藭r(shí)庫存為0,無法扣減.(可以看出扣減操作,不是扣減的讀取到的臨時(shí)值,而是操作的數(shù)據(jù)庫中數(shù)據(jù)對(duì)應(yīng)的地址),數(shù)據(jù)正確。 |
這種情況叫做不可重復(fù)度,但是可以看到雖然事務(wù)二的操作失敗了,但是數(shù)據(jù)本身是正確的。產(chǎn)生的原因本質(zhì)還是數(shù)據(jù)不同步而已。
可重復(fù)讀
級(jí)別:3
含義:就是當(dāng)其中一個(gè)事務(wù)在操作與自己要操作的相同數(shù)據(jù)的時(shí)候,由于加鎖的緣故自己不被允許的讀取該數(shù)據(jù),所以可以等一會(huì)再嘗試讀取,這就叫做可重復(fù)讀。解決了讀寫提交中的不可重復(fù)讀的情況。
克服不可重復(fù)度:
| T0 | 商品初始化為1 | ||
| T1 | 讀取庫存1 | ||
| T2 | 扣減庫存 | 事務(wù)1未提交 | |
| T3 | 嘗試讀取庫存 | 不允許讀,等待事務(wù)1的提交 | |
| T4 | 提交事務(wù) | 庫存變?yōu)? | |
| T5 | 讀取庫存 | 庫存為0,無法扣減 |
可以看到,其實(shí)就是對(duì)多個(gè)事務(wù)操作相同的數(shù)據(jù)時(shí),對(duì)該數(shù)據(jù)進(jìn)行加鎖,同一時(shí)刻只有先到的事務(wù)才能操作數(shù)據(jù)(讀寫)。但是這個(gè)級(jí)別也并不是完美的,其還會(huì)產(chǎn)生幻讀,如下:
幻讀:
| T0 | 讀取庫存50件 | 商品庫存初始化為100,現(xiàn)在已經(jīng)銷售50筆,庫存50筆 | |
| T1 | 查詢交易記錄,50筆 | ||
| T2 | 扣減庫存 | ||
| T3 | 插入1筆交易記錄 | ||
| T4 | 提交事務(wù) | 庫存49件,交易記錄51件 | |
| T5 | 打印交易記錄,51筆 | 這里與查詢不一致,在事務(wù)2看來有1筆是虛幻的,與之前查詢的不一致。 |
這里的筆數(shù)不是數(shù)據(jù)庫存儲(chǔ)的值,而是一個(gè)統(tǒng)計(jì)值,商品庫存則是數(shù)據(jù)庫存儲(chǔ)的值,這一點(diǎn)時(shí)要注意的。也就是說幻讀不是針對(duì)一條數(shù)據(jù)庫記錄而言的,而是多條記錄,例如,這51筆交易比數(shù)就是多條數(shù)據(jù)庫記錄統(tǒng)計(jì)出來的。而可重復(fù)度是針對(duì)數(shù)據(jù)庫的但一條記錄,例如,商品的庫存是以數(shù)據(jù)庫里面的一條記錄存儲(chǔ)的,他可以產(chǎn)生可重復(fù)度,而不能產(chǎn)生幻讀。
級(jí)別:4(也就是最高級(jí)別)
含義:就是要求所有的 SQL 都按照順序執(zhí)行,這樣就可以克服上述隔離級(jí)別出現(xiàn)的各種問題,所以他能夠完全保證數(shù)據(jù)的一致性。但是這樣就會(huì)使性能嚴(yán)重下降。
使用合理的隔離級(jí)別
隔離級(jí)別和可能發(fā)生的現(xiàn)象
| 未提交讀 | √ | √ | √ |
| 讀寫提交 | X | √ | √ |
| 可重復(fù)度 | X | X | √ |
| 串行化 | X | X | X |
在現(xiàn)實(shí)中一般而言,選擇隔離級(jí)別會(huì)議讀寫提交為主,它能夠防止臟讀,而不可避免不可重復(fù)讀和幻讀。為了克服數(shù)據(jù)不一致和性能問題,程序開發(fā)者還設(shè)計(jì)了樂觀鎖,甚至不再使用數(shù)據(jù)庫而使用其他手段。例如,使用 Redis 作為數(shù)據(jù)載體。
對(duì)于隔離級(jí)別,不同的數(shù)據(jù)庫至此也是不一樣的。例如,Oracle 只能支持讀寫提交和串行化,而 MySQL 則支持上述四種,對(duì)于 Oracle 默認(rèn)的隔離級(jí)別為讀寫提交,MySQL 則是可重復(fù)讀,這些需要根據(jù)具體數(shù)據(jù)庫來做決定。
總結(jié)
- 上一篇: 修改 cmd 控制台默认代码页编码的几种
- 下一篇: JS 与 JAVA 跨语言实现 RSA