搜尋
首頁資料庫mysql教程RocksDB上鎖定機制的實例詳解

RocksDB上鎖定機制的實例詳解

Jul 03, 2017 am 09:31 AM
rocksdb機制

      RocksDB作為一個開源的儲存引擎支援事務的ACID特性,而要支援ACID中的I(Isolation),並發控制這塊是少不了的,本文主要討論RocksDB的鎖機制實現,細節會涉及到原始碼分析,希望透過本文讀者可以深入了解RocksDB並發控制原理。文章主要從以下4方面展開,首先會介紹RocksDB鎖的基本結構,然後我會介紹RocksDB行鎖資料結構設計下,鎖空間開銷,接著我會介紹幾種典型場景的上鎖流程,最後會介紹鎖機制中不可或缺的死鎖偵測機制。

1.行鎖定資料結構
    RocksDB鎖定粒度最小是行,對於KV儲存而言,鎖定物件就是key,每個key對應一個LockInfo結構。所有key透過hash表管理,找出鎖定時,直接透過hash表定位即可確定這個key是否已經被上鎖。但如果全域只有一個hash表,會導致這個存取這個hash表的衝突很多,影響並發效能。 RocksDB先按Columnfamily進行拆分,每個Columnfamily中的鎖定透過一個LockMap管理,而每個LockMap再拆分成若干個分片,每個分片透過LockMapStripe管理,而hash表(std::unordered_map)則存在於Stripe結構中,Stripe結構中還包含一個mutex和condition_variable,這個主要作用是,互斥訪問hash表,當出現鎖定衝突時,將線程掛起,解鎖後,喚醒掛起的線程。這種設計很簡單但也帶來一個顯而易見的問題,就是多個不相關的鎖公用一個condition_variable,導致鎖釋放時,不必要的喚醒一批線程,而這些線程重試後,發現仍然需要等待,造成了無效的上下文切換。比較我們先前討論的InnoDB鎖定機制,我們發現InnoDB是一個page裡面的記錄複用一把鎖,而且複用是有條件的,同一個事務對一個page的若干條記錄加鎖才能復用;而且鎖等待隊列是精確等待,精確到記錄級別,不會導致的無效的喚醒。雖然RocksDB鎖設計比較粗糙,但也做了一定的最佳化,例如在管理LockMaps時,透過在每個執行緒本地快取一份拷貝lock_maps_cache_,透過全域鍊錶將每個執行緒的cache鏈起來,當LockMaps變更時(刪除columnfamily),則全域將每個執行緒的copy清空,由於columnfamily改動很少,所以大部分存取LockMaps操作都是不需要加鎖的,提高了並發效率。
相關資料結構如下:

struct LockInfo {
bool exclusive; //排它锁或是共享锁
autovector<TransactionID> txn_ids; //事务列表,对于共享锁而言,同一个key可以对应多个事务

// Transaction locks are not valid after this time in us
uint64_t expiration_time;
}

struct LockMapStripe { 
// Mutex must be held before modifying keys map
std::shared_ptr<TransactionDBMutex> stripe_mutex;

// Condition Variable per stripe for waiting on a lock
std::shared_ptr<TransactionDBCondVar> stripe_cv;

// Locked keys mapped to the info about the transactions that locked them.
std::unordered_map<std::string, LockInfo> keys;
}

struct LockMap {
const size_t num_stripes_; //分片个数
std::atomic<int64_t> lock_cnt{0}; //锁数目
std::vector<LockMapStripe*> lock_map_stripes_; //锁分片
}

class TransactionLockMgr {
using LockMaps = std::unordered_map<uint32_t, std::shared_ptr<LockMap>>;
LockMaps lock_maps_;

// Thread-local cache of entries in lock_maps_. This is an optimization
// to avoid acquiring a mutex in order to look up a LockMap
std::unique_ptr<ThreadLocalPtr> lock_maps_cache_;
}

2.行锁空间代价
    由于锁信息是常驻内存,我们简单分析下RocksDB锁占用的内存。每个锁实际上是unordered_map中的一个元素,则锁占用的内存为key_length+8+8+1,假设key为bigint,占8个字节,则100w行记录,需要消耗大约22M内存。但是由于内存与key_length正相关,导致RocksDB的内存消耗不可控。我们可以简单算算RocksDB作为MySQL存储引擎时,key_length的范围。对于单列索引,最大值为2048个字节,具体可以参考max_supported_key_part_length实现;对于复合索引,索引最大长度为3072个字节,具体可以参考max_supported_key_length实现。假设最坏的情况,key_length=3072,则100w行记录,需要消耗3G内存,如果是锁1亿行记录,则需要消耗300G内存,这种情况下内存会有撑爆的风险。因此RocksDB提供参数配置max_row_locks,确保内存可控,默认RDB_MAX_ROW_LOCKS设置为1G,对于大部分key为bigint场景,极端情况下,也需要消耗22G内存。而在这方面,InnoDB则比较友好,hash表的key是(space_id, page_no),所以无论key有多大,key部分的内存消耗都是恒定的。前面我也提到了InnoDB在一个事务需要锁大量记录场景下是有优化的,多个记录可以公用一把锁,这样也间接可以减少内存。

