MongoDB的事務實現

在前面我們介紹了MongoDB單文檔原子操作, 這是單文檔下的事務, 它能夠保證對於單文檔的操作的原子性。 在MongoDB3.6裏面, 支持了session, 這與之前的OperationContext很類似, 所不同的實, session可以多個CRUD操作使用共同的session。在MongoDB4.0裏面, 終於有了針對副本集的多文檔事務實現, 使得我們可以向使用其他的數據庫事務一樣進行事務操作。
WiredTiger對事務的支持, 是MongoDB支持事務的基礎, 這裏重點介紹一下WT下事務的實現過程。

transaction 相關的數據結構

__wt_txn_global

在WT_CONNECTION中, 有一個全局的事務相關的上下文結構體__wt_txn_global, 用來記錄事務實現的全局信息。 在open connection的時候調用__wt_txn_global_init來構建和初始化, 在關閉connection的時候調用__wt_txn_global_destroy,來清理資源。 主要包括:

  • 全局唯一的自增事務ID;
  • 最舊的未提交事務ID等;
  • checkpoint 相關信息;
  • named snapshot隊列信息;
  • 各個session對應state的數組;

爲了支持事務, 在__wt_txn_global裏面增加了如下的field:

  • commit_timestamp
  • last_ckpt_timestamp
  • meta_ckpt_timestamp
  • oldest_timestamp
    oldest_timestamp記錄了一個時間點, 在這個時間點之前的版本不需要保存, 由於每個讀操作都與一個snapshot關聯, 只要有讀操作與之相應, 該版本就需要保留, 爲了不保留太多的版本, 給WT的內存造成壓力, oldest_timestamp就是告訴引擎層, 該時間點之前的版本不需要保留。
  • pinned_timestamp
  • recovery_timestamp
  • stable_timestamp
    stable_timestamp記錄了當前已經同步到大多數節點的操作的時間點,隨着新的操作的進行, 該時間點逐漸變大, 當我們進行回滾操作, 不再像之前那樣, 對要回顧的操作進行相反的逆操作, 而是直接回到stable_timestamp。
  • TAILQ_HEAD(__wt_txn_cts_qh, __wt_txn) commit_timestamph;
  • TAILQ_HEAD(__wt_txn_rts_qh, __wt_txn) read_timestamph;
類型 名稱 描述
uint64_t current 全局唯一的遞增transactionID
uint64_t last_running 最舊的運行中的transaction id
uint64_t oldest_id 系統中最小的transaction id
wt_timestamp_t commit_timestamp 記錄事務可以讀取的時間點, 再次之前是讀不到的
wt_timestamp_t last_ckpt_timestamp 上一次checkpoint 的時間
wt_timestamp_t meta_ckpt_timestamp metadata裏面記錄的checkpoint time
wt_timestamp_t oldest_timestamp 記錄最舊的事務時間點
wt_timestamp_t pinned_timestamp 當前最小的需要固定在內存的transactionID, 就是有read綁定到它上面
wt_timestamp_t recovery_timestamp 記錄recovery的時候, 恢復到的checkpoint的timestamp
wt_timestamp_t stable_timestamp 一個固定時間點, 由客戶端設定, 主要用於rollback_to_stable
bool has_commit_timestamp 是否有commit_timestamp
bool has_oldest_timestamp 是否有oldest_timestamp
bool has_pinned_timestamp 是否有pinned_timestamp設定
bool has_stable_timestamp 是否有set_timestamp設定stable
bool oldest_is_pinned pinned_timestamp == oldest_timestamp?
bool stable_is_pinned pinned_timestamp == stable_timestamp?
WT_SPINLOCK id_lock 用於更新current, 獲取一個transactionID的時候用
WT_RWLOCK visibility_rwlock 可見性保護鎖, 防止日誌, checkpoint,事務完成之前可見
WT_RWLOCK commit_timestamp_rwlock commit_timestamp讀寫鎖, 修改commit timestamp隊列的時候用
TAILQ_HEAD(__wt_txn_cts_qh, __wt_txn) commit_timestamph commit_timestamp隊列
uint32_t commit_timestampq_len commit_timestamp的長度
WT_RWLOCK read_timestamp_rwlock read_timestamp讀寫鎖
TAILQ_HEAD(__wt_txn_rts_qh, __wt_txn) read_timestamph read_timestamp隊列
uint32_t read_timestampq_len read_timestamp隊列的長度
bool checkpoint_running checkpoint是否正在運行中
uint32_t checkpoint_id checkpoint id
WT_TXN_STATE checkpoint_state checkpoint的狀態
uint64_t metadata_pinned 綁定的metadata中最小的transactionID
WT_RWLOCK nsnap_rwlock 命名snapshot的讀寫鎖
uint64_t nsnap_oldest_id 最舊的命名snapshot ID
TAILQ_HEAD(__wt_nsnap_qh, __wt_named_snapshot) nsnaph 命名snapshot隊列
WT_TXN_STATE *states 事務列表的狀態

