RocksDB数据存储格式分析

存储
RocksDB本身只是一个KV存储,用户通过put(key,value)来写入key,或者通过get(key)接口来获取value,所以单从RocksDB而言,每条记录都是一个key-value。那么当RocksDB作为一个存储引擎接入到MySQL时,key-value结构如何存储表中各个索引,以及如何记录中各个列的信息是本文要具体讨论的。

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引擎中的存储格式再做详细说明。

我们创建表,并写入数据,如下:

  1. create table row_format(   
  2.     id int not null,   
  3.     c1 int,   
  4.     c2 char(10) not null,   
  5.     c3 char(10),   
  6.     c4 varchar(10),   
  7.     c5 varchar(10) not null,   
  8.     c6 blob,   
  9.     c7 binary(10) not null,   
  10.     c8 varbinary(10)) 
  11. engine=rocksdb; 
  12.  
  13. insert into row_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的存储格式。

  1. create table row_format(   
  2.     id int not null,   
  3.     c1 int,   
  4.     c2 char(10) not null,   
  5.     c3 char(10),   
  6.     c4 varchar(10),   
  7.     c5 varchar(10) not null,   
  8.     c6 blob,   
  9.     c7 binary(10) not null,   
  10.     c8 varbinary(10)) engine=innodb; 
  11.  
  12. insert into row_format(id,c2,c4,c5,c7) values(1,'1234','ab','efg','111'); 

记录内容

说明

  1. 03 02 0a,这里存的是长度信息,所有非null的变长列信息都逆序存在一起,这里按先后顺序是c5,c4,c2,这里innodb将char(10)也当作变长字段处理了。
  2. 1b存储的是null信息,与rocksdb对null处理一致。00 00  18 ff b5存储的是record-header。
  3. 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个字节。
  4. 最后一部分是数据,null不占任何存储空间,与rocksdb处理类似。

整体而言,InnoDB记录格式包含了record_header(记录头信息),占5个字节,主要包括记录号(heap_no),列数目,下一条记录的位置以及是否删除等信息。RocksDB则相对简单,只有整体的value-size,以及通过Meta中flag标示记录的状态put 或者是delete。InnoDB将变长列长度信息集中存放在一起,使得查找任意列的代价都差不多,而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层的大。

 

责任编辑:武晓燕 来源: 阿里巴巴数据库技术
相关推荐

2021-12-11 08:05:12

FlinkRocksDB数据

2023-09-06 15:00:35

Pandas存储格式

2018-07-04 09:30:55

列式存储格式

2022-06-08 07:34:02

持久化数据存储原理索引存储格式

2011-12-29 09:33:38

云计算云存储

2022-08-31 08:04:08

Ceph配置选项

2020-03-03 13:42:08

数据存储数据存储

2009-09-21 18:00:49

Hibernate X

2019-11-26 09:56:48

Python数据存储

2012-08-17 10:35:17

云计算存储大数据

2016-12-01 14:47:20

2010-07-09 10:42:38

HART协议

2019-05-28 10:32:29

TCPUDP SYN

2017-07-04 10:58:57

SAN存储网络存储系统架构

2014-03-24 10:54:10

大数据分析

2015-12-15 10:51:14

软件定义存储数据中心

2018-05-23 08:39:18

AlluxioCeph对象存储

2014-05-13 14:11:36

GoRedis

2013-05-16 10:45:46

BI云存储成本混合云

2011-08-29 10:55:03

SQL Server分页存储过程优化效率分
点赞
收藏

51CTO技术栈公众号