3.上锁流程分析
    前面简单了解了RocksDB锁数据结构的设计以及锁对内存资源的消耗。这节主要介绍几种典型场景下,RocksDB是如何加锁的。与InnoDB一样,RocksDB也支持MVCC,读不上锁,为了方便,下面的讨论基于RocksDB作为MySQL的一个引擎来展开,主要包括三类,基于主键的更新,基于二级索引的更新,基于主键的范围更新等。在展开讨论之前,有一点需要说明的是,RocksDB与InnoDB不同,RocksDB的更新也是基于快照的,而InnoDB的更新基于当前读,这种差异也使得在实际应用中,相同隔离级别下,表现有所不一样。对于RocksDB而言,在RC隔离级别下,每个语句开始都会重新获取一次快照;在RR隔离级别下,整个事务中只在第一个语句开始时获取一次快照,所有语句共用这个快照,直到事务结束。

3.1.基于主键的更新
这里主要接口是TransactionBaseImpl::GetForUpdate
1).尝试对key加锁,如果锁被其它事务持有,则需要等待
2).创建snapshot
3).调用ValidateSnapshot,Get key,通过比较Sequence判断key是否被更新过
4).由于是加锁后,再获取snapshot,所以检查一定成功。
5).执行更新操作
这里有一个延迟获取快照的机制,实际上在语句开始时,需要调用acquire_snapshot获取快照,但为了避免冲突导致的重试,在对key加锁后,再获取snapshot,这就保证了在基于主键更新的场景下,不会存在ValidateSnapshot失败的场景。

堆栈如下:

1-myrocks::ha_rocksdb::get_row_by_rowid
2-myrocks::ha_rocksdb::get_for_update
3-myrocks::Rdb_transaction_impl::get_for_update
4-rocksdb::TransactionBaseImpl::GetForUpdate
{
//加锁
5-rocksdb::TransactionImpl::TryLock
  6-rocksdb::TransactionDBImpl::TryLock
    7-rocksdb::TransactionLockMgr::TryLock 

 //延迟获取快照,与acquire_snapshot配合使用
 6-SetSnapshotIfNeeded()

 //检查key对应快照是否过期
 6-ValidateSnapshot
  7-rocksdb::TransactionUtil::CheckKeyForConflict
    8-rocksdb::TransactionUtil::CheckKey
      9-rocksdb::DBImpl::GetLatestSequenceForKey //第一次读取

//读取key
5-rocksdb::TransactionBaseImpl::Get
  6-rocksdb::WriteBatchWithIndex::GetFromBatchAndDB
    7-rocksdb::DB::Get
      8-rocksdb::DBImpl::Get
        9-rocksdb::DBImpl::GetImpl //第二次读取
}

3.2.基於主鍵的範圍更新
1).建立Snapshot,基於迭代器掃描主鍵
2).透過get_row_by_rowid,嘗試對key加鎖
3).呼叫ValidateSnapshot,Get key,透過比較Sequence判斷key是否被更新過
4).如果key被其它事務更新過( key對應的SequenceNumber比Snapshot要新),觸發重試
5).重試情況下,會釋放舊的快照並釋放鎖,透過tx->acquire_snapshot(false),延遲取得快照(加鎖後,再拿snapshot)
5).再呼叫get_for_update,由於此時key已經被加鎖,重試一定可以成功。
6).執行更新操作
7).跳到1,繼續執行,直到主鍵不符合條件時,則結束。

3.3.基於二級索引的更新
這種場景與3.2類似,只不過多一步從二級索引定位主鍵程序。
1).建立Snapshot,基於迭代器掃描二級索引
2).根據二級索引反向找到主鍵,實際上也是呼叫get_row_by_rowid,這個過程就會嘗試對key加鎖定
3).繼續根據二級索引遍歷下一個主鍵,嘗試加鎖
4).當傳回的二級索引不符合條件時,則結束

