分布式相關(guān)的內(nèi)容非常多, 本次分享主要是從分布式事務(wù)切入的, 所以主要講的是分布式的環(huán)境下, 怎么做事務(wù); 要講分布式事務(wù), 我覺得還是有必要提一下單機(jī)事務(wù)的, 單機(jī)事務(wù)的很多特性其實(shí)在分布式事務(wù)中也需要考慮到; 那么今天在這里, 我們主要從兩方面來解讀下單機(jī)事務(wù):
- 是什么? : 事務(wù)是什么?
- 怎么做?: 事務(wù)是怎么實(shí)現(xiàn)的?
1. 事務(wù)是什么?
我們的系統(tǒng)大體能分為兩類:
a. 數(shù)據(jù)密集型;
b. 計(jì)算密集型;
我們?nèi)粘9ぷ髦懈嘟佑|到的是數(shù)據(jù)密集型, 也就無可避免的接觸到數(shù)據(jù)持久話的問題了, 現(xiàn)在來一起看下幾個(gè)場景:
- 在持久化時(shí), 程序崩潰了;
- 在持久化時(shí), 服務(wù)器崩潰了;
- 在持久化時(shí), 程序與DB的鏈接斷開了;
- 持久化使用了多線程, 線程A把線程B的持久化數(shù)據(jù)覆蓋了, 甚至最終持久化的數(shù)據(jù)有1/3是線程A的, 2/3是線程B的, 形成了數(shù)據(jù)混淆;
- 在數(shù)據(jù)讀取時(shí), 把持久化一半的記錄讀取出來了;
- etc;
正因?yàn)槲覀兊某绦蜻\(yùn)行的環(huán)境是不穩(wěn)定的, 所以上面的種種場景都在日常生產(chǎn)中可能遇到的;
而正因?yàn)橄到y(tǒng)的不穩(wěn)定了, 造成了原本預(yù)期結(jié)果是true | false, 結(jié)果返回的是未知狀態(tài), 因?yàn)槌绦虮紳⒘? 我也不知道持久化到哪部分?jǐn)?shù)據(jù)了; 也就是形成了三態(tài)問題;
那業(yè)務(wù)系統(tǒng)為了數(shù)據(jù)的可靠性, 能不能處理這個(gè)三態(tài)問題呢? 答案是能的, 但是這個(gè)成本是很高的, 需要在業(yè)務(wù)代碼中嵌入許多非業(yè)務(wù)相關(guān)的代碼來保證數(shù)據(jù)的完整性, 同時(shí)也需要非常多的測試來驗(yàn)證這個(gè)方案的可行性;
那么有沒有更好的解決方案呢?
業(yè)務(wù)系統(tǒng)原本的預(yù)期就是true | false, 那么只需要數(shù)據(jù)庫層面把中間態(tài)解決掉就可以了, 這也就引出了事務(wù)是什么以及做了什么:
事務(wù)把業(yè)務(wù)系統(tǒng)的同一批操作抽象為一個(gè)邏輯單元, 該邏輯單元的所有操作要么都成功(commit), 否則就是全部失敗(abort), 然后回滾(rollback), 不會(huì)出現(xiàn)中間態(tài);
這樣做的好處就是對(duì)業(yè)務(wù)系統(tǒng)的反饋只會(huì)是成功或者失敗;
2. 事務(wù)是怎么實(shí)現(xiàn)的?
說到事務(wù)就不得不提到他的四個(gè)特性了:
2.1 ACID
單機(jī)事務(wù)的特性:(詳見維基百科)
- Atomicity(原子性):一個(gè)事務(wù)(transaction)中的所有操作,或者全部完成,或者全部不完成,不會(huì)結(jié)束在中間某個(gè)環(huán)節(jié)。事務(wù)在執(zhí)行過程中發(fā)生錯(cuò)誤,會(huì)被回滾(Rollback)到事務(wù)開始前的狀態(tài),就像這個(gè)事務(wù)從來沒有執(zhí)行過一樣。即,事務(wù)不可分割、不可約簡。
- Consistency(一致性):在事務(wù)開始之前和事務(wù)結(jié)束以后,數(shù)據(jù)庫的完整性沒有被破壞。這表示寫入的資料必須完全符合所有的預(yù)設(shè)約束、觸發(fā)器、級(jí)聯(lián)回滾等。
- Isolation(隔離性):數(shù)據(jù)庫允許多個(gè)并發(fā)事務(wù)同時(shí)對(duì)其數(shù)據(jù)進(jìn)行讀寫和修改的能力,隔離性可以防止多個(gè)事務(wù)并發(fā)執(zhí)行時(shí)由于交叉執(zhí)行而導(dǎo)致數(shù)據(jù)的不一致。事務(wù)隔離分為不同級(jí)別,包括未提交讀(Read uncommitted)、提交讀(read committed)、可重復(fù)讀(repeatable read)和串行化(Serializable)。
- Durability(持久性):事務(wù)處理結(jié)束后,對(duì)數(shù)據(jù)的修改就是永久的,即便系統(tǒng)故障也不會(huì)丟失。
2.1 原子性解讀
看到原子性第一反應(yīng)其實(shí)多線程中的原子性: 保證一個(gè)操作是原子的, 該操作的中間態(tài)是對(duì)其他線程不可見的; 但是事務(wù)的原子性不是用來解決并發(fā)的, 并發(fā)性是由隔離性來保證;
原子性解決的是中間的態(tài)的問題, 事務(wù)中的一批寫操作在執(zhí)行到某一個(gè)寫時(shí), 因?yàn)榫W(wǎng)絡(luò)或者其他原因, 寫失敗了, 那么該事務(wù)就應(yīng)該被中止, 并且數(shù)據(jù)庫必須撤銷該事務(wù)迄今為止所有的寫入;
所以原子性更多的是約束了事務(wù)可以中止的, 并且中止的時(shí)候會(huì)撤銷所有該事務(wù)未提交的寫入操作;
2.2 一致性解讀
一致性必須要說一下, 一致性這個(gè)詞在不同的語境中, 被賦予了不同含義:
- 在討論數(shù)據(jù)庫HA的時(shí)候主從復(fù)制的一致性, 或者異步復(fù)制的最終一致性, 這里強(qiáng)調(diào)的是兩個(gè)數(shù)據(jù)的值是相同的;
- 在CAP中的一致性, 指的是線性一致, 這個(gè)后面會(huì)講;
而事務(wù)的一致性怎么解讀呢?
維基百科中的解釋是: 在事務(wù)開始之前和事務(wù)結(jié)束以后,數(shù)據(jù)庫的完整性沒有被破壞;
數(shù)據(jù)庫的完整性怎么理解? 就是持久的數(shù)據(jù)是符合我們的一定的預(yù)期的, 而這個(gè)預(yù)期在后續(xù)的寫入中沒有被破壞掉, 也就是我們對(duì)持久化的數(shù)據(jù)的預(yù)期是始終成立的; 而維基百科中提到的約束, 觸發(fā)器等都是來保證我們的這種預(yù)期始終成立;
看到這里, 是不是發(fā)現(xiàn)數(shù)據(jù)庫的一致性其實(shí)是由應(yīng)用程序來定義的, 什么數(shù)據(jù)是有效的, 而什么數(shù)據(jù)是無效的, 數(shù)據(jù)庫純粹只是用來持久化數(shù)據(jù)的;
2.3 持久性解讀
數(shù)據(jù)庫的持久性其實(shí)就是找個(gè)地方存儲(chǔ)數(shù)據(jù), 保證事務(wù)完成以后, 這部分已經(jīng)持久化的數(shù)據(jù)不會(huì)丟失; 在單機(jī)事中, 持久性說的是,只有當(dāng)該事務(wù)中的所有寫入操作的數(shù)據(jù)都已經(jīng)落盤了, 事務(wù)才會(huì)提交成功; 如果是數(shù)據(jù)庫集群, 表示這部分?jǐn)?shù)據(jù)已經(jīng)復(fù)制到了一些節(jié)點(diǎn)上;
但是單機(jī)事務(wù), 并不能完美保證這部分?jǐn)?shù)據(jù)不丟失, 比如你這機(jī)房被炸了, 你的數(shù)據(jù)也就丟了, 所有才有多副本存儲(chǔ)的; 但是多副本也只是降低了數(shù)據(jù)丟失的概率;
2.4 隔離性解讀
隔離性放到最后是因?yàn)檫@個(gè)比較復(fù)雜, 并且內(nèi)容也比較多; 隔離性可以簡單的分為兩類: 弱隔離級(jí)別和強(qiáng)隔離級(jí)別; 我們先來說說日常生產(chǎn)中用的比較多的弱隔離級(jí)別:
2.4.1 弱隔離級(jí)別
為什么說弱隔離性在生產(chǎn)中比較常見的呢? 因?yàn)槲覀內(nèi)粘=佑|的大部分都是讀多寫少的業(yè)務(wù); 也就是兩個(gè)事務(wù)大部分情況下是并行執(zhí)行的, 不會(huì)產(chǎn)生數(shù)據(jù)競爭, 這種場景下弱隔離級(jí)別就十分有用了;
2.4.1.1讀已提交
讀已提交的隔離級(jí)別是我們最常用到的, 因?yàn)樗WC了:
- 單個(gè)事務(wù)只能讀取其他事務(wù)已經(jīng)提交的數(shù)據(jù)(保證了沒有臟讀);
- 單個(gè)事務(wù)只能覆蓋其他事務(wù)已經(jīng)提交的數(shù)據(jù)(保證了沒有臟寫);
說一下臟讀和臟寫:
- 臟讀很好理解: 就是讀取了其他事務(wù)還未提交的數(shù)據(jù); 為什么要防止臟讀其實(shí)最大的原因是如果另一個(gè)事務(wù)中止了, 需要做回滾, 但是因?yàn)槟惝?dāng)前事務(wù)已經(jīng)讀取了回滾事務(wù)操作的數(shù)據(jù), 當(dāng)前事務(wù)的提交將導(dǎo)致回滾是無效的;
- 臟寫: 臟寫就是如果一個(gè)事務(wù)寫入了數(shù)據(jù), 但是還未提交事務(wù), 但是已經(jīng)被另一個(gè)事務(wù)的寫入給覆蓋了; 臟寫問題更嚴(yán)重, 舉個(gè)例子: 小明和小紅同時(shí)編輯同一個(gè)文章, 我們來看一下臟寫的問題:
<colgroup><col span="1" width="426"><col span="1" width="426"></colgroup>
| 小明 | 小紅 |
|---|---|
| begin transaction | Begin transaction; |
| Select article; | Select article; |
| update title = '小明'; | |
| update title = '小紅'; | |
| Update author = '小紅'; | |
| Commit transaction; | |
| Update author = '小明'; | |
| Commit transaction; |
最終的結(jié)果會(huì)變成title是小紅, author是小明, 可以看到如果有臟寫, 來自不同事務(wù)的沖突, 導(dǎo)致數(shù)據(jù)被混淆了;
2.4.1.2 讀已提交的實(shí)現(xiàn)
- 臟寫的防止: 寫入需要獲取操作對(duì)象的鎖, 當(dāng)該對(duì)象鎖被其他事務(wù)占有時(shí), 當(dāng)前事務(wù)必須等待, 直到事務(wù)完成或中止時(shí)才釋放鎖;
- 臟讀的防止: 一種方式是讀也需要申請(qǐng)鎖, 如果該對(duì)象已經(jīng)被其他事務(wù)鎖定了, 當(dāng)前事務(wù)需要等待; 但是讀鎖的釋放是在讀取完成之后立即釋放的; 這種方式其實(shí)并不好, 因?yàn)橹灰嬖谝粋€(gè)寫入的長事務(wù), 必然會(huì)阻塞其他只讀事務(wù); 所有就有了方式二: 既然只需讀取其他事務(wù)已提交的數(shù)據(jù), 那么只要讀取當(dāng)前事務(wù)寫入前的數(shù)據(jù)就可以了; 這種方式就是當(dāng)有寫入事務(wù)正在執(zhí)行的時(shí)候, 數(shù)據(jù)庫會(huì)做一個(gè)快照保存當(dāng)前寫入事務(wù)寫入前的值, 當(dāng)有讀取事務(wù)進(jìn)來時(shí), 只需要讀取快照即可;只有當(dāng)寫入事務(wù)提交了, 讀取事務(wù)才會(huì)去讀取最新值;
- 所以讀已提交的實(shí)現(xiàn), 讀不阻塞寫, 寫不阻塞讀;
讀已提交存在的問題
讀已提交隔離級(jí)別看上去已經(jīng)十分美好了: 因?yàn)樗瑫r(shí)防止了臟讀和臟寫, 但是我們來看下這個(gè)例子:
拿用戶購買基金以后, 需要扣賬戶余額與加資產(chǎn)舉例(假設(shè)賬戶表與資產(chǎn)表都在同一個(gè)DB上):
<colgroup><col span="1" width="426"><col span="1" width="426"></colgroup>
| 投資事務(wù) | 查詢事務(wù) |
|---|---|
| Begin transaction | |
| Select position;(1000) | |
| Begin transaction; | |
| update account = 500; | |
| update position = 1500; | |
| Commit transaction | |
| select account;(500) | |
| Commit transaction | |
因?yàn)槭亲x已提交的隔離級(jí)別, 所以在投資事務(wù)未提交前, 用戶先看了下資產(chǎn)數(shù)據(jù), 只有1000, 在投資事務(wù)提交以后, 用戶看了賬戶余額是500, 導(dǎo)致用戶以為自己少了500的資產(chǎn);
這種問題被稱為不可重復(fù)讀, 這種問題在我們業(yè)務(wù)上其實(shí)也是存在的, 比如需要查詢并進(jìn)行校驗(yàn)的時(shí)候, 一個(gè)長事務(wù)的兩次讀取可能會(huì)讀取到不同的值, 導(dǎo)致本次操作失敗的問題;
2.4.1.3 不可重復(fù)讀的解決方案(可重復(fù)讀)
這個(gè)問題還是讀寫并發(fā)的問題, 在不考慮加鎖的情況下, 可以參考讀已提交的快照隔離來實(shí)現(xiàn):讀已提交的實(shí)現(xiàn)是考慮了寫事務(wù)包了讀事務(wù)的時(shí)候, 怎么來處理臟讀; 不可重復(fù)的問題是讀事務(wù)包了寫事務(wù);那么只保留一份快照是不可取的, 因?yàn)橐粋€(gè)讀事務(wù)可能會(huì)包掉多個(gè)寫事務(wù), 會(huì)導(dǎo)致連快照都已經(jīng)被覆蓋的情況; 所以不可重復(fù)讀的解決方案就是保存多份數(shù)據(jù)快照:這種方案也就是MVCC(多版本并發(fā)控制);
MVCC實(shí)現(xiàn)快照隔離
數(shù)據(jù)庫會(huì)默認(rèn)給每張表加上兩個(gè)屬性: created_by 和 deleted_by, 這兩個(gè)屬性分別寫入的是該條記錄是由哪個(gè)事務(wù)創(chuàng)建的(created_by), 由哪個(gè)事務(wù)更新的(deleted_by), 每次新建事務(wù)的時(shí)候數(shù)據(jù)庫會(huì)默認(rèn)分配事務(wù)ID;
- insert 只會(huì)寫入created_by;
- Delete 會(huì)寫入deleted_by;
- update會(huì)被解析為 delete + insert;
怎么通過created_by 和 deleted_by來實(shí)現(xiàn)可重復(fù)讀呢?
多版本控制類似于下面的存儲(chǔ):
<colgroup><col span="1" width="284"><col span="1" width="284"><col span="1" width="284"></colgroup>
| data | created_by | deleted_by |
|---|---|---|
| A | 4 | |
| D | 3 | 4 |
| C | 2 | 3 |
| B | 1 | 2 |
| A | 0 | 1 |
Deleted_by只有事務(wù)真正提交時(shí)才會(huì)生成快照, 也就是所有中止操作都不會(huì)生成快照;
只要在該事務(wù)開始前的快照, 都是對(duì)該事務(wù)可見的; 在該事務(wù)開始后的快照, 對(duì)該事務(wù)是不可見的;
通過deleted_by關(guān)聯(lián)created_by可以找到該個(gè)事務(wù)之前所有的快照, 那么可以通過[delete_by, created_by]這個(gè)區(qū)間來判斷, 當(dāng)前需要讀取哪份快照了;
我們通過前面的購買基金的例子來說明:(假設(shè)account和position的created_by為0)
<colgroup><col span="1" width="213"><col span="1" width="213"><col span="1" width="213"><col span="1" width="213"></colgroup>
| 投資事務(wù)1 | 讀取事務(wù)1 | 投資事務(wù)2 | 讀取事務(wù)2 |
|---|---|---|---|
| Begin;t_id = 1; | |||
| Begin;t_id = 2; | select account = 1000; | ||
| update account = 500; | |||
| Udpdate position = 1500; | |||
| Commit; | |||
| Begin;t_id = 3; | Begin;t_id = 4; | ||
| Update account = 1000; | Select account = 500; | ||
| select position = 1000;select account = 1000; | commit; | Select account = 500; | |
| Commit; | commit; |
account最終的快照:
<colgroup><col width="284" span="1"><col width="284" span="1"><col width="284" span="1"></colgroup>
| account | create_by | deleted_by |
| 1000 | 3 | |
| 500 | 2 | 3 |
| 1000 | 0 | 2 |
position最終的快照:
<colgroup><col span="1" width="284"><col span="1" width="284"><col span="1" width="284"></colgroup>
| position | created_by | deleted_by |
|---|---|---|
| 1500 | 2 | |
| 1000 | 0 | 2 |
先來看讀取事務(wù)2: 該條事務(wù)開始時(shí), 數(shù)據(jù)庫會(huì)先列出當(dāng)前所有還未提交的事務(wù)集合[1,3,4], 然后再判斷account的當(dāng)前快照[0,2], 因?yàn)?>2, 所以第一次讀取到的是500; 第二次讀取的時(shí)候, 因?yàn)槭聞?wù)3已經(jīng)提交了, 所以當(dāng)前account的快照是[0-3], 如果這個(gè)時(shí)候僅僅只是判斷4>3, 那讀取到的應(yīng)該是1000; 但是因?yàn)樵谑聞?wù)開始的時(shí)候已經(jīng)記錄了[1,3,4]是還未提交的, 所以應(yīng)該過濾掉事務(wù)3提交所產(chǎn)生的快照, 所以第二次讀取的仍然是500;
2.4.1.4 寫并發(fā)引發(fā)的問題
快照隔離主要解決是讀寫并發(fā)時(shí), 只讀事務(wù)能看到什么, 但是還有一種情形其實(shí)也是很常見的, 那就是寫寫并發(fā)了, 比如金融場景中很常見的加資產(chǎn): 如果一個(gè)用戶購買了2個(gè)產(chǎn)品, 需要給他加2次資產(chǎn), 并更新總資產(chǎn)值;
<colgroup><col span="1" width="426"><col span="1" width="426"></colgroup>
| 事務(wù)1 | 事務(wù)2 |
|---|---|
| Begin; | Begin; |
| select position = 600; | |
| Update position = 1100; | select position = 600; |
| Commit; | Update position = 1100; |
| commit; |
不管是讀已提交或者是可重復(fù)讀這種場景都是正常, 但是當(dāng)這兩個(gè)事務(wù)提交完成以后, 用戶會(huì)發(fā)現(xiàn)自己賬戶少了500的資產(chǎn), 為什么呢, 因?yàn)槭聞?wù)2把事務(wù)1的結(jié)果覆蓋了, 這種情況叫丟失更新;
如何避免呢?
- 原子寫, update position = position + 500; 現(xiàn)在數(shù)據(jù)庫基本都支持這種原子寫了;
- 顯示鎖定: select for update;
- CAS: 加樂觀鎖 update position = 1100 where position = 600;
還有一種更神奇的場景: 當(dāng)我們需要在一個(gè)事務(wù)里操作多個(gè)對(duì)象的時(shí)候, 比如購買產(chǎn)品需要先扣余額, 再加資產(chǎn);
賬戶初始金額是1000;
<colgroup><col span="1" width="426"><col span="1" width="426"></colgroup>
| 事務(wù)1 | 事務(wù)2 |
|---|---|
| Begin; | Begin; |
| Select account = 1000; | |
| if(account > 0) then; | Select account = 1000; |
| update account = 0; | if(account > 0) then; |
| update position = position + 1000; | update account = 0; |
| commit; | update position = position + 1000; |
| commit; |
在事務(wù)中先判斷余額是否足夠, 如果單個(gè)事務(wù)執(zhí)行, 上面的邏輯沒有任何問題; 但是當(dāng)兩個(gè)事務(wù)并發(fā)的時(shí)候, 只要有一個(gè)事務(wù)生效了, 另一個(gè)事務(wù)的前提其實(shí)已經(jīng)不滿足了, 但是現(xiàn)在并發(fā)的結(jié)果是, 明明只有1000塊, 卻購買了2000塊的產(chǎn)品并成功加了資產(chǎn); 這種問題為寫偏差, 也稱為幻讀, 寫偏差的類型其實(shí)很好總結(jié)的:
- select符合條件的記錄, 然后做判斷;
- 如果第一個(gè)條件是滿足的, 就進(jìn)行后續(xù)寫入操作;
解決方案:
- 對(duì)先決條件加鎖, 即select for update;
- 有些場景里是沒法加鎖的, 比如用戶余額這種場景, 那這個(gè)時(shí)候怎么辦呢? 我們可以人為的在數(shù)據(jù)庫中引入一個(gè)可加鎖的對(duì)象, 比如扣款申請(qǐng)單, 那么用戶加資產(chǎn)就可以拆分為 扣余額 和 加資產(chǎn)兩個(gè)不同的事務(wù), 每個(gè)事務(wù)都可以引入原子寫了;
當(dāng)然 以上兩個(gè)問題都可以用串行化的隔離級(jí)別來搞定, 但是這個(gè)隔離級(jí)別效率實(shí)在太低了;
2.4.2 強(qiáng)隔離級(jí)別
只有串行化(Serializable)是強(qiáng)隔離性的, 字面意思就是如果事務(wù)并發(fā)了, 一個(gè)時(shí)間點(diǎn)只會(huì)有一個(gè)事務(wù)在運(yùn)行, 其他都需要等待, 正因?yàn)榇谢瘓?zhí)行, 性能非常低, 所有在實(shí)踐中運(yùn)用的比較少, 甚至于有些數(shù)據(jù)庫, 比如Oracle中壓根就沒實(shí)現(xiàn), 雖然有可序列化這個(gè)級(jí)別, 但是真正的實(shí)現(xiàn)確是快照隔離(在下面的弱隔離級(jí)別中會(huì)討論);
2.4.2.1 2PL
兩階段鎖(注意不是2階段提交): 即當(dāng)多個(gè)事務(wù)操作需要寫入同一個(gè)對(duì)象時(shí)(修改或刪除), 需要單獨(dú)占有這個(gè)對(duì)象(可以理解為JAVA中的獨(dú)占鎖):
- 如果對(duì)象A已經(jīng)被事務(wù)1讀取了, 這時(shí)候事務(wù)B需要操作對(duì)象A, 必須等事務(wù)A提交或者終止;
- 如果對(duì)象A已經(jīng)被事務(wù)1操作了, 這時(shí)候事務(wù)B需要讀取對(duì)象A, 必須等事務(wù)A提交或者終止;
所以兩階段鎖是即阻塞讀也阻塞寫, 來保證沒有臟讀與臟寫;
2PL的實(shí)現(xiàn)
2PL的實(shí)現(xiàn)主要是通過對(duì)數(shù)據(jù)庫中每個(gè)對(duì)象加鎖來實(shí)現(xiàn)的, 鎖分為共享模式和獨(dú)占模式;
- 事務(wù)會(huì)以共享鎖來讀取對(duì)象, 但是如果這個(gè)對(duì)象已經(jīng)被其他事務(wù)使用獨(dú)占鎖鎖定了, 則當(dāng)前的讀事務(wù)必須等待;
- 事務(wù)需要操作對(duì)象時(shí), 必須獲取獨(dú)占鎖; 但是如果當(dāng)前對(duì)象已經(jīng)有其他事務(wù)獲取了共享鎖或者獨(dú)占鎖, 則當(dāng)前事務(wù)必須等待;
- 事務(wù)先以共享鎖讀取了對(duì)象, 但是寫入對(duì)象時(shí), 需要將共享鎖升級(jí)為獨(dú)占鎖; 鎖升級(jí)過程與獲取獨(dú)占鎖一致;
- 事務(wù)獲取鎖以后, 必須持有至事務(wù)結(jié)束(commit | abort)才能釋放鎖; 這也是兩階段鎖的名稱來源: 一階段(事務(wù)開始或者執(zhí)行時(shí))獲取鎖, 二階段(事務(wù)結(jié)束)釋放鎖;
既然用到了鎖, 且一個(gè)事務(wù)中可能需要去獲取多把鎖, 那么就很有可能發(fā)生死鎖: 事務(wù)A持有A對(duì)象的鎖, 需要去獲取B對(duì)象的鎖, 但是事務(wù)B已經(jīng)持有了B對(duì)象的鎖, 需要去獲取A對(duì)象的鎖, 這個(gè)時(shí)候就會(huì)發(fā)生死鎖;
死鎖發(fā)生以后, 數(shù)據(jù)庫會(huì)自動(dòng)檢測并中止其中一個(gè)事務(wù), 以便讓另一個(gè)事務(wù)繼續(xù)進(jìn)行, 中止的事務(wù)的重試有應(yīng)用系統(tǒng)完成;這也是兩階段鎖效率低下的一個(gè)原因;
2.4.3 串行化快照隔離(SSI)
弱隔離級(jí)別會(huì)有臟讀, 不可重復(fù)讀, 幻讀等問題, 但是采用強(qiáng)隔離級(jí)別就會(huì)有性能問題, 那么有沒有一種方案能兼容這兩者的優(yōu)點(diǎn)呢?那就是2008年才被提出來的串行化快照隔離;