1. 程式人生 > 實用技巧 >《設計資料密集型應用》- Designing Data-Intensive Application - 第7章 事務 讀書筆記

《設計資料密集型應用》- Designing Data-Intensive Application - 第7章 事務 讀書筆記

在這裡插入圖片描述

事務

事務的棘手性

ACID

單物件和多物件操作

  • 單物件寫入

    • 對單節點上的單個物件(例如鍵值對)上提供原子性和隔離性

      • 原子性通過日誌做崩潰恢復
      • 隔離性通過加鎖實現
  • 多物件事務

    • 關係型資料庫更新帶有外來鍵的表
    • 文件型資料同時更新多份文件
    • 二級索引與資料的同時更新
  • 處理錯誤和中止

    • 對於已經提交的事務,如果在給客戶端返回結果的網路中斷,如果此時重試事務,則需要去重機制
    • 如果是由網路擁塞導致的重試,則需要限制重試次數
    • 非必要問題不用重試,如違反約束不需要重試
    • 兩階段提交

弱隔離級別

相比可序列化的強隔離性之外都算做弱隔離級別

讀已提交

  • 髒寫

    • 同時寫入後面的寫入覆蓋前一個
    • 解決方法:加行鎖
  • 髒讀

    • 讀到未提交的資料

    • 解決方法

      • 與解決髒寫的方法一樣,加行鎖,但如果寫事務資料時間過長,讀請求會有較大的時間延遲

      • 儲存資料的舊值,在事務未提交時讀到舊值,事務提交後讀到新值

        • 如果讀取事務的時間範圍內出現了寫事務的提交,則讀事務中相同的兩次或多次讀取事件所讀到的資料可能會不同

快照隔離和可重複讀

  • 支援資料庫:PostgreSQL oracle MySQL(innodb) SQL Server

  • 實現快照隔離

    • 對於PostgreSQL,按如下方法實現MVCC

      • 基於txid事務ID實現,每個事務開始時均有一個唯一的TXID,約40億次後溢位
      • 表中每一行資料都有created_by與deleted_by欄位,分別儲存寫入時的txid
      • 刪除時標記deleted_by,不實際刪除,在稍候的時間,當確定沒有事務再訪問已標記刪除的行時,垃圾回收器將該行移除,釋放空間
      • 對於UPDATE操作實際執行了兩步操作:DELETE INSERT
    • 拓展(非本書內容):MYSQL5.6 MVCC實現

      • 僅存在於讀已提交與可重複讀兩種隔離級別,特別地,對於可重複讀,使用了MVCC+行鎖實現,讀時不加鎖,寫時加鎖

      • 在MYSQL中如果事務更改了某行,則會針對該行生成UNDOLOG(記錄了該行之前的記錄內容),在發生回滾時,將UNDOLOG該行的記錄恢復

        • 行鎖 間隙鎖

          • MYSQL:基於事務ID,比如undolog中存了v7這條回滾資料,而現在最小的活躍事務ID是v8,那v7這條資料就不可能再被讀到,就可以刪除了。
  • 觀察一致性快照的可見性規則

    • 列出所有尚且未提交或尚未終止的事務列表,即使這些事務後來提交了,忽略這些寫入
    • 具有較晚事務ID的都將被忽略
  • 索引和快照隔離

    • PostgreSQL:同一物件的不同版本可以放入同一個頁面中,PostgreSQL的優化可以避免更新索引
    • CouchDB,Datomic和LMDB:為修改頁面建立一個索引副本,從父級到根進行級聯指向更新指向新版本
  • 可重複讀與命名混淆