__wt_txn

每一個session, 都有一個相關的上下文狀態的結構__wt_txn, 它用來產生事務結構體, 並且記錄了session的當前正在執行的transaction的相關信息。主要包括:

  • 事務的ID;
  • snapshot 數組;
  • WT_TXN_OP 修改數組;
  • log record日誌記錄;
  • checkpoint的狀態信息;
  • transaction的類型狀態信息;
  • commit_timestamp
  • durable_timestamp
  • first_commit_timestamp
  • prepare_timestamp
  • read_timestamp
  • TAILQ_HEAD(__wt_txn_cts_qh, __wt_txn) commit_timestamph;
  • TAILQ_HEAD(__wt_txn_rts_qh, __wt_txn) read_timestamph;
類型 名稱 描述
uint64_t id transaction ID
WT_TXN_ISOLATION isolation 事務的隔離級別: read uncommitted, read committed, snapshot
uint32_t forced_iso 強制使用的隔離級別
uint64_t snap_min, snap_max 該事務對應的snapshot的最大最小id
uint64_t * snapshot 該事務對應的snapshot數組
uint32_t snapshot_count 該事務的對應的snapshot數組的大小
uint32_t txn_logsync 事務日誌的sync設定
wt_timestamp_t commit_timestamp 該transaction的commit timestamp
wt_timestamp_t first_commit_timestamp commit_timstamp在一個transaction可以被設定多次, 這裏記錄第一個
wt_timestamp_t prepare_timestamp 該事務的prepare timestamp
TAILQ_ENTRY(__wt_txn) commit_timestampq commit timestamp隊列
TAILQ_ENTRY(__wt_txn) read_timestampq readtimestamp隊列
bool clear_commit_q commit timestamp隊列是否需要被清理
bool clear_read_q read timestamp隊列是否需要被清理
WT_TXN_OP *mod transaction裏面的修改操作數組
size_t mod_alloc transaction裏面的修改操作數組大小
u_int mod_count transaction裏面的修改操作數組已分配的個數
WT_ITEM* logrec transaction對應的log內存buffer, 記錄了transaction的操作
WT_TXN_NOTIFY * notify transaction的notify函數
WT_LSN ckpt_lsn checkpoint的logical sequence number
uint32_t ckpt_nsnapshot checkpoint對應的snapshot count
WT_ITEM* ckpt_snapshot 記錄checkpoint的snapshot數組的ID
bool full_ckpt 是不是fullcheckpoint
const char * rollback_reason transaction rollback的原因
uint32_t flags transaction的設定

__wt_txn_state

Per-session的transaction狀態信息。

事務的ACID

  • 原子性(Atomicity):事務內的所有操作要麼全部完成,要麼全部回滾;
  • 一致性(Consistency):事務執行前後的狀態都是合法的數據狀態,不會違反任何的數據完整性;
  • 隔離性(Isolation):主要是事務之間的相互的影響,根據隔離有不同的影響效果。
  • 持久性(Durability):事務一旦提交,就會體現在數據庫上,不能回滾

這些基本的特性是如何保證的哪?對比mysql, 參考下面的表格:

事務屬性 MySQL MongoDB
Atomicity undo log機制來實現 journal 機制來實現
Consistency redo log機制來實現 journal機制來實現
Isolation 隔離級別+ 讀寫鎖 + MVCC 來實現 隔離級別 + 讀寫鎖 + snapshot+ MVCC來實現
Durability 依賴WAL 刷盤頻率的設定 依賴WAL 刷盤頻率的設定

事務的隔離級別

log刷盤設

4.0事務的限制

  • 事務執行時間的限制
    默認情況下, 一個事務從開始到事務提交的時間間隔要小於60秒, 如果大於60秒, 該事務就認爲失敗, 進行回滾。這個時間限制可以通過transactionLifetimeLimitSeconds來進行修改。

    db.adminCommand( { setParameter: 1, transactionLifetimeLimitSeconds: 40 } )
    
  • oplog size的限制
    在MongoDB下面, 默認一個JSON文件的最大爲16M, oplog本身也是一個JSON,也遵守這個規則。一個事務, 包括它裏面所有的寫操作都包括在一個oplog裏面。

  • 事務獲取鎖的等待時間限制
    默認情況下, 使用事務需要獲取鎖的等待時間爲5毫秒, 超過這個時間事務就失敗。我們可以通過修改參數maxTransactionLockRequestTimeoutMillis開改變這個設定。

    db.adminCommand( { setParameter: 1, maxTransactionLockRequestTimeoutMillis: 20 } )
    
  • 事務寫衝突的限制

