在上一篇文档中,export导出有一些问题
为了深入理解KV的存储结构,我们参考文档:
https://github.com/cockroachdb/cockroach/blob/master/docs/tech-notes/encoding.md
CRDB中的数据结构编码方式
像其他的键值数据库数据库一样,CRDB将数据以“键-值对”的这种形式存储,这种存储方式在不断地发展,能够保证向前向后兼容。
CRDB的键值存储有三个版本,
第一个版本在https://www.cockroachlabs.com/blog/sql-in-cockroachdb-mapping-table-data-to-key-value-storage/
第二个版本在https://www.cockroachlabs.com/blog/sql-cockroachdb-column-families/
下面创建一张表,并插入一条数据,简单看看3个版本是如何存储数据的。
CREATE TABLE test ( key INT PRIMARY KEY, floatVal FLOAT, stringVal STRING ) INSERT INTO test VALUES (10, 4.5, "hello”) |
-
第1个版本的存储格式如下:
Key Value /test/10/floatVal
4.5
/test/10/stringVal
"hello”
这种方式很简单,key的存储方式为:/表名/主键值/列名,value直接记录这个值。
事实上,还存在一张元数据的表:
test Table ID
1000
key Column ID
1
floatVal Column ID
2
stringVal Column ID
3
那么将上面各位置的信息替换成元数据表中的值,键值对的存储形式如下:
Key Value /1000/10/2
4.5
/1000/10/3
"hello”
是不是更简单了。
-
第2个版本的存储方式如下:
首先,版本2引入了一个重要的概念,列簇(column family),加入列簇后,KV的插入、更新、删除操作相比于版本1平均快了5倍。
版本1的编码格式有以下几个问题:
(1)key的主键前缀重复,如有100个字段,“/表名/主键值/”重复了100次,并且value中前4个字节是循环冗余校验码,当数据量大的时候,非常浪费磁盘空间和网络带宽。(2)最严重的问题是在提交事务时,每个key都要以“write intent”的方式与其关联,以便进行后续的解析。
自然而然,很多的NOSQL数据库设计就会不约而同地提出一种解决方案:列簇(column family),将多个相似字段汇聚成一个值。
现在的存储格式如下所示:
Key Value /<tableid>/0/11/0
<crc>/1/string/"Hal"/1/string/"hal@cockroachlabs.com"
/<tableid>/0/13/0
<crc>/1/string/"Orin"/1/string/"orin@cockroachlabs.com"
key:/tableid/index/pk val/cf0/ value:/crc/col id0/value/.../col idn/value/
-
第3个版本的存储方式如下:
版本3还是利用了列簇的特性,因此,一行数据的存储会以一个或多个的键值对(KV-pairs)的形式存在,主键值会存放在key中,而数据项会存放在value中。
这里还分为主键索引(primary indexes)和非主键索引(secondary indexes)。主键索引的key格式为:/Table/<id>/<index>/<pk val>/<family>
主键索引的value格式为:<crc><type>/TUPLE/colIDDiff:colID:type/val/.../colIDDiff:colID:type
对于这样一张表
CREATE TABLE test ( id INT, name STRING PRIMARY KEY, age INT, school STRING, INDEX idx (age ASC) )
id name age school 123 lml 25 scut 实际存储结构如下
key: /Table/52/1/"lml"/0 value:/TUPLE/1:1:Int/123/2:3:Int/26/1:4:Bytes/scut
现在,执行
./cockroach debug rocksdb scan --db=cockroach-data --value_hex | grep "lml"
看看数据
/Table/52/1/"lml"/0/1531803756.900601973,0 : 0xE5ACBDEE0A13F6012334160473637574
/Table/52/2/26/"lml"/0/1531803756.900601973,0 : 0xF4D2D1CB03有两行数据,这是因为为创建了INDEX idx(age),即非主键索引。
非主键索引的存储格式:非主键索引的key格式为:/Table/<id>/<index>/<Data from where the row intersects the indexed columns>/0
非主键索引的value格式为:说实话,我也没看懂- -。但绝大多数都是空值。
温馨提示:
通过tableDesc.PrimaryIndexSpan()可以过滤出主键索引的Span。
从版本3的存储方式来看,上篇文档我们留下的第一个问题:
-
若设置主键则会导致value值中没有主键字段的数据,并且不能确定字段的顺序
因为主键索引是在key中的(比如key=/Table/60/1/"tom"/88/0,这里"tom"/88就是双主键)。
那么为了解决这个问题,我们可以简单地将key中的多主键字段取出来,然后用分隔符‘/’进行切分,得到主键字段数据,然后拼接value的数据。但是,这种做法是很蠢的,有没有什么更好的办法?
可以回过头来看看SELECT * FROM TABLE;的处理逻辑。