事務是應用程式將多個讀寫操作組合成一個邏輯單元的一種方式
從概念上講,事務中的所有讀寫操作被視作單個操作來執行:整個事務要麼成功(提交(commit))要麼失敗(中止(abort),回滾(rollback))。如果失敗,應用程式可以安全地重試。
並不是所有的應用都需要事務,有時候弱化事務保證、或完全放棄事務也是有好處的(例如,為了獲得更高效能或更高可用性)。
事務的棘手概念
ACID含義
原子性
原子是指不能分解成小部分的東西
多執行緒程式設計
- 如果一個執行緒執行一個原子操作,這意味著另一個執行緒無法看到該操作的一半結果。
- 系統只能看到操作前或者操作後的狀態,而不能看到中間狀態
ACID 的原子性是 能夠在錯誤時中止事務,丟棄該事務進行的所有寫入變更的能力。 而不是關於併發的,隔離性才是關於併發的
一致性
ACID 的一致性是 對資料的一組特定約束必須始終成立。即不變數(invariants),例如在會計系統中,所有賬戶整體上必須借貸相抵
- 如果一個事務開始於一個滿足這些不變數的有效資料庫,且在事務處理期間的任何寫入操作都保持這種有效性,那麼可以確定,不變數總是滿足的
- 原子性,隔離性和永續性是資料庫的屬性,而一致性(在ACID意義上)是應用程式的屬性。應用可能依賴資料庫的原子性和隔離屬性來實現一致性,但這並不僅取決於資料庫。因此,字母C不屬於ACID
隔離性
ACID的隔離性是 同時執行的事務是相互隔離的,它們不能相互冒犯
永續性
ACID的永續性是 一個承諾,即一旦事務成功完成,即使發生硬體故障或資料庫崩潰,寫入的任何資料也不會丟失。
如何做到永續性?
- 為了提供永續性保證,資料庫必須等到這些寫入或複製完成後,才能報告事務成功提交。
- 完美的永續性是不存在的:萬一所有硬碟和備份同時被銷燬。
在帶複製的資料庫中
- 永續性可能意味著資料已成功複製到一些節點。
單物件和多物件操作
客戶端在同一事務中執行多次寫入時,資料庫應該做的事:
原子性
不會部分失敗--保證all-or-nothing
隔離性
- 同時執行的事務不應該互相干擾:事務如果多次寫入,要麼事務看到全部寫入結果,要麼什麼都看不到
- 通常需要多物件事務來保持多個資料物件的同步
單物件寫入
- 儲存引擎的普遍目標:對單個節點上的單個物件(例如鍵值對)上提供原子性和隔離性
- 實現方法:緣溪行可以透過日誌來實現崩潰恢復;可以使用每個物件上的鎖來實現隔離(每次只允許一個執行緒訪問物件)
- 更高階的原子操作,但只對單個物件操作有用,不是通常意義上的事務
- 自增操作
- CAS操作
- 事務通常被理解為:將多個物件上的多個操作合併為一個執行單元的機制
多物件事務的必要性
為什麼現在分散式資料儲存放棄了多物件事務
- 多物件事務很難跨分割槽實現
- 在需要高可用性或高效能的情況下,可能會礙事
需要協調寫入幾個不同物件的場景:
- 關係資料模型中,一個表中的行通常具有對另一個表中的行的外來鍵的引用
- 在文件資料模型中,需要一起更新的欄位通常在同一個文件中,這被視為單個物件--需要單個文件時不需要多物件事務。但是,當需要更新非規範化的資訊時,需要一次更新多個文件
- 在具有二級索引的資料庫中(處理純粹的鍵值儲存以外幾乎都有),每次更改值都需要更新索引
沒有原子性,錯誤處理就要複雜的多,缺乏隔離性,就會導致併發問題
處理中止和錯誤
重試一箇中止的事務的問題:
- 如果事務實際成功了,但是在伺服器試圖向客戶端確認提交成功時網路發生故障,那麼重試事務導致事務執行了兩次
- 如果是因為負載過大導致的事務失敗,那重試會將問題變得更糟糕,需要限制重試次數
- 僅在臨時性錯誤(如由於死鎖、異常情況、臨時性網路中斷和故障切換)後才值得重試。發生永久性錯誤後重試無意義
- 如果事務在資料庫之外也有副作用,即使事務被中止,也可能發生這些副作用。比如更新操作後附帶傳送電子郵件。(如果想讓多個不同的系統同時提交或者放棄,需要兩階段提交。)
- 如果客戶端在重試過程中也失敗了,並且沒有其他人負責重試,那麼資料就會丟失
弱隔離級別
併發問題發生的條件:
- 如果兩個事務不觸及相同的資料,那麼可以安全的並行執行
- 如果兩個事務中一讀一寫或者同時寫,才會出現併發問題
讀已提交
沒有髒讀
- 髒讀:一個事務可以看到另一個事務未提交的資料
- 為什麼防止髒讀:髒讀會導致其他事務看到稍後需要回滾的資料
沒有髒寫
- 髒寫:一個事務的寫入覆蓋了另一個事務未提交的寫入
- 為什麼防止髒寫:如果事務更新多個物件,髒寫會導致非預期的錯誤結果
○ Alice 和 Bob 同事購買一輛車,買車需要兩次資料庫寫入:商品列表更新、開發票。
○ 下圖中,Alice 先更新了商品列表,但是被 Bob 覆蓋了商品列表;Bob 先更新了開發票,但是被 Alice 覆蓋。
實現讀已提交
- 如何實現:資料庫透過使用行鎖來防止髒寫/髒讀
- 當事務想要修改/讀取特定物件(行或文件)時,它必須首先獲得該物件的鎖。然後必須持有該鎖直到事務被提交或中止。
- 一次只有一個事務可持有任何給定物件的鎖
- 如果另一個事務要寫入同一個物件,則必須等到第一個事務提交或中止後,才能獲取該鎖並繼續。
- 這種鎖定是讀已提交模式(或更強的隔離級別)的資料庫自動完成的
- 讀鎖的缺點
- 一個長時間執行的寫入事務會迫使很多隻讀事務等到這個慢寫入事務完成
- 因為等待鎖,應用某個部分的遲緩可能由於連鎖效應,導致其他部分出現問題
- 實際方法
- 對於寫入的每個物件,資料庫都會記住舊的已提交值,和由當前持有寫入鎖的事務設定的新值。
- 當事務正在進行時,任何其他讀取物件的事務都會拿到舊值。
- 只有當新值提交後,事務才會切換到讀取新值。
快照隔離和可重複讀
- 讀已提交會存在不可重複讀/讀取偏差的異常情況
- Alice 有 1000 美元,分為兩個賬戶
- 先查了賬戶 1,發現有 500 塊
- 一個事務把賬戶 2 的錢轉了 100 到賬戶 1
- 再查了賬戶 2,發現只有 400 塊
- 導致 Alice 誤以為總的只有 900 塊
- 不能接受不可重複讀的情況:
備份:大型資料庫備份會幾個小時才能完成,如果備份時資料庫仍然接受寫入操作,那麼備份就可能有一些新的部分和舊的部分,從這樣的備份中恢復,那麼資料不一致會變成永久的
分析查詢和完整性檢查:一個分析需要查詢資料庫的大部分內容,如果不同時間點的查詢結果不一樣,那就沒意義 - 解決方案:快照隔離
- 每個事務都從資料庫的一致快照(consistent snapshot) 中讀取,即事務可以看到事務開始時在資料庫中提交的所有資料
- 優點: 快照隔離對長時間執行的只讀查詢(如備份和分析)非常有用。
- 實現快照隔離
- 思路
- 與讀取提交的隔離類似,快照隔離的實現通常使用寫鎖來防止髒寫
- 這意味著進行寫入的事務會阻止另一個事務修改同一個物件
- 但是讀取不需要任何鎖定,即讀不阻塞寫,寫不阻塞讀
-這允許資料庫在處理一致性快照上的長時間查詢時,可以正常地同時處理寫入操作。且兩者間沒有任何鎖定爭用
- 實現
- 資料庫必須可能保留一個物件的幾個不同的提交版本,因為各種正在進行的事務可能需要看到資料庫在不同的時間點的狀態,即同時維護著單個物件的多個版本,稱為多版本併發控制
- 思路
- 保留幾個版本的快照
- 如果一個資料庫只需要提供讀已提交的隔離級別,而不提供快照隔離,那麼保留一個物件的兩個版本就足夠了:提交的版本和被覆蓋但尚未提交的版本。
- 讀已提交為每個查詢使用單獨的快照,而快照隔離對整個事務使用相同的快照
觀察一致性快照的可見性規則
當一個事務從資料庫中讀取時,事務ID用於巨頂它可以看見哪些物件,看不見哪些物件
- 在每次事務開始時,資料庫列出當時所有其他(尚未提交或中止)的事務清單,即使之後提交了,這些事務的寫入也都被忽略
- 被中止事務鎖執行的任何寫入都將被忽略
- 由具有較晚事務ID(即, 在當前事務開始之後開始的)的事務所做的任何寫入都被忽略,而不管這些事務是否已經提交
- 所有其他寫入,對應用都是可見的
一個物件可見的兩個條件:
- 讀事務開始時,建立該物件的事務已經提交
- 物件為被標記為刪除,或如果被標記為刪除,請求刪除的是事務在讀事務開始時尚未提交
索引和快照隔離
索引在多版本資料庫中:
- 索引指向物件的所有版本,再進行索引查詢時,過濾掉當前事務不可見的物件版本
- 垃圾收集機制刪除事務不再可見的舊物件版本時,對應的索引條目也能隨之刪除
- 在PostgreSQL中,如果同一物件的不同版本可放在同一個頁面中,其最佳化措施能避免更新索引
CouchDB、Datomic和LMDB採用的方法
- 使用一種僅追加/寫時複製的B樹變體。在更新時,不會覆蓋樹的頁面,為每個修改頁面建立副本,並且從父頁面到樹根會進行級聯更新,使其指向子頁面的新版本。不受寫入影響的頁面無需複製,可保持原樣
- 採用這種僅追加的B樹方式,每個寫入事務(或一批事務)都會生成一棵新的B樹,在建立時,從該特定樹根生長出來的樹就構成了資料庫的一個一致性快照。
- 後續寫入不能修改現有的B樹,只能建立新的樹根,所以不需要依據事務ID過濾物件,但需要有負責壓縮和垃圾收集的後臺程序來配置運作。
可重複讀與命名混淆
- PostgreSQL和MySQL稱其快照隔離級別為可重複讀
- Oracle中稱為可序列化
防止丟失更新
什麼情況下發生丟失更新
- 從資料庫中讀取一些值,修改它並寫回修改的值(讀取-修改-寫入序列),可能發生丟失更新的問題
- 兩個事務同時執行,其中一個的修改可能會丟失,因為第二個寫入的內容並沒有包括第一個事務的修改(有時說後面寫入clobber(狠揍)了前面的寫入
解決方案
原子寫
原子寫的實現方法
- 遊標穩定性(cursor stability):在讀取物件時,獲取其上的排他鎖實現,使得更新完成之前,沒有其他事務可以讀取它
- 簡單地強制所有的原子操作在單一執行緒上執行
顯示鎖定
如果資料庫不支援內建原子操作,可以是應用程式顯式的鎖定將要更新的物件
多人棋子游戲
BEGIN TRANSACTION;
SELECT * FROM figures
WHERE name = 'robot' AND game_id = 222
FOR UPDATE;
-- 檢查玩家的操作是否有效,然後更新先前SELECT返回棋子的位置。
UPDATE figures SET position = 'c4' WHERE id = 1234;
COMMIT;
- FOR UPDATE 子句告訴資料庫應該對該查詢返回的所有行加鎖
自動檢索丟失的更新
- 原子操作和鎖是透過強制讀取-修改-寫入序列按順序發生,來防止丟失更新的方法
- 可以允許併發執行,如果事務管理器檢測到丟失更新,則中止事務並強制它們重試其讀取-修改-寫入序列
優點
- 資料庫可以結合快照隔離高效的執行此檢查
- PostgreSQL的可重複讀,Oracle的可序列化和SQL Server的快照隔離級別,都會自動檢測到丟失更新,並中止該事務
- MySQL、InnoDB的可重複讀不會檢測丟失更新。一些作者認為,資料庫必須能防止丟失更新才能算是提供了快照隔離,所以在這個定義下,MySQL不提供快照隔離
- 資料庫自定檢查,應用程式不需要任何操作就能使用丟失更新檢測
比較並設定CAS
- 只有當前值從上次讀取時一直未改變,才允許更新發生。
- 如果當前值與先前讀取的值不匹配,則更新不起作用,且必須重試讀取-修改-寫入序列
- 如果更新失敗,需要應用層重試
衝突解決和複製
鎖和CAS操作的缺點:
- 鎖和CAS操作假定只有一個最新的資料副本
- 多主和無主複製的資料庫允許多個寫入併發執行,並非同步複製到副本上,無法保證只有一個最新資料的副本,因此鎖或CAS操作的技術不適用此
複製資料庫解決衝突的常用方法:允許併發寫入建立多個衝突版本的值(也稱為兄弟),並使用應用程式碼或特殊資料結構在事實發生之後解決和合並這些版本
原子操作的適用條件:可以在複製的上下文中很好地工作,尤其當它們具有可交換性時(即,可以在不同的副本上以不同的順序應用它們,且仍然可以得到相同的結果)
最後寫入勝利(LWW)缺點:容易對是更新,但LWW是許多資料庫中的預設方案
寫入偏斜與幻讀
- 髒寫或丟失更新:不同事務併發地嘗試寫入相同的物件
- 其他併發問題:修改不同的物件
例如:醫院通常會同時要求幾位醫生待命,但底線是至少有一位醫生在待命。醫生可以放棄他們的班次(例如,如果他們自己生病了),只要至少有一個同事在這一班中繼續工作。Alice和Bob是兩位值班醫生,同時請假:
應用首先檢查是否有兩個或以上的醫生正在值班;
- 如果是的話,它就假定一名醫生可以安全地休班。由於資料庫使用快照隔離,兩次檢查都返回 2 ,所以兩個事務都進入下一個階段。
- Alice更新自己的記錄休班了,而Bob也做了一樣的事情。
- 兩個事務都成功提交了,現在沒有醫生值班了。
- 違反了至少有一名醫生在值班的要求
寫偏差:如果兩個事務讀取相同的物件,然後更新其中一些物件(不同的事務可能更新不同的物件),可能會發生寫偏差
解決方案較少的原因:
- 由於設計多個物件,單物件的原子操作不起作用
- 在一些快照隔離的實現中,自動檢測丟失更新對此沒有幫助。目前的可重複讀、快照隔離級別中,都不會自動檢測寫入偏差。自動防止寫入偏差需要真正的可序列化隔離
- 某些資料庫允許配置約束,然後資料庫強制執行(例如,唯一性,外來鍵約束或特定值限制)
解決辦法:
-
使用可序列化隔離級別
-
顯示鎖定事務所依賴的行
-
使用唯一約束
FOR UPDATE告訴資料庫鎖定返回的所有行以用於更新
BEGIN TRANSACTION; SELECT * FROM doctors WHERE on_call = TRUE AND shift_id = 1234 FOR UPDATE; UPDATE doctors SET on_call = FALSE WHERE name = 'Alice' AND shift_id = 1234; COMMIT;
寫入偏差更多例子
會議室預定
防止會議室被同一個會議室多次預定,需要檢查時間是否有重疊
BEGIN TRANSACTION;
-- 檢查所有現存的與12:00~13:00重疊的預定
SELECT COUNT(*) FROM bookings
WHERE room_id = 123 AND
end_time > '2015-01-01 12:00' AND start_time '2015-01-01 13:00';
-- 如果之前的查詢返回0
INSERT INTO bookings(room_id, start_time, end_time, user_id)
VALUES (123, '2015-01-01 12:00', '2015-01-01 13:00', 666);
COMMIT;
多人遊戲
● 可以用鎖防止丟失更新(也就是確保兩個玩家不能同時移動同一個棋子)。
● 但是鎖定並不妨礙玩家將兩個不同的棋子移動到棋盤上的相同位置,或者採取其他違反遊戲規則的行為。
● 按照您正在執行的規則型別,也許可以使用唯一約束(unique constraint),否則您很容易發生寫入偏差
搶注使用者名稱
● 在每個使用者擁有唯一使用者名稱的網站上,兩個使用者可能會嘗試同時建立具有相同使用者名稱的帳戶。
● 快照隔離下這是不安全的。
● 幸運的是,唯一約束是一個簡單的解決辦法(第二個事務在提交時會因為違反使用者名稱唯一約束而被中止)。
幻讀
幻讀:一個事務中的寫入改變另一個事務的搜尋查詢的結果。快照隔離避免了只讀查詢中的幻讀,但是會導致寫入偏差的問題
物化衝突:將幻讀變為資料庫中一組具體行上的鎖衝突
- 如果幻讀的問題是沒有物件可以加鎖,也許可以人為的在資料庫中引入一個鎖物件
- 例如,在會議室預定的場景下,可以建立一個關於時間槽和房間的表。要建立預定的事務可以鎖定(select for update)表中與所需房間和時間段對應的行。在獲得鎖定之後,可以檢查重疊的預定並像以前一樣插入新的預定
- 缺點:弄清楚如何物化衝突比較難,也容易出錯,而讓併發控制機制洩漏到應用資料模型是很醜陋的做法
可序列化隔離級別更可取
可序列化
真的序列執行
避免併發問題的最簡單方法:在單個執行緒上按栓徐一次只執行一個事務
優點:設計用於單執行緒執行的系統有時可以比支援併發的系統更好,因為它可以避免鎖的協調開銷
缺點:其吞吐量僅限於單個CPU核的吞吐量,並且為了充分利用單一執行緒,需要與傳統形式的事務不同的結構
在儲存過程中封裝事務
- 互動式的事務中,為了提高吞吐量,必須允許資料庫併發處理
- 採用單執行緒序列事務處理的系統不允許互動式的多語句事務,就要求應用程式必須將整個事務程式碼作為儲存過程提交給資料庫
儲存過程的優缺點
- 儲存過程名聲不好的原因
- 每個資料庫廠商都有自己的儲存過程語言,而不是通用語言如java
- 資料庫程式碼管理困難,除錯困難,版本控制困難
- 資料庫通常比應用伺服器對效能敏感的多,或資料庫中一個寫得不好的儲存過程(例如,佔用大量記憶體或CPU時間)會比在應用伺服器中相同的程式碼造成更多的麻煩。
- 克服方法:現代的儲存過程實現放棄了PL/SQL,而是使用現有的通用程式語言VoltDB使用Java或Groovy,Datomic使用Java或Clojure,而Redis使用Lua
- 單執行緒執行事務變得可行
- 儲存過程與記憶體儲存,使得在單個執行緒上執行所有事務變得可行。由於不需要等待I/O,且避免了併發控制機制的開銷,他們可以在單個執行緒上實現相當好的吞吐量
- voltDB還是用儲存過程進行復制
分割槽
- 問題:順序寫導致寫入吞吐比較高的應用,單執行緒事務處理器可能成為一個嚴重的瓶頸
- 解決方法:分割槽,每個分割槽可以擁有自己獨立執行的事務處理執行緒
- 缺點:
- 需要訪問多個分割槽的任何事務,資料庫必須在觸及的所有分割槽之間協調事務
- 儲存過程需要跨越所有分割槽鎖定執行,以確保整個系統的可序列化
- 跨分割槽事務比單分割槽事務慢
- 事務能否分割槽取決於應用資料的結構:kv儲存容易分割槽,但是多個二級索引的資料不方便分割槽
真的序列執行事務的條件
- 每個事務必須小而快,只要有一個緩慢的事務,就會拖慢所有事務處理
- 僅限於活躍資料集可以放入記憶體的情況。很少訪問的資料可能會被移動到磁碟,但如果需要在單執行緒執行的事務中訪問,系統會變得非常慢
- 寫入吞吐量必須低到能在單個CPU核上處理,否則需要分割槽,但最好沒有跨分割槽事務
- 跨分割槽事務也可以支援,但是佔比必須小
兩階段鎖定(2PL)
實現兩階段鎖
- 使用場景:2PL用於MySQL(InnoDB)和SQL Server中的可序列化隔離級別,以及DB2中的可重複讀隔離級別
- 實現方式:讀與寫的阻塞是透過為資料庫中每個物件新增鎖實現的,鎖可以處於共享模式或獨佔模式
- 鎖的使用
- 若事務要讀取物件,則須先以共享模式獲取鎖。允許多個事務同時持有共享鎖。但如果另一個事務已經在物件上持有獨佔鎖,則這些事務必須等待
- 若事務要寫入一個物件,它必須首先以獨佔模式獲取該鎖。不允許其他事務可以同時持有該鎖(無論是共享模式還是獨佔模式)。即,如果物件上存在任何鎖,則修改事務必須等待
- 如果事務先讀取再寫入物件,則它可能會將其共享鎖升級為獨佔鎖。升級鎖的工作等價於直接獲取獨佔鎖
- 事務獲取鎖之後,必須等待持有鎖直到事務結束(提交或中止)。兩階段鎖:第一階段(當事務正在執行時)獲取鎖,第二階段(在事務結束時)釋放所有的鎖
- 可能會發生死鎖:資料庫會檢測事務之間的死鎖
- 兩階段鎖定的效能差的表現
- 執行2PL的資料庫可能具有相當不穩定的延遲,如果在工作負載中存在爭用,那麼可能高百分位點處的響應會非常慢
- 可能只需要一個緩慢的事務,或者一個訪問大量資料並獲取許多鎖的事務,就能把系統的其他部分拖慢,甚至迫使系統停機
- 兩階段鎖定的效能差的原因
- 獲取和釋放鎖的開銷,更重要的是併發行的降低
- 如果兩個併發事務試圖做任何可能導致競爭條件的事情,必須等待另一個完成
謂詞鎖
- 謂詞鎖:類似共享/排他鎖,但不屬於特定的物件,屬於所有符合某些搜尋條件的物件
- 謂詞鎖限制訪問的方式
- 如果事務A想要讀取匹配某些條件的物件,就像這個SELECT查詢中那樣,它必須獲取查詢條件上的共享謂詞鎖(shared-mode predicate lock)。如果另一個事務B持有任何滿足這一查詢條件物件的排他鎖,那麼A必須等到B釋放它的鎖之後才允許進行查詢
- 如果事務A想要插入,更新或刪除任何物件,則必須首先檢查舊值或新值是否與任何現有的謂詞鎖匹配。如果事務B持有匹配的謂詞鎖,那麼A必須等到B已經提交中止後才能繼續
關鍵思想
- 謂詞鎖甚至適用於資料庫中尚不存在,但將來可能會新增的物件(幻象)。
- 如果兩階段鎖定包含謂詞鎖,則資料庫將阻止所有形式的寫入偏差和其他競爭條件,因此其隔離實現了可序列化。
索引範圍鎖/間隙鎖
- 問題:謂詞鎖效能不佳,如果活躍事務持有很多鎖,檢查匹配的鎖會非常耗時。多數運用2PL的資料庫實際實現的是索引範圍鎖(間隙鎖),屬於謂詞鎖的簡化近似版。
- 實現:讓謂詞匹配更大集合來簡化謂詞鎖。
- 例如:若資料庫基於 room_id 列有索引,查詢 123 號房間現有預訂時,可將共享鎖附加到該索引項上,表示事務對該房間預訂進行了搜尋;若基於時間索引查詢現有預訂,就把共享鎖附加到對應時間範圍的一系列值上,意味著事務標記了該時段用於預訂。如此一來,其他事務若要插入、更新或刪除同一房間和 / 或重疊時間段預訂,更新索引相同部分時會遇到共享鎖,需等鎖釋放。
- 作用:能有效防止幻讀和寫入偏差
- 如果沒有索引,會退化到整個表的共享鎖。對效能不利,但是比較安全
可序列化快照隔離(SSL)
悲觀與樂觀的併發控制
- 兩階段鎖是悲觀併發控制:如果有事情可能出錯(如另一個事務所持有的鎖所表示的),最好等到情況安全後再做任何事情
- 序列化快照隔離是樂觀併發控制:如果存在潛在的危險,也不阻止事務,而是繼續執行事務。當一個事務想要提交時,資料庫檢查是否有什麼不好的事情發生(即隔離是否被違反);如果是的話,事務將被中止,並且必須重試,只有可序列化的事務才被允許提交
適用場景
- 如果存在很多contention(很多事務試圖訪問相同的物件),則表現不佳,因為會導致很大一部分事務需要中止
- 如果有足夠的備用容量,並且事務之間的爭用不是很高,樂觀的併發控制技術往往比悲觀的要好
區別於樂觀併發控制技術
- SSL基於快照,即事務中的所有讀取都是來自資料庫的一致性快照
- 在快照隔離的基礎上,ssl透過演算法來檢測寫入之間的序列化衝突,並確定要中止哪些事務
基於過時前提的決策
- 資料庫判斷結果是否可能已經已經改變
- 檢測對舊MVCC物件版本的讀取(讀之前存在未提交的寫入)
- 檢測影響先前讀取的寫入(讀之後發生寫入)
檢測舊MVCC讀取
- 快照隔離出現寫入偏差的原因:一個事務從MVCC資料庫中的一致性快照讀時,將忽略取快照時尚未提交的任何其他事務所做的寫入
- 如何避免:資料庫需要跟蹤一個事務由於MVCC可見性規則而忽略另一個事務的寫入。當事務想要提交時,資料庫檢查是否有任何被忽略的寫入現在已經被提交,如果是這樣,事務必須中止
- 為什麼等到提交時才檢查和中止:因為另一個事務可能是隻讀事務,或者本事務可能提交中止或者仍未提交。在提交時再判斷,可以避免不必要的事務中止
檢測影響之前讀取的寫入
- SSI 採用和索引範圍鎖類似的技術,除了SSI鎖不會阻塞其他事務
- 資料庫可以索引項來記錄讀取這個資料的事務
- 寫入時的通知機制:事務寫入資料庫時,需在索引裡查詢曾讀取受影響資料的其他事務,這類似獲取寫鎖,但此鎖不阻塞事務,只是起到通知作用,告知其他事務其之前讀取的資料可能已過時。例如事務 43 和 42 都讀取班次 1234 相關資料,事務 43 寫操作會通知事務 42 其先前讀已過時,反之亦然,最終依據提交順序及衝突情況決定事務是否要中止(如事務 43 因事務 42 先提交且衝突寫入已生效,所以事務 43 需中止)。
可序列化快照隔離的效能
- 與兩階段鎖定相比,優勢:
- 一個事務不需要阻塞等待另一事務所持有的鎖。就像再快照隔離下一樣,寫不會阻塞讀,反之亦然
- 使得查詢延遲更可預測,變數更少。特別是,只讀查詢可以執行在一致性快照上,而不需要任何鎖定,這對於讀取繁重的工作負載非常有吸引力
與序列執行相比,可序列化快照隔離優點
- 並不侷限與單個CPU核的吞吐量
- 即使資料可能跨很多臺機器進行分割槽,事務也可以在保證可序列化隔離等級的同時讀寫多個分割槽中的資料
適用場景
- 中止率顯著影響ssl的整體表現。例如,長時間讀取和寫入資料的事務很可能會發生衝突並中止,因此SSI要求同時讀寫的事務儘量短(只讀的長事務可能沒問題)
- ssl可能比兩階段鎖定或序列執行更能容忍慢事務