本地事務
事務特性:ACID,其中C一致性是目的,AID是手段,
實作隔離性
寫鎖:資料加了寫鎖,其他事務不能寫也不能讀,
讀鎖:資料加了讀鎖,其他事務不能加寫鎖可以加讀鎖,可以允許自己升級為寫鎖,
范圍鎖:對某個范圍加寫鎖,范圍內資料不能寫入,
隔離級別
以鎖為手段來實作隔離性才是資料庫表現出不同隔離級別的根本原因,
可串行化:對事務所有讀、寫資料加上三種鎖,
可重復讀:不加范圍鎖,會有幻讀問題,
幻讀是指在事務執行程序中,兩個完全相同的范圍查詢得到了不同的結果集,譬如現在準備統計一下 Fenix's Bookstore 中售價小于 100 元的書有多少本,會執行以下第一條 SQL 陳述句:
SELECT count(1) FROM books WHERE price < 100 /* 時間順序:1,事務: T1 */
INSERT INTO books(name,price) VALUES ('深入理解Java虛擬機',90) /* 時間順序:2,事務: T2 */
SELECT count(1) FROM books WHERE price < 100 /* 時間順序:3,事務: T1 */
兩次執行之間有另外一個事務在資料庫插入了一本小于 100 元的書籍,那這兩次相同的查詢就會得到不
一樣的結果,原因是可重復讀沒有范圍鎖來禁止在該范圍內插入新的資料,這是一個事務受到其他事務影響,隔離性被破壞的表現,
讀已提交:寫鎖會一直持續到事務結束,讀鎖在查詢操作完成后馬上釋放,有不可重復讀問題,讀已提交的隔離級別缺乏貫穿整個事務周期的讀鎖,無法禁止讀取過的資料發生變化,
讀已提交:有臟讀問題,
MVCC
MVCC是并發訪問控制技術,解決了讀寫沖突問題(幻讀),
基本思路是對資料庫的修改不會直接覆寫之前的資料,而是產生一個新版副本與老版本共存,
“版本”可以理解為每一行記錄存在倆看不見的欄位:CREATE_VERRSION和DELETE_VERSION,這兩個欄位都是事務ID,事務ID是全域遞增的值,根據以下規則寫入資料,
- 插入資料:CREATE_VERRSION記錄插入資料的事務ID,DELETE_VERSION為空,
- 洗掉資料:DELETE_VERSION記錄洗掉資料的事務ID,CREATE_VERRSION為空,
- 修改資料:將修改資料視為“洗掉舊資料,插入新資料”,將原有資料復制一份,原有資料DELETE_VERSION記錄修改資料的事務ID,復制出來的新資料CREATE_VERSION記錄修改資料的事務ID,
此時,有另一個事務讀取這些發生了變化的資料,根據隔離級別決定讀取哪個版本的資料,
- 可重復讀:在“總是讀取CREATE_VERSION小于等于當前事務ID的資料”前提下,如果資料有多個版本,讀取事務ID最大的,
- 讀已提交:總是讀取最新版本,即最近被Commit版本的資料,
MVCC是針對“讀+寫”的優化,“寫+寫”只能加鎖解決,競爭激烈的情況下,樂觀鎖可能更慢,
MVCC超售問題
資料庫采用的是MVCC方案,是否有可能出現以下這種超售情況
初始quantity值為10,事務T1和事務T2都想要將quantity減8
SELECT quantity FROM books WHERE id=1 /* 時間順序:1,事務: T1 */
SELECT quantity FROM books WHERE id=1 /* 時間順序:2,事務: T2 */
/*事務: T1 運算后將quantity改為2 因為MVCC方案中事務2的select并不會加讀鎖,所以這條陳述句可以順利執行并commit*/
UPDATE books SET quantity=2 WHERE id=1 /* 時間順序:3,事務: T1 */
commit
/*事務: T2 運算后將quantity改為2 ,此時沒有其他的事務了,所以這條陳述句可以順利執行并commit*/
UPDATE books SET quantity=2 WHERE id=1 /* 時間順序:4,事務: T2 */
commit
這種寫法會出現超售,相當于賣了兩次8本書,
之前提到過,MVCC只解決“讀-寫”事務的情況(也就是解決可重復讀級別下的幻讀),在“寫-寫”的場景中它是不適用的,
也正是為了解決這類情況,InnoDB之類采用MVCC的引擎,都會提供諸如“lock in share mode”的語法,讓開發者在“寫-寫”的場景中顯式加共享鎖,讓資料庫進行當前讀而非快照讀,
以MySQL為例,把代碼修改為這樣,它就可以保證T1的Update陳述句被T2的共享鎖阻塞了,達到避免超售的目的了,
SELECT quantity FROM books WHERE id=1 lock in share mode; /* 時間順序:1,事務: T1 */
SELECT quantity FROM books WHERE id=1 lock in share mode; /* 時間順序:2,事務: T2 */
全域事務
2PC
假如你平時以宣告式事務來編碼,那它與本地事務看起來可能沒什么區別,都是標個@Transactional注解而已,但如果以編程式事務來實作的話,就能在寫法上看出差異,偽代碼如下所示:
public void buyBook(PaymentBill bill) {
userTransaction.begin();
warehouseTransaction.begin();
businessTransaction.begin();
try {
userAccountService.pay(bill.getMoney());
warehouseService.deliver(bill.getItems());
businessAccountService.receipt(bill.getMoney());
userTransaction.commit();
warehouseTransaction.commit();
businessTransaction.commit();
} catch(Exception e) {
userTransaction.rollback();
warehouseTransaction.rollback();
businessTransaction.rollback();
}
}
從代碼上可看出,程式的目的是要做三次事務提交,但實際上代碼并不能這樣寫,試想一下,如果在
businessTransaction.commit()中出現錯誤,代碼轉到catch塊中執行,此時userTransaction和
warehouseTransaction已經完成提交,再去呼叫rollback()方法已經無濟于事,這將導致一部分資料被提
交,另一部分被回滾,整個事務的一致性也就無法保證了,
為了解決這個問題,XA 將事務提交拆分成為兩階段程序:
- 準備階段:又作投票階段,協調者詢問事務的所有參與者是否準備好提交,參與者如果準備好提交則回復Prepared,對于資料庫來說,準備操作是在重做日志中記錄全部事務提交操作所要做的內容,不釋放隔離性,繼續持有鎖,
- 提交階段:又作執行階段,協調者在上一階段收到Prepared訊息,先自己在本地持久化事務狀態為Commit,操作完成之后向所有參與者發送Commit指令;否則,任意一個參與者回復了 Non-Prepared 訊息,或任意一個參與者超時未回復,協調者將將自己的事務狀態持久化為 Abort 之后,向所有參與者發送 Abort 指令,參與者立即執行回滾操作,
缺點
-
單點問題:協調者在兩段提交中具有舉足輕重的作用,協調者等待參與者回復時可以有超時機制,允許參與者宕機,但參與者等待協調者指令時無法做超時處理,一旦宕機的不是其中某個參與者,而是協調者的話,所有參與者都會受到影響,如果協調者一直沒有恢復,沒有正常發送 Commit 或者 Rollback 的指令,那所有參與者都必須一直等待,
-
性能問題:兩段提交程序中,所有參與者相當于被系結成為一個統一調度的整體,期間要經過兩次遠程服務呼叫,三次資料持久化(準備階段寫重做日志,協調者做狀態持久化,提交階段在日志寫入 Commit Record),整個程序將持續到參與者集群中最慢的那一個處理操作結束為止,這決定了兩段式提交的性能通常都較差,
-
一致性風險
3PC
三段式提交把原本的兩段式提交的準備階段細分為兩個階段,分別稱為CanCommit、PreCommit,提交階段改為
DoCommit階段,CanCommit是詢問階段,協調者讓每個參與者根據自身狀態評估事務是否可能完成,
將準備階段一分為二的理由是:協調者發出開始準備的訊息,參與者開始寫重做日志,如果此時,某一個參與者宣布無法完成,相當于大家做了一輪無用功,
因此,在事務需要回滾的場景中,三段式的性能通常是要比兩段式好很多的,但在事務能夠正常提交的場景中,兩者的性能都依然很差,甚至三段式因為多了一次詢問,還要稍微更差一些,
同樣也是由于事務失敗回滾概率變小的原因,在三段式提交中,如果在 PreCommit 階段之后發生了協調者宕機,即參與者沒有能等到 DoCommit 的訊息的話,默認的操作策略將是提交事務而不是回滾事務或者持續等待,這就相當于避免了協調者單點問題的風險,
分布式事務
CAP與ACID
柔性事務與最終一致性,
可靠事件佇列
最大努力一次提交:將最有可能出錯的業務以本地事務的方式完成后,采用不斷重試的方式促使分布式事務中的其他關聯業務全部完成,
TCC事務
如果業務需要隔離,該方案天生適合用于需要強隔離性的分布式事務中,
TCC 較為煩瑣,它是一種業務侵入式較強的事務方案,要求業務處理程序必須拆分為“預留業務資源”和“確認/釋放消費資源”兩個子程序,如同 TCC 的名字所示,它分為以下三個階段,
- Try:嘗試執行階段,完成所有業務可執行性的檢查(保障一致性),并且預留好全部需用到的業務資源(保障隔離性),
- Confirm:確認執行階段,不進行任何業務檢查,直接使用 Try 階段準備的資源來完成業務處理,Confirm 階段可能會重復執行,因此本階段所執行的操作需要具備冪等性,
- Cancel:取消執行階段,釋放 Try 階段預留的業務資源,Cancel 階段可能會重復執行,也需要滿足冪等性,
Reference
《鳳凰架構》
轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/304829.html
標籤:MySQL
上一篇:MySQL高性能索引策略
