Rocksdb是一個kv引擎,由facebook團隊基於levelDB改進而來,Rocksdb采用LSM-tree存儲數據,良好的讀寫特性以及壓縮特性使得其非常受歡迎。此外,Rocksdb引擎作為插件已經集成在facebook維護的MySQL分支,用戶可以通過SQL來訪問rocksDB。本文主要通過分析Rocksdb引擎的記錄格式,並通過對比innodb,來讓大家了解Rocksdb。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引擎中的存儲格式來說明問題。
rocksdb基本記錄存儲格式
key_size
key
value_size
value
PK/SecKey
Columns data
SeqenceId,flag
create table row_format( id int not null, c1 int, c2 char(10) not null, c3 char(10), c4 varchar(10), c5 varchar(10) not null, c6 blob, c7 binary(10) not null, c8 varbinary(10)) engine=rocksdb; insert into row_format(id,c2,c4,c5,c7) values(1,'abc','abc','efg','111')
key部分:
Index_id
key
4bytes
8bytes
0x7fdfa4278ea0: 0x00 0x00 0x01 0x7b 0x00 0x00 0x00 0x00
0x7fdfa4278ea8: 0x00 0x00 0x00 0x05
Index_id:索引的編號,全局唯一。
rowid:由於表沒有主鍵,系統會產生一個bigint類型的rowid作為主鍵,占用8個字節,而innodb引擎的rowid占6個字節,需要注意的是rowid存儲采用的大端的存儲(高位存儲低字節),這裡主要是為了memcompare。
Value部分:
Null-flag
ID
C1
C2
C3
C4
C5
C6
C7
C8
Length
1B
4B
----
30B
----
4B
4B
----
10B
----
Value
1
abc0x20…
len+value
len+value
1110x00…
0x7fdfa4251e50: 0x1b 0x01 0x00 0x00 0x00 0x61 0x62 0x63
0x7fdfa4251e58: 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20
0x7fdfa4251e60: 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20
0x7fdfa4251e68: 0x20 0x20 0x20 0x20 0x20 0x20 0x20 0x20
0x7fdfa4251e70: 0x20 0x20 0x20 0x03 0x61 0x62 0x63 0x03
0x7fdfa4251e78: 0x65 0x66 0x67 0x31 0x31 0x31 0x00 0x00
0x7fdfa4251e80: 0x00 0x00 0x00 0x00 0x00
說明:
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信息。
主鍵索引
key
Value
Meta
Index_id
PK
NULL標記位
列數據
Checksum(可選)
SeqId,flag
二級索引
key
Value
Meta
Index_id
SecondaryKey
PK
Checksum(可選)
SeqId,flag
對比innodb引擎(innodb_file_format=Barracuda,row_format=compact)
innodb記錄格式
變長字段長度列表
NULL標記位
record_header
Trxid
Roll_ptr
列數據
create table row_format( id int not null, c1 int, c2 char(10) not null, c3 char(10), c4 varchar(10), c5 varchar(10) not null, c6 blob, c7 binary(10) not null, c8 varbinary(10)) engine=innodb; insert into row_format(id,c2,c4,c5,c7) values(1,'1234','ab','efg','111');
記錄內容:
0000c0b0 00 00 03 02 0a 1b 00 00 18 ff b5 00 00 00 00 28 |...............(|
0000c0c0 00 00 00 00 01 01 03 83 00 00 01 36 01 10 80 00 |...........6....|
0000c0d0 00 01 31 32 33 34 20 20 20 20 20 20 61 62 65 66 |..1234 abef|
0000c0e0 67 31 31 31 00 00 00 00 00 00 00 00 00 00 00 00 |g111............|
說明:
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處理類似。區別在於對於char類型的處理,innodb將字段c2類型char(10)補齊到10個字節,存儲為31 32 33 34 20 20 20 20 20 20,將其作為varchar處理,記錄了長度信息;而Rocksdb則是補齊到30個字節(utf8字符集),作為char處理,不記錄長度信息。
整體而言,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層的大。具體可以參考各自max_supported_key_length的實現。
參考文檔
http://dev.mysql.com/doc/refman/5.7/en/innodb-physical-record.html
http://hedengcheng.com/?p=127
http://www.cnblogs.com/zhoujinyi/articles/2726462.html