myrocks之事務處理
title: MySQL · myrocks · myrocks之事務處理
author: 張遠
前言
mysql目前支援的事務引擎有innodb,tokudb. rocksdb加入mysql陣營後,mysql支援的事務引擎增長至3個。
myrocks目前支援的事務隔離級別有read-committed和repeatable-read. 同innodb一樣,myrocks也支援MVCC機制。
可以說,myrocks提供了很好的事務支援,能夠滿足的一般業務的事務需求。
sequence number
談到rocksdb事務,就必須提及rocksdb中的sequence number機制。rocksdb中的每一條記錄都有一個sequence number, 這個sequence number儲存在記錄的key中。
InternalKey: | User key (string) | sequence number (7 bytes) | value type (1 byte) |
對於同樣的User key記錄,在rocksdb中可能存在多條,但他們的sequence number不同。
sequence number是實現事務處理的關鍵,同時也是MVCC的基礎。
snapshot
snapshot是rocksdb的快照資訊,snapshot實際就是對應一個sequence number.
簡單的講,假設snapshot的sequence number為Sa, 那麼對於此snapshot來說,只能看到sequence number<=sa的記錄,sequence number>sa的記錄是不可見的。
- snapshot 結構
snapshot 主要包含sequence number和snapshot建立時間,sequence number 取自當前的sequence number.
class SnapshotImpl : public Snapshot {
SequenceNumber number_; // sequenct number
int64_t unix_time_; // snapshow建立時間
......
};
- snapshot 管理
snapshot由全域性雙向連結串列管理,根據sequence number排序。snapshot的建立和刪除都需要維護雙向連結串列。 - snapshot與compact
rocksdb的compact操作與snapshot有緊密聯絡。以我們熟悉的innodb為例,rocksdb的compact類似於innodb的purge操作, 而snapshot類似於InnoDB的read view. innodb做purge操作時會根據已有的read view來判斷哪些undo log可以purge,而rocksdb的compact操作會根據已有snapshot資訊即全域性雙向連結串列來判斷哪些記錄在compace時可以清理。
判斷的大體原則是,從全域性雙向連結串列取出最小的snapshot sequence number Sn. 如果已刪除的老記錄sequence number <=Sn, 那麼這些老記錄在compact時可以清理掉。
MVCC
有了snapshot,MVCC實現起來就很順利了。記錄的sequence number天然的提供了記錄的多版本資訊。
每次查詢使用者記錄時,並不需要加鎖。而是根據當前的sequence number Sn建立一個snapshot, 查詢過程中只取小於或等於Sn的最大sequence number的記錄。查詢結束時釋放snapshot.
關鍵程式碼段
DBIter::FindNextUserEntryInternal
if (ikey.sequence <= sequence_) {
if (skipping &&
user_comparator_->Compare(ikey.user_key, saved_key_.GetKey()) <= 0) {
num_skipped++; // skip this entry
PERF_COUNTER_ADD(internal_key_skipped_count, 1);
} else {
switch (ikey.type) {
case kTypeDeletion:
case kTypeSingleDeletion:
// Arrange to skip all upcoming entries for this key since
// they are hidden by this deletion.
saved_key_.SetKey(
ikey.user_key,
!iter_->IsKeyPinned() || !pin_thru_lifetime_ /* copy */);
skipping = true;
num_skipped = 0;
PERF_COUNTER_ADD(internal_delete_skipped_count, 1);
break;
case kTypeValue:
valid_ = true;
saved_key_.SetKey(
ikey.user_key,
!iter_->IsKeyPinned() || !pin_thru_lifetime_ /* copy */);
return;
case kTypeMerge:
......
隔離級別
隔離級別也是通過snapshot來實現的。在innodb中,隔離級別為read-committed時,事務中每的個stmt都會建立一個read view, 隔離級別為repeatable-read時,只在事務開啟時建立一次read view. rocksdb同innodb類似,隔離級別為read-committed時,事務中每的個stmt都會建立一個snapshot, 隔離級別為repeatable-read時,只在事務開啟時第一個stmt建立一次snapshot.
關鍵程式碼片段
rocksdb_commit:
if (my_core::thd_tx_isolation(thd) <= ISO_READ_COMMITTED)
{
// For READ_COMMITTED, we release any existing snapshot so that we will
// see any changes that occurred since the last statement.
tx->release_snapshot();
}
- 隔離級別實現差異
在read committed隔離級別下,如果一個大事務要更新1000w行,當它更新了前900w行時,
同時另一個事務已經更新了後100w行,那麼myrocks會重新獲取快照,再次嘗試更新,這樣
更新的是新提交的資料,也符合read committed邏輯。具體的討論可以參考最近的issue#340.
而之前的處理方式是直接報死鎖錯誤。
rocksdb::Status ha_rocksdb::get_for_update(
Rdb_transaction* tx,
rocksdb::ColumnFamilyHandle* column_family,
const rocksdb::Slice& key,
std::string* value) const
{
rocksdb::Status s= tx->get_for_update(column_family, key, value);
// If we have a lock conflict and we are running in READ COMMITTTED mode
// release and reacquire the snapshot and then retry the get_for_update().
if (s.IsBusy() && my_core::thd_tx_isolation(ha_thd()) == ISO_READ_COMMITTED)
{
tx->release_snapshot();
tx->acquire_snapshot(false);
s= tx->get_for_update(column_family, key, value);
}
return s;
}
innodb不會出現上述情況,當第一個大事更新是會持有b樹的index lock, 第二個事務會一直等待index lock直至第一個事務提交完成。
鎖
myrocks目前只支援一種鎖型別:排他鎖(X鎖),並且所有的鎖資訊都儲存在記憶體中。
- 鎖結構
每個鎖實際上儲存的哪條記錄被哪個事務鎖住。
struct LockInfo {
TransactionID txn_id;
// Transaction locks are not valid after this time in us
uint64_t expiration_time;
......
}
每個鎖實際是key和LockInfo的對映. 鎖資訊都儲存在map中
struct LockMapStripe {
std::unordered_map<std::string, LockInfo> keys;
......
}
為了減少全域性鎖資訊訪問的衝突, rocksdb將鎖資訊進行按key hash分割槽,
struct LockMap {
std::vector<LockMapStripe*> lock_map_stripes_;
}
同時每個column family 儲存一個這樣的LockMap.
using LockMaps = std::unordered_map<uint32_t, std::shared_ptr<LockMap>>;
LockMaps lock_maps_;
鎖相關引數:
max_num_locks:事務鎖個數限制
expiration:事務過期時間
通過設定以上兩個引數,來控制事務鎖佔用過多的記憶體。
- 死鎖檢測
rocksdb內部實現了簡單的死鎖檢測機制,每次加鎖發生等待時都會向下面的map中插入一條等待資訊,表示一個事務id等待另一個事務id.
同時會檢查wait_txn_map_是否存在等待環路,存在環路則發生死鎖。
std::unordered_map<TransactionID, TransactionID> wait_txn_map_;
死鎖檢測關鍵程式碼片段
TransactionLockMgr::IncrementWaiters:
for (int i = 0; i < txn->GetDeadlockDetectDepth(); i++) {
if (next == id) {
DecrementWaitersImpl(txn, wait_id);
return true;
} else if (wait_txn_map_.count(next) == 0) {
return false;
} else {
next = wait_txn_map_[next];
}
}
死鎖檢測相關引數
deadlock_detect:是否開啟死鎖檢測
deadlock_detect_depth:死鎖檢查深度,預設50
- gap lock
innodb中是存在gap lock的,主要是為了實現repeatable read和唯一性檢查的。
而在rocksdb中,不支援gap lock(rocksdb insert是也會多對唯一鍵加鎖,以防止重複插入,
嚴格的來講也算是gap lock).
那麼在rocksdb一些需要gap lock的地方,目前是報錯和列印日誌來處理的。
相關引數
gap_lock_write_log: 只列印日誌,不返回錯誤
gap_lock_raise_error: 列印日誌並且返回錯誤
- 鎖示例
直接看例子
binlog XA & 2pc
myrocks最近也支援了binlog xa.
在開啟binlog的情況下,myrocks提交時,會經歷兩階段提交階段。
prepare階段,根據server層生成的xid(由MySQLXid+server_id+qurey_id組成),在rockdb內部執行2pc操作,生成Prepare(xid),EndPrepare()記錄。
commit階段,根據事務成還是失敗,生成Commit(xid)或Rollback(xid)記錄。
rocksdb 2pc參考這裡
總結
myrocks在事務處理方面還有些不完善的地方,比如鎖型別只有單一的X鎖,不支援gap lock,純記憶體鎖佔用記憶體等。 myrocks社群正在持續改進中,一起期待。
相關文章
- Redis之事務處理Redis
- react之事件處理React事件
- JavaScript之事件處理詳解JavaScript事件
- MyRocks事務鎖分析
- Vue學習筆記之事件處理Vue筆記事件
- Java GUI之事件監聽與處理JavaGUI事件
- Java-GUI程式設計之事件處理JavaGUI程式設計事件
- Mysql之事務MySql
- MyRocks之bloomfilterOOMFilter
- 分散式事務處理方案,微服事務處理方案分散式
- Spring之事務原始碼Spring原始碼
- 【MySQL】MyRocks 漫談MySql
- [原創]上司經常要求你處理份外之事,如何應對?
- Java GUI之事件監聽與處理的匿名類實現方法JavaGUI事件
- MySQL事務處理MySql
- 12事務處理
- mysqli 事務處理MySql
- Spring 框架系列之事務管理Spring框架
- MySQL學習之事務隔離MySql
- MySQL之事務和redo日誌MySql
- Spring註解之事務管理Spring
- MySQL面試必備三之事務MySql面試
- 微服務的故障處理微服務
- ITL與事務處理
- redis的事務處理Redis
- php事務處理方法PHP
- java事務的處理Java
- MYSQL--事務處理MySql
- SpringDataRedis事務處理SpringRedis
- mysql事務處理(轉)MySql
- redis原始碼分析之事務Transaction(下)Redis原始碼
- SpringBoot實戰之事務抽象Spring Boot抽象
- redis原始碼分析之事務Transaction(上)Redis原始碼
- 記·處理服務端返回data不統一處理服務端
- 分散式柔性事務之事務訊息詳解分散式
- Laravel 分散式事務處理Laravel分散式
- PHP 多程式處理任務PHP
- springboot事務處理Spring Boot