RocksDB數(shù)據(jù)存儲格式分析
RocksDB本身只是一個(gè)KV存儲,用戶通過put(key,value)來寫入key,或者通過get(key)接口來獲取value,所以單從RocksDB而言,每條記錄都是一個(gè)key-value。那么當(dāng)RocksDB作為一個(gè)存儲引擎接入到MySQL時(shí),key-value結(jié)構(gòu)如何存儲表中各個(gè)索引,以及如何記錄中各個(gè)列的信息是本文要具體討論的。
RocksDB引擎與InnoDB引擎類似,也是采用索引組織表,無論是表(主鍵索引)還是二級索引都是以LSM tree方式組織,RocksDB記錄主要包括三部分,key,value和meta三部分內(nèi)容,具體見下表,后面通過介紹一條具體記錄在RocksDB引擎中的存儲格式再做詳細(xì)說明。
我們創(chuàng)建表,并寫入數(shù)據(jù),如下:
- 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部分
由于表沒有主鍵,所以實(shí)質(zhì)是rowid:
實(shí)際數(shù)據(jù):
index_id:索引的編號,全局唯一。
rowid:由于表沒有主鍵,系統(tǒng)會產(chǎn)生一個(gè)bigint類型的rowid作為主鍵,占用8個(gè)字節(jié),而InnoDB引擎的rowid占6個(gè)字節(jié),需要注意的是rowid存儲采用的大端的存儲(高位存儲低字節(jié)),這里主要是為了memcompare。
Value部分
說明:
- Value的最前面部分(0x1b)就是存放記錄的null信息。根據(jù)記錄中可以為null字段的個(gè)數(shù),確認(rèn)需要占用的字節(jié)數(shù),如果小于8個(gè),則只需要一個(gè)字節(jié)。例子中,c1,c3,c4,c6,c8均可以為null,因此需要5個(gè)bit,所以用1個(gè)byte表示Null-flag即可,由于插入記錄中,c4不為null,則對應(yīng)的bit為0,也就是0x00011011。
- 對于null,無論是定長還是非定長數(shù)據(jù)類型,都不占用真實(shí)的存儲空間,只需要一個(gè)bit位來表示為null即可。
- 空串’’與null,上面提到了null需要占一個(gè)標(biāo)記位,而對于’’,如果是變長字段仍然需要存儲長度信息,對于定長字段,則會補(bǔ)全。
- 對于變長字段,比如varchar,0x03 0x61 0x62 0x63數(shù)據(jù)有l(wèi)en+data組成,如果數(shù)據(jù)長度小于256,len只需要占用一個(gè)byte;如果len大于255,且小于65536,則需要占用2個(gè)字節(jié),對于longblob類型,則需要占4個(gè)字節(jié)。
- 對于定長字段,不需要存長度信息直接存儲data,如果不足則補(bǔ)充。補(bǔ)充字符有點(diǎn)詫異,對于char類型,補(bǔ)充0x20,對于binary類型,補(bǔ)充0x00。
- 對于lob類型,比如tinyblob,blob,mediumblob,longblob,以及對應(yīng)的text類型,處理策略與varchar類似,存儲長度的字節(jié)數(shù)根據(jù)數(shù)據(jù)類型的范圍確定,比如blob長度占用2個(gè)字節(jié),而longblob的長度占4個(gè)字節(jié)。所以在rocksdb里面,沒有innodb中所謂“溢出頁”的概念。對于innodb引擎,如果blob字段內(nèi)容超過768字節(jié),多余的data存儲在溢出頁,頁內(nèi)通過20個(gè)字節(jié)指向溢出頁,主要包括第一個(gè)blob頁的space_id,page_no和起始偏移,如果存在多個(gè)blob頁,則頁與頁之間通過類似的方式進(jìn)行關(guān)聯(lián)。具體可以參考btr0cur.h文件中關(guān)于BTR_EXTERN_xxx相關(guān)的宏定義,以及接口btr_copy_externally_stored_field_prefix_low。
- 有關(guān)value部分的存儲實(shí)現(xiàn)可以參考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,這個(gè)SequenceID在事務(wù)提交時(shí)產(chǎn)生,主要用于RocksDB實(shí)現(xiàn)MVCC,用于可見性判斷,此外meta中還包含flag信息,由于標(biāo)示記錄類型,put,delete,singleDelete等,具體而言Sequence占7個(gè)字節(jié),flag占1個(gè)字節(jié)。
RocksDB索引格式
RocksDB中,所有的數(shù)據(jù)都是通過索引來組織,與InnoDB類似,也是索引組織表,每個(gè)索引有一個(gè)全局唯一的index_id。索引主要包括兩類:主鍵索引和二級索引,前面介紹的記錄格式,也就是主鍵索引的格式,包括key,value和meta三部分。二級索引也包含key,value和meta三部分,但是value中不包含任何數(shù)據(jù),只是包含checksum信息。
主鍵索引
二級索引
對比InnoDB引擎(innodb_file_format=Barracuda,row_format=compact)
InnoDB記錄格式
我們?nèi)匀灰陨厦娴谋斫Y(jié)構(gòu)來看看InnoDB的存儲格式。
- 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');
記錄內(nèi)容
說明
- 03 02 0a,這里存的是長度信息,所有非null的變長列信息都逆序存在一起,這里按先后順序是c5,c4,c2,這里innodb將char(10)也當(dāng)作變長字段處理了。
- 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個(gè)字節(jié),6個(gè)字節(jié)和7個(gè)字節(jié)。
- 最后一部分是數(shù)據(jù),null不占任何存儲空間,與rocksdb處理類似。
整體而言,InnoDB記錄格式包含了record_header(記錄頭信息),占5個(gè)字節(jié),主要包括記錄號(heap_no),列數(shù)目,下一條記錄的位置以及是否刪除等信息。RocksDB則相對簡單,只有整體的value-size,以及通過Meta中flag標(biāo)示記錄的狀態(tài)put 或者是delete。InnoDB將變長列長度信息集中存放在一起,使得查找任意列的代價(jià)都差不多,而RocksDB的變長列信息則是放在每列的前面,訪問最后一列需要逐一計(jì)算前面的列,才能定位。此外,由于InnoDB引擎與RocksDB引擎由于實(shí)現(xiàn)MVCC的機(jī)制不同,導(dǎo)致InnoDB引擎和RocksDB引擎需要存儲的額外信息也不同。InnoDB實(shí)現(xiàn)MVCC依賴于回滾段信息,記錄需要額外存儲trxid和roll_ptr兩個(gè)字段,分別是6個(gè)字節(jié)和7個(gè)字節(jié)(type,rsegid,pageNO,offset),其中type占一個(gè)bit位,標(biāo)示insert 或者是update類型,rsegid回滾段id占7bit位,pageNo占4個(gè)字節(jié),頁內(nèi)偏移占2個(gè)字節(jié)。RocksDB實(shí)現(xiàn)MVCC則是依賴于SequenceID,通過SequenceID來判斷記錄的可見性,SequenceID占7個(gè)字節(jié)。
細(xì)節(jié)上來說,RocksDB引擎和InnoDB引擎在處理null,char和varchar的方式類似,但I(xiàn)nnoDB對于char類型做了優(yōu)化,統(tǒng)一作為varchar處理。另外RocksDB引擎沒有對blob做特殊處理。你可能會有疑問,RocksDB不是也有block_size嗎,如果設(shè)置為16k,blob數(shù)據(jù)超過16k怎么辦?對于InnoDB而言,由于表實(shí)質(zhì)是以一個(gè)個(gè)page通過B-tree組織起來的,每個(gè)page是固定大小,當(dāng)記錄非常大時(shí),就需要借助溢出頁,通過鏈接的方式關(guān)聯(lián)起來。而RocksDB中block_size只是一個(gè)壓縮單位,并沒有嚴(yán)格約束,文件內(nèi)容以block組織,由于文件中block可能是壓縮過的,因此每個(gè)block的大小不固定,通過偏移來定位具體某個(gè)block的位置。如果遇到大的blob數(shù)據(jù),則可能這個(gè)block比較大,記錄所有數(shù)據(jù)存儲在一起,不會跨block。
對于索引長度限制也有所不同,對于InnoDB引擎來說,索引中單列長度不能超過767個(gè)字節(jié),而RocksDB引擎單列長度不超過2048個(gè)字節(jié),具體可以參考max_supported_key_part_length各自的實(shí)現(xiàn);整個(gè)索引的長度,RocksDB和InnoDB都限制在3072個(gè)字節(jié),實(shí)際上是server層的限制,因?yàn)樗鼈兊母髯韵拗频拈L度都比server層的大。