Design Data Intensive Applications 筆記 (事務)

事務特性:

  • Atomicity (原子性) :
    定義: 用戶發起屢次寫請求: 中間某次寫失敗則全部寫操做都被回滾數據庫

    實現方式: 災害恢復日誌網絡

  • Consistency (一致性):
    定義: 應用層面的不變性在事務中被保持併發

  • Isolation (隔離性):
    定義: 多個事務操做同一筆數據的時候效果互不影響, 不產生racing condition
    好比多個事務同時從一個帳號扣減金額,最終金額應該等於初始金額-總扣減金額

隔離級別: 解決事務併發對數據產生影響的問題
常見的併發問題:分佈式

  • 髒讀 (dirty read):事務2讀到事務1未提交的寫操做
  • 髒寫 (dirty write): 事務2覆蓋事務1未提交的寫操做. 問題: 多對象更新出現不一致
  • 不可重複讀 (non-repeatable read): 事務中和事務結束時讀到的數據不一樣 (多數爲統計查詢而且短暫不一致). 可是有時這種不一致性沒法容忍. 好比: 數據庫複製備份. 大範圍分析查詢
  • 幻影讀 (phantom read): 事務一的寫操做的結果影響事務二的查詢結果
  • 寫誤差 (write skew): 兩個事務同時檢測某個前提條件知足,執行寫操做 (未必是同一對象) 並提交, 結果致使前提條件再也不被知足. 好比:醫院規定最多不能超過兩個醫生同時請假, A和B同時申請假期. 前提條件都知足,但結果是兩個醫生同時請假了.
隔離級別 描述 髒讀 不可重複讀 幻影讀 實現方式
serializable 併發的事務等同於事務一個個執行,徹底沒有併發問題,可是性能最差 false false false 實際序列化執行, 二段鎖, 可序列化快照分離
repeatable read 快照分離(snapshot isolation): 事務中讀取事務開始前的提交數據,即便其餘事務在期間改動數據並提交 false false true MVCC (Multi-version concurrency control) 記錄數據的多個版本,找到事務開始前的數據版本
read committed 事務提交的數據才能被讀到, 寫操做只能覆蓋提交的數據 false true true 防止髒寫: 對事務中寫對象持有行級鎖直到事務結束. 防止髒讀: 記錄數據的2個版本,沒法解決計數器更新丟失問題
  • Durability (持久性):
    定義: 事務提交以後數據被保存到持久化存儲,不會由於宕機而丟失ide

    實現方式:
    單機: 先寫日誌(write ahead log)
    集羣: 保證寫操做被持久化到多個節點性能

單對象寫:

處理複雜對象如JSON等, 須要保證原子性和隔離性
原子性經過先寫日誌 (write ahead log) 保證
隔離性經過鎖或者compare and swap, 有的數據庫提供原子性的read-modify-write操做如increment
不一樣於事務, 事務通常是針對多個不一樣對象的處理
許多分佈式數據庫由於實現上的困難只保證單對象寫而放棄多對象事務
多對象事務仍然在如下場景須要:
關係型數據庫:確保外鍵一致性被正確保持
文檔型數據庫: 因爲數據被DENORMALIZE,一般一次更新會涉及多個文檔,須要保證多個文檔被同時正確更新
存在索引的狀況下, 保證數據更新和索引的一致性優化

事務錯誤恢復:

放棄執行到一半的事務並重試
重試可能存在的問題:
事務成功執行但由於網絡錯誤沒法獲得事務結果:
若是錯誤是由於系統超載,重試只會讓問題變得更糟
重試通常能解決暫時問題, 死鎖,網絡異常,隔離級別錯誤等, 若是是永久性錯誤引發的問題(違反外鍵約束等), 重試沒有意義
重試不能解決事務中數據庫以外額外影響, 須要輔助二段式事務等方式
重試中若是客戶進程崩潰會致使數據丟失線程

MVCC實現方式:

寫數據時,修改的數據被TAG惟一自增交易ID(用於斷定交易發生前後), 例如transactionID = 13的事務修改ID爲1的數據從V1->V2, 再修改ID爲2的數據從V1->V2, 爲每次操做生成修改前和修改後兩條交易版本數據:
id=1, dataValue=V1, createdBy=5, deleteBy=13
id=1, dataValue=V2, createBy=13, deleteBy=nil
id=2, dataValue=V1, createdBy=5, deleteBy=13
id=2, dataValue=V2, createdBy=13, deleteBy=nil
MVCC數據可見規則:日誌

  • 事務開始前,全部正在進行的事務對數據的修改不可見,即便最後事務被提交
  • 取消的事務對數據的修改會被忽略
  • 事務開始後,新的事務(transactionID >= currentTransactionID)對數據修改不可見, 即便最後事務被提交
  • 其餘寫操做對讀事務可見

