作者:
公祺,一个专注于 OBKV 的程序员
海芊,一个致力于当网红的 OceanBase 文档工程师。个人频道:Amber loves OB
1. 微块和宏块的关系
OceanBase 数据库的存储引擎采用了基于 LSM-Tree 的架构,把基线数据和增量数据分别保存在磁盘(SSTable)和内存(MemTable)中,其中 SSTable 以宏块(Macro Block)为单位组织数据,每个宏块大小为 2MB。宏块内部又划分出很多个大小为 16K(压缩前的大小)微块(Micro Block),每个微块内则包含多个行(Row)。
OceanBase 数据库内部 IO 的最小单元就是微块,微块数据可以进行压缩,下图是微块在宏块中的存储格式:
其中:
1.macro block header:宏块的元数据,包括:版本、宏块大小、微块的个数、列的类型、行的个数、压缩算法等信息。
2.micro block n:微块的具体数据,具体信息见下文微块的存储格式。
3.micro block index:微块的索引数据,用来索引每个微块,通常常驻内存。
每一个微块包含多个行数据,行数据中有多个列的数据,如下是当前微块的存储格式:
其中:
1.record header:包含微块数据大小、头部大小、version、checksum 等。
2.micro block header:包含微块的元数据,包括:列数、行索引的偏移量、行数等。
3.Row n:包含行数据,包括:row header、列数据、列索引。
(1)row header:行的元数据,包括:列索引占用的字节数、删除标记等。
(2)col n:列的值,包括:类型、长度、列的值。
(3)column index:列的索引数据,用来索引每一列,记录了每列值的偏移和长度。
4.Row Index:行的索引数据,用来索引每一行,记录了每行的偏移和长度。
接下来,让我们根据 OceanBase 的开源代码,介绍微块的具体代码实现,后续的代码解读都是基于此版本:v3.1.0_CE_BP1。
2. 微块中行数据的编码
行数据写入的主要逻辑在 ObMacroBlockWriter 模块,见如下的代码:
// src/storage/blocksstable/ob_macro_block_writer.cpp
// 该函数主要逻辑为:
// 1. 将行数据写入到微块中,并更新 bloomfilter 和 checksum
// 2. 在当前微块写满的情况下:构建当前的微块,并切换微块写入器
int ObMacroBlockWriter::append_row(const ObStoreRow& row, const int64_t split_size, const bool ignore_lob)
{int ret = OB_SUCCESS;const ObStoreRow* row_to_append = &row;// ... 省略参数检查的相关代码if (OB_SUCC(ret)) {// 向当前的微块中写入一行数据if (OB_FAIL(micro_writer_->append_row(*row_to_append))) { if (OB_BUF_NOT_ENOUGH == ret) {if (0 == micro_writer_->get_row_count()) {// 不支持超过 16KB 的行ret = OB_NOT_SUPPORTED;STORAGE_LOG(ERROR, "The single row is too large, ", K(ret), K(row));} else if (OB_FAIL(build_micro_block(false))) { // 当前微块写满后,需要构建该微块:编码、压缩等// for compatibilitySTORAGE_LOG(WARN, "Fail to build micro block, ", K(ret));} else if (OB_FAIL(micro_writer_->append_row(*row_to_append))) { // 继续写当前行数据STORAGE_LOG(ERROR, "Fail to append row to micro block, ", K(ret), K(row));} else if (data_store_desc_->need_calc_column_checksum_ && OB_FAIL(add_row_checksum(row_to_append->row_val_))) { // 计算 checksumSTORAGE_LOG(WARN, "fail to add column checksum", K(ret));}if (OB_SUCC(ret) && data_store_desc_->need_prebuild_bloomfilter_) {// 根据 rowkey 构建 bloomfilterconst ObStoreRowkey rowkey(row_to_append->row_val_.cells_, data_store_desc_->bloomfilter_rowkey_prefix_);if (OB_FAIL(micro_rowkey_hashs_.push_back(static_cast<uint32_t>(rowkey.murmurhash(0))))) {STORAGE_LOG(WARN, "Fail to put rowkey hash to array ", K(ret), K(rowkey));micro_rowkey_hashs_.reuse();ret = OB_SUCCESS;}