vs做网站图片明明在文件夹里却找不到,wordpress数据库密码解密,旅游型网站的建设背景,宁波网站开发公司怎么样背景
在数据库开发过程中#xff0c;“写盘”是一项核心操作#xff0c;即将内存中暂存的数据安全地转储到磁盘上。在诸如MySQL这样的传统数据库管理系统中#xff0c;写盘主要有以下几步#xff1a;首先将数据写入缓存池#xff1b;其次#xff0c;为了确保数据的完整性…背景
在数据库开发过程中“写盘”是一项核心操作即将内存中暂存的数据安全地转储到磁盘上。在诸如MySQL这样的传统数据库管理系统中写盘主要有以下几步首先将数据写入缓存池其次为了确保数据的完整性系统会将数据写到二进制日志文件中这一步骤对于在系统崩溃时恢复数据至关重要最后将数据写入磁盘。为了提升这一过程的性能可以使用SSD盘、延迟写或增加缓存池大小等多种策略。
而在OceanBase 数据库中写盘与传统数据库大不相同。由于OceanBase 存储引擎是基于LSM-Tree架构实现的数据被分为了静态基线数据放在 SSTable 中存储于磁盘和动态增量数据放在 MemTable 中存储于内存两部分。当内存的增量数据达到一定规模的时候会触发冻结转储操作保证将要被转储的MemTable不再进行新的数据写入同时生成新的活动MemTable冻结Freeze以及将被冻结MemTable的数据存储到磁盘上以释放内存空间转储Mini Merge。此时一些小伙伴会存在疑问到底如何读取那些所谓的row结构才能将MemTable的数据存储到磁盘上
本文从MemTable的结构、读行过程讲起逐步带大家解开疑问并对OceanBase 的MemTable产生新的认识。
MemTable的结构
OceanBase中的MemTable是由BTree和Hash Table两个数据结构结合而成二者各司其职BTree发挥其范围查询的能力Hash Table发挥其点查的能力。什么意思呢我们来看下面这张图。图中是BTree和Hash Table的一个结合结构BTree的每个叶节点和Hash Table的节点都指向着某一个row节点而row节点指向着一个链表实现上是双向链表。我们只需要知道每个rowkey在MemTable中最终是对应着一个row节点即一行数据这行数据可能经过了insert、update、delete、insert的一系列操作在OceanBase中每个这样的操作都会通过一个MvccNode存储起来并按照操作的先后顺序从尾到头组成一个MvccNode list链表尾部是最新的操作。 好到这里我们已经掌握了OceanBase中MemTable的核心科技了也没那么复杂是不是
接下来我们一起经历一遍冻结后的MemTable在将要存储到磁盘时逐行读取数据的过程尽可能简单地加深我们对核心科技的理解。
Memtable迭代读行过程
为了方便大家自主阅读代码后续介绍过程会贴出部分源码所有源码都基于GitHub master分支。
容易理解的是逐行读取数据是一个scan的过程因此我们会通过遍历BTree而不是HashTable来完成读取。
整个迭代过程可以分为两个部分一部分是对BTree的遍历每次我们将得到一个rowkey对应的行换句话说就是得到一个行的MvccNode list另一部分则是对MvccNode list的遍历过程中得到一个行的多版本数据。
那么什么叫“行的多版本数据”呢既然说到这就不得不插入一段对MvccNode的解释。
我们刚才提到每个MvccNode代表着一个操作这个操作放到事务中来说的话其事务的状态可能是已提交的也可能是未提交的就是图中的statuscommit/uncommit。MvccNode会记录事务的提交版本号即图中的trans_version对于未提交的操作来说trans_version是MAX。
由于每个rowkey不允许多写容易理解MvccNode list中的uncommit node一定是属于同一个事务的即事务id相同并且一定处在MvccNode list的最新位置即链尾。
MvccNode的具体实现结构如下所示
struct ObMvccTransNode {transaction::ObTransID tx_id_; // 事务idshare::SCN trans_version_; // 事务提交版本share::SCN scn_; // 数据所在日志的 scnint64_t seq_no_; // 在事务中的sql numble // 可能一个存储过程有多个相同sql no的mvcc nodeshare::SCN tx_end_scn_; // 数据所在事务的 commit/abort 日志的 scn // 未提交为max...uint8_t type_; // 标记是否是COMPACTuint8_t flag_; // 事务状态 F_COMMITTED/F_ABORTED 等等
};
需要注意的是每个操作实际上是被包含在一个事务中的因此该操作存在一个sql_no_标记着其在整个事务中的序号同时操作本身对应着一条日志scn_记录了该日志的scn简单理解为时间戳即可而整个事务的提交对应着另一条日志tx_end_scn_记录了该日志的scn。trans_version_记录着事务的提交版本我们只需要知道这是和日志时间戳不同的两个维度的概念。 我们已经知道了uncommit和commit的概念那么图中被标记为compact的node是什么呢
简单来说compact node是将后续所有已提交MvccNode中数据进行整合后得到的一个特殊的包含完整行的node。
如下图所示三个MvccNode从尾到头分别是update/update/insert操作其中insert node是包含全部列数据的完整行update node只包含更新列的数据的部分行。对于一个想要全部列的查询来说这里我们忽略版本的问题每次都需要读取这三个node并整合成一个新的完整行。因此compact node产生了。我们会将整合三个node所得到的完整行保留下来最终形成一个包含当前最新完整行的compact node。 再回到最开始的问题什么叫行的多版本数据
在逐行读取的过程中对于每个rowkey我们最终会读取出多个sql numble不一样的未提交行如果有的话、一个compact行以及多个多版本行这就是所谓的“行的多版本数据”。
这里就涉及一个版本范围的限制即(base_version, snapshot_version, multi_version_start) 三元组。我们的读取实际上附带着这样一个版本范围。我们所读取出的compact行是在遍历MvccNode list过程中将多个提交版本在(base_version, snapshot_version)之间的compact node或者commit node再次整合所得。而读取出的多个多版本行分为两部分一部分是将事务提交版本在(multi_version_startsnapshot_version)之间的node进行整合所得可能会有多个结果每个结果是由相同事务提交版本的node整合所得另一部分是将事务提交版本在(base_versionmulti_version_start)之间所有node整合所得只有一个结果。
看完以上解释大家其实已经掌握了七八了整个逐行读取的过程就是根据所谓的版本范围遍历BTree上的每个rowkey再将每个rowkey所拥有的MvccNode通过一系列的整合操作得到一条条提交或者未提交的行记录最后将这些行依次写入微块、微块写入宏块、多个宏块形成SSTable。
如果对源码细节不感兴趣的同学看到这已经可以转身离开了。但是剩下的同学别着急让我们继续向细节探寻。
整个逐行读取过程通过ObMemtableMultiVersionScanIterator实现参见ObMemtableMultiVersionScanIterator::inner_get_next_row。其中包含两个子迭代器ObMultiVersionRowIterator和ObMultiVersionValueIterator两者分别用于迭代BTree和迭代MvccNode list
class ObMultiVersionRowIterator {...common::ObVersionRange version_range_; // (base_version,snapshot_version,multi_version_start)三元组ObMultiVersionValueIterator value_iter_;...ObIQueryEngineIterator *query_engine_iter_; // BTree迭代器
};
class ObMultiVersionValueIterator {...common::ObVersionRange version_range_;ObMvccRow *value_; // 装载着ObMvccTransNode *list_head_ObMvccTransNode *version_iter_; // value-list_head_ObMvccTransNode *multi_version_iter_; // 用于迭代多版本行来自某一次的version_iter_int64_t max_committed_trans_version_; // 这一行的最大已提交版本share::SCN cur_trans_version_;bool is_node_compacted_;bool has_multi_commit_trans_;share::SCN merge_scn_; // 来自merge_param.scn_range_.end_scn_
};
迭代过程被实现为一个简易的状态机每个状态以及每个状态的行为如下
状态行为输出行结果SCAN_END通过ObMultiVersionRowIterator row_iter_迭代每个rowkey得到下一个rowkey的MvccNode list存放到ObMultiVersionValueIterator value_iter_.version_iter_。SCAN_UNCOMMITTED_ROW通过value_iter_的version_iter_迭代MvccNode list每次先拿出一个uncommit node然后向后迭代将所有相同sql_no_的uncommit node compact成一行将该行输出。该状态会重复进入直到迭代到commit node。多个uncommit行每行由多个sql_no_相同的node compact而成SCAN_COMPACT_ROW通过value_iter_的version_iter_继续迭代MvccNode list将所有访问到的commit或者compact node compact成一行将该行输出。在遇到compact node或者事务提交版本小于等于base_version的node时结束遍历并将value_iter_置为空。一个事务提交版本在base_version, snapshot_version)范围内所有commit node的compact行SCAN_MULTI_VERSION_ROW通过value_iter_的multi_version_iter_迭代MvccNode list将所有访问到的具有相同trans_version并且trans_version大于multi_version_start的node compact成一行将该行输出。所有trans_version介于(base_version,multi_version_start)之间的node将compact成一行输出。特别地当遇到事务提交版本大于multi_version_start的compact node时后续相同事务提交版本的node可以直接跳过因为已经通过compact node进行过compact了。多个多版本行在(snapshot_version,multi_version_start)范围内每个相同事务提交版本的node compact为一个多版本行在(multi_version_start,base_version)范围内所有事务提交版本的node compact为一个多版本行
状态之间的转换过程如下 需要注意的是在SCAN_UNCOMMITTED_ROW向SCAN_COMPACT_ROW进行状态转换时会对multi_version_iter_进行初始化 当前version_iter_是commit node其trans_version为所有commit node中的最大已提交版本如果该版本比multi_version_start_小或者后续node没有比最大已提交版本小的trans_versionmulti_version_iter_被初始化为null否则初始化为当前version_iter_ 以上迭代读行细节的描述可能会有一定的门槛如果现在没有理解也没有关系。如果大家真的对源码本身感兴趣并且做了一定的研究之后回过头来看这些内容相信会有更深入的思考。
后记
这篇博客依然省略了很多细节旨在分享一个以存储视角对MemTable最基本的解读希望能够让大家对OceanBase相关源码产生兴趣可以在Github上找到只有真正的去探究了源码才能领略到其中的艰涩与风光。当然笔者文中难免有不少纰漏也欢迎大家指正和讨论错误的订正也是加深理解的一个过程。