3.4 與InnoDB加鎖的差異
      前面我們說到了RocksDB與InnoDB的一點差異是,對於更新場景,RocksDB仍然是快照讀取,而InnoDB是當前讀取,導致行為上的差異。例如在RC隔離等級下的範圍更新場景,例如一個事務要更新1000筆記錄,由於是邊掃描邊加鎖,可能在掃描到第999筆記錄時,發現這個key的Sequence大於掃描的快照(這個key被其它事務更新了),這個時候會觸發重新獲取快照,然後基於這個快照拿到最新的key值。 InnoDB則沒有這個問題,透過目前讀,掃描過程中,如果第999筆記錄被更新了,InnoDB可以直接看到最新的記錄。在這種情況下,RocksDB和InnoDB看到的結果是一樣的。在另一種情況下,假設也是掃描的範圍中,新插入了key,這key的Sequence毫無疑問會比掃描的Snapshot要大,因此在Scan過程中這個key會被過濾掉,也就不存在所謂的衝突偵測了,這個key不會被找到。更新過程中,插入了id為1和900的兩筆記錄,最後第900筆記錄由於不可見,所以更新不到。而對於InnoDB而言,由於是當前讀,新插入的id為900的記錄可以被看到並更新,所以這裡是與InnoDB有區別的地方。
      除了更新基於快照這個差異以外,RocksDB在加鎖上也更簡潔,所有加鎖只涉及唯一索引,具體而言,在更新過程中,只對主鍵加鎖;更新當列涉及唯一約束時,需要加鎖;而普通二級索引,則不用加鎖,這個目的是為了避免唯一約束衝突。這裡面,如果更新了唯一約束(主鍵,或唯一索引),都需要加鎖。而InnoDB則是需要對每個索引加鎖,例如基於二級索引定位更新,則二級索引也需要加鎖。之所以有這個差別是,是因為InnoDB為了實現RR隔離等級。這裡稍微講下隔離級別,實際上MySQL中定義的RR隔離級別與SQL標準定義的隔離級別有點不一樣。 SQL標準定義RR隔離等級解決不可重複讀取的問題,Serializable隔離等級解決幻讀問題。不可重複讀取著重講同一記錄值不會修改;而幻讀則著重講兩次讀取回傳的記錄條數是固定的,不會增加或減少記錄數目。 MySQL定義RR隔離等級同時解決了不可重複讀取和幻覺問題,而InnoDB中RR隔離等級的實作就是依賴GAP鎖定。而RocksDB不支援GAP鎖(僅支援唯一約束檢查,對不存在的key加鎖),因為基於快照的機制可以有效過濾掉新插入的記錄,而InnoDB由於當前讀,導致需要通過間隙鎖禁止其它插入,所以二級索引也需要加鎖,主要是為了鎖間隙,否則兩次目前讀取的結果可能不一樣。當然,對於RC割裂級別,InnoDB普通二級索引也是沒有必要加鎖的。

4.死锁检测算法
      死锁检测采用DFS((Depth First Search,深度优先算法),基本思路根据加入等待关系,继续查找被等待者的等待关系,如果发现成环,则认为发生了死锁,当然在大并发系统下,锁等待关系非常复杂,为了将死锁检测带来的资源消耗控制在一定范围,可以通过设置deadlock_detect_depth来控制死锁检测搜索的深度,或者在特定业务场景下,认为一定不会发生死锁,则关闭死锁检测,这样在一定程度上有利于系统并发的提升。需要说明的是,如果关闭死锁,最好配套将锁等待超时时间设置较小,避免系统真发生死锁时,事务长时间hang住。死锁检测基本流程如下:
1.定位到具体某个分片,获取mutex
2.调用AcquireLocked尝试加锁
3.若上锁失败,则触发进行死锁检测
4.调用IncrementWaiters增加一个等待者
5.如果等待者不在被等待者map里面,则肯定不会存在死锁,返回
6.对于被等待者,沿着wait_txn_map_向下检查等待关系,看看是否成环
7.若发现成环,则将调用DecrementWaitersImpl将新加入的等待关系解除,并报死锁错误。

相关的数据结构:

class TransactionLockMgr {
// Must be held when modifying wait_txn_map_ and rev_wait_txn_map_.
std::mutex wait_txn_map_mutex_;

// Maps from waitee -> number of waiters.
HashMap<TransactionID, int> rev_wait_txn_map_;

// Maps from waiter -> waitee.
HashMap<TransactionID, autovector<TransactionID>> wait_txn_map_;

DecrementWaiters //

IncrementWaiters //
}

struct TransactionOptions {
bool deadlock_detect = false; //是否检测死锁
int64_t deadlock_detect_depth = 50; //死锁检测的深度
int64_t lock_timeout = -1; //等待锁时间,线上一般设置为5s
int64_t expiration = -1; //持有锁时间,
}




以上是RocksDB上鎖定機制的實例詳解的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn
解釋酸的特性(原子,一致性,隔離,耐用性)。解釋酸的特性(原子,一致性,隔離,耐用性)。Apr 16, 2025 am 12:20 AM

ACID屬性包括原子性、一致性、隔離性和持久性,是數據庫設計的基石。 1.原子性確保事務要么完全成功,要么完全失敗。 2.一致性保證數據庫在事務前後保持一致狀態。 3.隔離性確保事務之間互不干擾。 4.持久性確保事務提交後數據永久保存。

MySQL:數據庫管理系統與編程語言MySQL:數據庫管理系統與編程語言Apr 16, 2025 am 12:19 AM

MySQL既是數據庫管理系統(DBMS),也與編程語言緊密相關。 1)作為DBMS,MySQL用於存儲、組織和檢索數據,優化索引可提高查詢性能。 2)通過SQL與編程語言結合,嵌入在如Python中,使用ORM工具如SQLAlchemy可簡化操作。 3)性能優化包括索引、查詢、緩存、分庫分錶和事務管理。

