RocksDB 本身只是一个 KV 存储, 用户通过 put(key,value)来写入 key, 或者通过 get(key)接口来获取 value, 所以单从 RocksDB 而言, 每条记录都是一个 key-value 那么当 RocksDB 作为一个存储引擎接入到 MySQL 时, key-value 结构如何存储表中各个索引, 以及如何记录中各个列的信息是本文要具体讨论的
RocksDB 引擎与 InnoDB 引擎类似, 也是采用索引组织表, 无论是表 (主键索引) 还是二级索引都是以 LSM tree 方式组织, RocksDB 记录主要包括三部分, key,value 和 meta 三部分内容, 具体见下表, 后面通过介绍一条具体记录在 RocksDB 引擎中的存储格式再做详细说明
我们创建表, 并写入数据, 如下:
- createtablerow_format(
- idintnotnull,
- c1int,
- c2char(10)notnull,
- c3char(10),
- c4varchar(10),
- c5varchar(10)notnull,
- c6blob,
- c7binary(10)notnull,
- c8varbinary(10))
- engine=rocksdb;
- insertintorow_format(id,c2,c4,c5,c7)values(1,'abc','abc','efg','111')
Key 部分
由于表没有主键, 所以实质是 rowid:
实际数据:
index_id: 索引的编号, 全局唯一
rowid: 由于表没有主键, 系统会产生一个 bigint 类型的 rowid 作为主键, 占用 8 个字节, 而 InnoDB 引擎的 rowid 占 6 个字节, 需要注意的是 rowid 存储采用的大端的存储(高位存储低字节), 这里主要是为了 memcompare
Value 部分
说明:
Value 的最前面部分 (0x1b) 就是存放记录的 null 信息根据记录中可以为 null 字段的个数, 确认需要占用的字节数, 如果小于 8 个, 则只需要一个字节例子中, c1,c3,c4,c6,c8 均可以为 null, 因此需要 5 个 bit, 所以用 1 个 byte 表示 Null-flag 即可, 由于插入记录中, c4 不为 null, 则对应的 bit 为 0, 也就是 0x00011011
对于 null, 无论是定长还是非定长数据类型, 都不占用真实的存储空间, 只需要一个 bit 位来表示为 null 即可
空串与 null, 上面提到了 null 需要占一个标记位, 而对于, 如果是变长字段仍然需要存储长度信息, 对于定长字段, 则会补全
对于变长字段, 比如 varchar,0x03 0x61 0x62 0x63 数据有 len+data 组成, 如果数据长度小于 256,len 只需要占用一个 byte; 如果 len 大于 255, 且小于 65536, 则需要占用 2 个字节, 对于 longblob 类型, 则需要占 4 个字节
对于定长字段, 不需要存长度信息直接存储 data, 如果不足则补充补充字符有点诧异, 对于 char 类型, 补充 0x20, 对于 binary 类型, 补充 0x00
对于 lob 类型, 比如 tinyblob,blob,mediumblob,longblob, 以及对应的 text 类型, 处理策略与 varchar 类似, 存储长度的字节数根据数据类型的范围确定, 比如 blob 长度占用 2 个字节, 而 longblob 的长度占 4 个字节所以在 rocksdb 里面, 没有 innodb 中所谓溢出页的概念对于 innodb 引擎, 如果 blob 字段内容超过 768 字节, 多余的 data 存储在溢出页, 页内通过 20 个字节指向溢出页, 主要包括第一个 blob 页的 space_id,page_no 和起始偏移, 如果存在多个 blob 页, 则页与页之间通过类似的方式进行关联具体可以参考 btr0cur.h 文件中关于 BTR_EXTERN_xxx 相关的宏定义, 以及接口 btr_copy_externally_stored_field_prefix_low
有关 value 部分的存储实现可以参考 rocksdb 引擎接口 convert_record_to_storage_format,convert_record_from_storage_format 和 innodb 引擎接口 row_mysql_store_col_in_innobase_format,row_sel_field_store_in_mysql_format
meta 部分
meta 部分主要是 SequenceID, 这个 SequenceID 在事务提交时产生, 主要用于 RocksDB 实现 MVCC, 用于可见性判断, 此外 meta 中还包含 flag 信息, 由于标示记录类型, put,delete,singleDelete 等, 具体而言 Sequence 占 7 个字节, flag 占 1 个字节
RocksDB 索引格式
RocksDB 中, 所有的数据都是通过索引来组织, 与 InnoDB 类似, 也是索引组织表, 每个索引有一个全局唯一的 index_id 索引主要包括两类: 主键索引和二级索引, 前面介绍的记录格式, 也就是主键索引的格式, 包括 key,value 和 meta 三部分二级索引也包含 key,value 和 meta 三部分, 但是 value 中不包含任何数据, 只是包含 checksum 信息
主键索引
二级索引
对比 InnoDB 引擎(innodb_file_format=Barracuda,row_format=compact)
InnoDB 记录格式
我们仍然以上面的表结构来看看 InnoDB 的存储格式
- createtablerow_format(
- idintnotnull,
- c1int,
- c2char(10)notnull,
- c3char(10),
- c4varchar(10),
- c5varchar(10)notnull,
- c6blob,
- c7binary(10)notnull,
- c8varbinary(10))engine=innodb;
- insertintorow_format(id,c2,c4,c5,c7)values(1,'1234','ab','efg','111');
记录内容
说明
03 02 0a, 这里存的是长度信息, 所有非 null 的变长列信息都逆序存在一起, 这里按先后顺序是 c5,c4,c2, 这里 innodb 将 char(10)也当作变长字段处理了
1b 存储的是 null 信息, 与 rocksdb 对 null 处理一致 00 00 18 ff b5 存储的是 record-header
00 00 00 00 28 00 00 00 00 01 01 03 83 00 00 01 36 01 10, 这三部分别是 rowid,trxid 和 roll_ptr, 分别占 6 个字节, 6 个字节和 7 个字节
最后一部分是数据, null 不占任何存储空间, 与 rocksdb 处理类似
整体而言, InnoDB 记录格式包含了 record_header(记录头信息), 占 5 个字节, 主要包括记录号(heap_no), 列数目, 下一条记录的位置以及是否删除等信息 RocksDB 则相对简单, 只有整体的 value-size, 以及通过 Meta 中 flag 标示记录的状态 put 或者是 deleteInnoDB 将变长列长度信息集中存放在一起, 使得查找任意列的代价都差不多, 而 RocksDB 的变长列信息则是放在每列的前面, 访问最后一列需要逐一计算前面的列, 才能定位此外, 由于 InnoDB 引擎与 RocksDB 引擎由于实现 MVCC 的机制不同, 导致 InnoDB 引擎和 RocksDB 引擎需要存储的额外信息也不同 InnoDB 实现 MVCC 依赖于回滚段信息, 记录需要额外存储 trxid 和 roll_ptr 两个字段, 分别是 6 个字节和 7 个字节(type,rsegid,pageNO,offset), 其中 type 占一个 bit 位, 标示 insert 或者是 update 类型, rsegid 回滚段 id 占 7bit 位, pageNo 占 4 个字节, 页内偏移占 2 个字节 RocksDB 实现 MVCC 则是依赖于 SequenceID, 通过 SequenceID 来判断记录的可见性, SequenceID 占 7 个字节
细节上来说, RocksDB 引擎和 InnoDB 引擎在处理 null,char 和 varchar 的方式类似, 但 InnoDB 对于 char 类型做了优化, 统一作为 varchar 处理另外 RocksDB 引擎没有对 blob 做特殊处理你可能会有疑问, RocksDB 不是也有 block_size 吗, 如果设置为 16k,blob 数据超过 16k 怎么办? 对于 InnoDB 而言, 由于表实质是以一个个 page 通过 B-tree 组织起来的, 每个 page 是固定大小, 当记录非常大时, 就需要借助溢出页, 通过链接的方式关联起来而 RocksDB 中 block_size 只是一个压缩单位, 并没有严格约束, 文件内容以 block 组织, 由于文件中 block 可能是压缩过的, 因此每个 block 的大小不固定, 通过偏移来定位具体某个 block 的位置如果遇到大的 blob 数据, 则可能这个 block 比较大, 记录所有数据存储在一起, 不会跨 block
对于索引长度限制也有所不同, 对于 InnoDB 引擎来说, 索引中单列长度不能超过 767 个字节, 而 RocksDB 引擎单列长度不超过 2048 个字节, 具体可以参考 max_supported_key_part_length 各自的实现; 整个索引的长度, RocksDB 和 InnoDB 都限制在 3072 个字节, 实际上是 server 层的限制, 因为它们的各自限制的长度都比 server 层的大
来源: http://www.chinastor.org/gdcc/10837.html