換而言之, 若是一個對象對於讀事務可見: 對象

  • 讀數據的事務開始時,建立對象的事務已經提交.
  • 對象沒有被刪除
  • 對象已經被刪除,可是刪除對象的事務在讀事務開始時還沒提交

MVCC索引實現和優化:

  • 索引葉節點須要指向數據全部版本,再經過transactionId過濾
  • 若是更新的數據和原來在同一數據頁,則能夠避免更新索引 (POSTGRES)
  • 複製有數據更新的索引頁和直到根節點的全部索引頁,查詢時無需作數據過濾,可是須要按期作索引壓縮

防止寫丟失:

問題定義: 兩個事務同時讀取數據,進行更新,再將數據寫回,其中一個事務的寫結果被另外一個覆蓋. 好比計數器更新
解決方法:

  • 原子化更新: update table set value = value + 1 where id = 1
  • 顯式加鎖: select ... for update
  • 自動寫丟失檢測, 取消並重試衝突事務 (PostgreSQL’s repeatable read, Oracle’s serializable, and SQL Server’s snapshot isolation) (MySQL repeatable read 不支持)
  • 比較寫入 (CAS): update table set value = V2 where id = 1 and value = V1 (若是數據庫支持從舊快照讀則無效)

在集羣複製場景下(多主複製或無主複製): 比較寫入方法不能正常工做, LAST WRITE WIN策略會形成寫丟失. 原子化更新有效

防止寫誤差:

衝突實體化 (materialize conflict): 將寫衝突轉化爲一系列的主鍵鎖. 好比建立預訂房間記錄(房間號+時間範圍),並對預訂記錄加鎖. 將併發控制處理滲漏到業務邏輯中,不推薦.

實際序列化執行 (actual serialization):
實現方式:

  • 單線程按順序處理事務. (REDIS, VOLTDB 等)
  • 存儲過程執行事務
  • 數據分片: 對每一個分片順序執行事務 (跨分片事務性能有較大損失)
    適合場景:
  • 事務影響的數據集不大,可存儲在內存中
  • 單個事務讀寫很少
  • 存儲過程沒法實現用戶交互式事務
  • 對事務處理吞吐量要求不高

二階段鎖 (two phase locking):
實現方式:

  • 事務讀數據時必須得到讀鎖(共享模式),多個讀鎖可共存,可是若是有寫鎖,讀操做必須等待
  • 事務寫數據時必須得到寫鎖(排他模式),若是有其餘鎖(讀或寫), 寫操做必須等待
  • 事務先讀後寫, 讀鎖必須升級爲寫鎖
  • 鎖必須被持有直到事務結束

缺點:

  • 併發度降低致使吞吐量降低
  • 執行延遲不可預測
  • 死鎖更加容易發生

前提鎖 (predicate lock):
鎖符合查詢條件的對象,讀寫鎖獲取和二階段鎖相似

索引範圍鎖 (index range lock):
比predicate lock範圍大,不精確匹配全部查詢條件而匹配其中某個索引查詢,以下午1:00-2:00對某個房間的預訂. 直接對房間號加鎖或者對時間範圍加鎖.
若是沒有找到合適的索引,對全表加共享鎖.

可序列化快照分離 (serializable snapshot isolation):

  • 兼顧性能和序列性
  • 採起樂觀併發控制,在事務提交的時候檢查衝突,而後取消事務並重試
  • 樂觀併發控制適合數據爭用不太多的場景,不然會致使大量事務取消重試

實現方式:
根據事務開始前預設定的前提決定事務是否能提交, (好比每一個房間/時間段預訂數<=1). 而後檢查查詢結果是否被改變.
事務1讀到一箇舊的MVCC版本(數據已經被另外一個事務修改但未提交), 再提交時發現新的MVCC版本已經被事務2提交, 而且後續事務1也對這個數據進行了修改. 則事務1必須回滾
事務1開始後事務2修改了數據. 並早於事務2提交. 若是後續事務1也對這個數據進行了修改. 則事務1必須回滾

總結

我的閱讀事務章節的一點感想:事務控制無非兩種: 樂觀: 經過沖突檢測決定事務是否回滾. 悲觀: 經過加鎖阻止併發發生.從另外一個維度看, 不一樣的隔離級別決定了兩個事務對於數據庫的讀寫操做是否阻塞,隔離級別越低,阻塞的場景越少任何隔離級別一個事務的讀操做不會阻塞讀讀阻塞寫, 寫阻塞讀 (Serializable級別 -- two phase locking)寫阻塞讀, 但讀不阻塞寫寫阻塞寫 ()

相關文章
相關標籤/搜索