mySQL:使用SQL命令管理數據mySQL:使用SQL命令管理數據Apr 16, 2025 am 12:19 AM

MySQL使用SQL命令管理數據。 1.基本命令包括SELECT、INSERT、UPDATE和DELETE。 2.高級用法涉及JOIN、子查詢和聚合函數。 3.常見錯誤有語法、邏輯和性能問題。 4.優化技巧包括使用索引、避免SELECT*和使用LIMIT。

MySQL的目的:有效存儲和管理數據MySQL的目的:有效存儲和管理數據Apr 16, 2025 am 12:16 AM

MySQL是一種高效的關係型數據庫管理系統,適用於存儲和管理數據。其優勢包括高性能查詢、靈活的事務處理和豐富的數據類型。實際應用中,MySQL常用於電商平台、社交網絡和內容管理系統,但需注意性能優化、數據安全和擴展性。

SQL和MySQL:了解關係SQL和MySQL:了解關係Apr 16, 2025 am 12:14 AM

SQL和MySQL的關係是標準語言與具體實現的關係。 1.SQL是用於管理和操作關係數據庫的標準語言,允許進行數據的增、刪、改、查。 2.MySQL是一個具體的數據庫管理系統,使用SQL作為其操作語言,並提供高效的數據存儲和管理。

說明InnoDB重做日誌和撤消日誌的作用。說明InnoDB重做日誌和撤消日誌的作用。Apr 15, 2025 am 12:16 AM

InnoDB使用redologs和undologs確保數據一致性和可靠性。 1.redologs記錄數據頁修改,確保崩潰恢復和事務持久性。 2.undologs記錄數據原始值,支持事務回滾和MVCC。

在解釋輸出(類型,鍵,行,額外)中要查找的關鍵指標是什麼?在解釋輸出(類型,鍵,行,額外)中要查找的關鍵指標是什麼?Apr 15, 2025 am 12:15 AM

EXPLAIN命令的關鍵指標包括type、key、rows和Extra。 1)type反映查詢的訪問類型,值越高效率越高,如const優於ALL。 2)key顯示使用的索引,NULL表示無索引。 3)rows預估掃描行數,影響查詢性能。 4)Extra提供額外信息,如Usingfilesort提示需要優化。

在解釋中使用臨時狀態以及如何避免它是什麼?在解釋中使用臨時狀態以及如何避免它是什麼?Apr 15, 2025 am 12:14 AM

Usingtemporary在MySQL查詢中表示需要創建臨時表,常見於使用DISTINCT、GROUPBY或非索引列的ORDERBY。可以通過優化索引和重寫查詢避免其出現,提升查詢性能。具體來說,Usingtemporary出現在EXPLAIN輸出中時,意味著MySQL需要創建臨時表來處理查詢。這通常發生在以下情況:1)使用DISTINCT或GROUPBY時進行去重或分組;2)ORDERBY包含非索引列時進行排序;3)使用複雜的子查詢或聯接操作。優化方法包括:1)為ORDERBY和GROUPB

See all articles

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

AI Hentai Generator

AI Hentai Generator

免費產生 AI 無盡。

熱門文章

R.E.P.O.能量晶體解釋及其做什麼(黃色晶體)
4 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳圖形設置
4 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您聽不到任何人,如何修復音頻
4 週前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.聊天命令以及如何使用它們
4 週前By尊渡假赌尊渡假赌尊渡假赌

熱工具

DVWA

DVWA

Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

MantisBT

MantisBT

Mantis是一個易於部署的基於Web的缺陷追蹤工具,用於幫助產品缺陷追蹤。它需要PHP、MySQL和一個Web伺服器。請查看我們的演示和託管服務。

SublimeText3 英文版

SublimeText3 英文版

推薦:為Win版本,支援程式碼提示!

mPDF

mPDF

mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),