防止丟失更新

  • 定義

    • 併發寫入衝突(丟失)問題(併發的資料庫讀值–更改資料–寫回資料操作)
  • 解決方法

    • 原子寫

      • 如UPDATE XX SET a=a+1

        • 實現方案

          • 在物件上加排它鎖
          • 強制原子操作在單一的執行緒上執行
    • 顯式鎖定

      • 在查詢範圍上加排它鎖

    • 自動檢測丟失更新

      • PostgreSQL的可重複讀,Oracle的可序列化和SQL Server的快照隔離級別,都會自動檢測到丟失更新,並中止惹麻煩的事務。但是,MySQL/InnoDB的可重複讀並不會檢測丟失更新
    • Compare and Set

    • 衝突解決和複製

      • 允許寫入多個衝突值,並在應用層進行合併
    • 寫入偏差與幻讀

      • 場景

        • 一個SELECT查詢找出符合條件的行,並檢查是否符合一些要求。
        • 按照第一個查詢的結果,應用程式碼決定是否繼續。
        • 如果應用決定繼續操作,就執行寫入(插入、更新或刪除),並提交事務。
        • 如兩個人同時搶購1件商品,二人都先查到還要一件庫存,在應用程式當中,二者均向資料庫發出減庫存的寫入請求,此時會出現庫存異常
      • 快照隔離解決了讀事務中出現的幻讀情況,但會出現寫入偏差

可序列化

背景:

  • 很難檢查程式碼在特定隔離級別下是否安全
  • 併發測試困難

真的序列執行

  • 單執行緒執行併發事務

  • 產生背景

    • RAM足夠便宜,可將事務需要的活躍的資料集放在RAM當中,而不是之前放在磁碟上,執行效率提高了
    • OLTP事務通常執行速度很快,只進行少量的讀寫操作
  • 例項:VoltDB/H-Store,Redis和Datomic

  • 在儲存過程中封裝事務

  • 儲存過程的優缺點

  • 分割槽

    • 對於可以把資料劃分為多個分割槽,並且每個事務只在一個分割槽上進行,即可實現事務隨分割槽數量增加而線性擴充套件。對於跨分割槽的事務,則不能通過增加機器提升效能

二階段鎖定

  • 相比較快照隔離,二階段鎖定基於讀不阻塞寫,寫阻塞讀的方案,解決了寫入偏差與丟失更新

  • 實現2PL

    • 分為共享鎖與排它鎖

      • 多個事務可同時持有共享鎖
      • 有一個寫入事件則升級共享鎖為排它鎖,該鎖阻塞其它讀取與寫入,直到事務完成
      • 效能相比較弱隔離級別差得多
  • 謂詞鎖

    • 為了防止範圍查詢出現幻讀的情況,需要一種範圍鎖(非指定物件,比如表中某一行的鎖),需要實現以下兩點

      • 排它阻塞讀–任一讀取事務必須等到持有排它鎖的事務結束後才可進行讀取操作
      • 任意一種鎖(排它與共享)阻塞排它鎖
  • 索引範圍鎖(間隙鎖)

    • 謂詞鎖由於效能不佳(鎖粒度過小,檢查鎖匹配非常耗時),多數資料庫實現了索引範圍鎖

      • 如房間預訂請求,查詢條件為某時間段與房間號,如果表分別在時間段和房間號建立了索引,則索引範圍鎖會將其中一個索引(時間段或房間號)上附加共享鎖,如果其它事務的寫入操作需要修改該索引,則它會等到上述共享鎖釋放

可序列化快照隔離

  • 在快照隔離的基礎上,SSI添加了一種演算法來檢測寫入之間的序列化衝突,並確定要中止哪些事務。(樂觀鎖)

    • 如果出現了大併發寫入事務爭搶同一寫入物件,會導致一大部分寫入事務需要終止,如果系統已經接近最大吞吐,則重試事務可能會導致效能變差
  • 快照隔離會出現寫入偏差,檢測方法有兩種

    • 檢測對舊MVCC物件版本的讀取

      • 在提交事務時檢測是否有任何被忽略的寫入現在已經被提交,如果有則終止事務並重試;如果其它提交的事務只包含查詢,由於其不會產生寫入偏差,則不中止事務

    • 檢測影響先前讀取的寫入

      • 藉助索引,如果事務中的UPDATE語句的WHERE條件列有索引,則事務提交時記錄下該修改值,在其它事務也基於該WHERE條件進行更新時,則通知該事務所讀取的資料已經不是最新的

  • 效能

    • 在SSI中,一個事務不需要阻塞等待另一個事務所持有的鎖,與兩階段鎖定相比,其查詢延遲可預測更好;與序列執行相比,其效能不侷限於單個CPU核的吞吐量