時間點 事務1 事務2
t1 x=1
t2 begin_transaction
t3 x=2
t4 y=1
t5 y=2
t6 commit

如上圖所示, 有事務1和事務2, you個時間t1 ~ t5.在時間點t1, 事務2將X設定爲1, 在t3, 另外一個事務將其該爲2, 由於事務2沒有在一個局部的事務裏面, 它的操作可以認爲在事務之外, 這種情況下, 事務1會失敗;
另外一種情況, 在事務1內部時間點t4,y的值被改爲1, 在事務外部t5, y的值被改爲2, 這種情況下, y=2的設定要等待事務1提交之後, 纔會被處理;

  • 事務讀取舊的數據
時間點 事務1 事務2 事務3
t1 x=1 y=1
t2 begin_transaction
t3 begin_transaction y=2
t4 x=2 y=3
t5 commit x=3
t6 commit

如上圖所示, 在t1是x=1,y=1,事務1 在時間點t4將x變成2, 事務2在時間點t3將y值設定爲2,在外部的在t4將y設定爲3, 但是在整個的事務2(t2 ~ t5), 我們讀取的事務2的y的值始終是2.

事務的執行過程

整體上來看, MongoDB的事務的使用和其他的數據庫非常類似, 如下是從官網複製的一個transaction API的例子, 我們順着程序的執行順序來分析下事務的實現。

// Start a session.
session = db.getMongo().startSession( { readPreference: { mode: "primary" } } );

employeesCollection = session.getDatabase("hr").employees;
eventsCollection = session.getDatabase("reporting").events;

// Start a transaction
session.startTransaction( { readConcern: { level: "snapshot" }, writeConcern: { w: "majority" } } );

// Operations inside the transaction
try {
   employeesCollection.updateOne( { employee: 3 }, { $set: { status: "Inactive" } } );
   eventsCollection.insertOne( { employee: 3, status: { new: "Inactive", old: "Active" } } );
} catch (error) {
   // Abort transaction on error
   session.abortTransaction();
   throw error;
}

// Commit the transaction using write concern set at transaction start
session.commitTransaction();
session.endSession();

startSession

startSession 是3.6版本新開放的API,它可以產生一個內部的session, 它聯合client的write concern 和read concern, 可以實現因果一致性

startTransaction

對於某個session, 在任一時刻只能有一個運行的transaction。
通過__session_begin_transaction --> __wt_txn_begin調用__wt_txn_get_snapshot, 來產生一個新的事務。其實, 產生一個snapshot的過程很簡單, 就是在__wt_txn_global->stats, 將某個session->id指定爲當前的__wt_txn_state, 更新裏面的字段, 當我們需要某個transaction的時候, 通過TXN的ID可以找到相關的snapshot的ID。

transaction中間的CRUD

在startTransaction之後, commitTranscation/abortTransaction之前的操作, 都是在同一個事務裏面, 遵守事務的ACID特性。
相應的操作除了直接應用到內存的數據和索引內存頁之外, 還記錄到journal對應的txn->mod裏面, 這樣, 如果事務失敗, 通過__wt_txn_unmodify, 回滾log裏面記錄的事務內的操作, 可以實現事務的回滾。
在內存裏面, 內存頁裏面有page對應的insert_array和update_array;在一個事務裏面, 每一個更新操作, 都有一個update操作對應__wt_txn_modify, 將該操作與實務id相關聯。

commitTransaction

__wt_txn_commit
當事務需要提交的時候, __wt_txn_commit需要根據配置設定事務的日誌提交方式, 然後通過__wt_txn_log_commit寫入日誌。 如果寫入成功, 就釋放掉該事務對應的修改操作txn->mod, 並且將事務清除; 如果失敗, 就調用__wt_txn_rollback rollback處理。

abortTransaction

一旦需要rollback, __wt_txn_rollback會將txn->mod的所有需改, 狀態變成WT_TXN_ABORTED, 然後清理事務對應的資源。

參考文檔

https://source.wiredtiger.com/3.0.0/transactions.html
https://www.jianshu.com/p/9b83ea78b380
https://www.cnblogs.com/cjsblog/p/8365921.html
https://baijiahao.baidu.com/s?id=1625607423998953705&wfr=spider&for=pc

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章