(接 從草稿開始寫一個 MySQL 存儲引擎(一))
增加功能
現(xiàn)在是時候來填充我們的 Handler 類中的各種方法了涡贱。詳細的描述每個方法將會花上很多時間堪夭。同時愕把,其中的一部分我暫時也不太清楚。但是我會給出一個概覽森爽,并且提供具體實現(xiàn)的鏈接恨豁。
意識到同一個表中運行了多種 Handler 的實例是一個非常重要的過程。實際的表數(shù)據(jù)因此需要存儲在不同的對象中(一個 'share')爬迟,然后 Handler 將會需要這個 Share橘蜜。如果你的數(shù)據(jù)存儲在一個原始文件中,那么這個文件的 Handle 將會成為 Share 的一個成員付呕,而不是 Handler 中的成員计福。
你能在 這兒 看到一個樣例的Share,以及我的 UpscaledbShare 徽职∠笥保可以看到 UpscaledbShare 存儲了實際的數(shù)據(jù) handles,以及額外關(guān)于數(shù)據(jù)庫的元數(shù)據(jù)姆钉。
創(chuàng)建表
ExampleHandler::create 和 UpscaledbHandler::create 每當(dāng)創(chuàng)建表時都會被調(diào)用说订。之后,MySQL 將會在表上調(diào)用 open() 方法潮瓶。你的 create() 方法因此可以準(zhǔn)備表陶冷,但是并不一定需要打開它。
UpscaledbHandler::create() 的實現(xiàn)非常直接毯辅。首先它創(chuàng)建一個 upscaledb 環(huán)境埃叭,然后創(chuàng)建一個針對每個索引的數(shù)據(jù)庫。如果用戶沒有為對應(yīng)的表創(chuàng)立主鍵悉罕,那么將會生成一個索引赤屋。數(shù)據(jù)庫的配置文件依賴于每列的實際類型以及其他的一下參數(shù)(例如,是否唯一)壁袄。
打開表
ExampleHandler::open 和 UpscaledbHandler::open 每當(dāng)打開表時都會被調(diào)用类早。如上面提到的,它將會在同一個表中發(fā)生多次嗜逻。因此你需要將你的實際數(shù)據(jù)存儲到 'Share' 中涩僻。
當(dāng)檢索到一個 'Share' 對象的指針后,UpscaledbHandler::open() 方法將會檢查 upscaledb 環(huán)境是否已經(jīng)打開。如果打開逆日,它將會立即返回嵌巷。如果沒有,它將會打開文件室抽,并且將環(huán)境的 Handle 存儲到 'Share' 中搪哪。
關(guān)閉表
ExampleHandler::close 和 UpscaledbHandler::close 方法被用來關(guān)閉表。如果你的表數(shù)據(jù)存儲在 Share 中坪圾,那么你將會使用引用計數(shù)來確定何時銷毀 Share 對象晓折。在我的 UpscaledbHandler 中我不會銷毀我的 Share,因為 Share 在之后的很長時間將會起作用兽泄。
插入行(INSERTing rows)
不論你何時調(diào)用 INSERT SQL 狀態(tài)漓概,你 handler 的 write_row() 方法都會被調(diào)用。它唯一的參數(shù)就是新的一行病梢,字節(jié)陣列中的序列胃珍。當(dāng)你調(diào)用 CREATE TABLE 狀態(tài)時,陣列會用不同的順序存儲實際上的列蜓陌。主鍵永遠是初始的堂鲜,后面跟隨著其他索引過的列,最后跟著未索引的列护奈。
這個字節(jié)陣列通常開始于一個(可選的)位映射缔莲,它描述了當(dāng)前列中的空值。后面跟隨著任何一個修改過長度的列霉旗,或者是變長的列(例如 TINYTEXT痴奏,MEDIUMTEXT, TEXT, LONGTEXT 或者是一個對應(yīng)的BLOB 列)。變長的列通常以一到兩個存儲了列的大小的字節(jié)開始厌秒,后面跟著相關(guān)的數(shù)據(jù)读拆。(這些數(shù)據(jù)能夠被存儲為分隔的塊;因此這個字節(jié)序列包含了一個對應(yīng)了實際數(shù)據(jù)的編碼指針鸵闪。)如果你將一行存進文件中檐晕,它將會壓縮變量長度為一個更加緊湊的格式,從而節(jié)約空間蚌讼。
我的 UpscaledbHandler 緩存了索引的域?qū)ο蟊倩遥瑥亩梢钥焖俳鈮核饕牧小O旅娴拇a可以用于解壓一個索引行的 key (‘index’ 是指索引的數(shù)字 ID篡石;主索引一直是0)
static inline ups_key_t
key_from_row(TABLE *table, const uchar *buf, int index)
{
KEY_PART_INFO *key_part = table->key_info[index].key_part;
uint16_t key_size = (uint16_t)key_part->length;
uint32_t offset = key_part->offset;
if (key_part->type == HA_KEYTYPE_VARTEXT1
|| key_part->type == HA_KEYTYPE_VARBINARY1) {
key_size = buf[offset];
offset += 1;
}
else if (key_part->type == HA_KEYTYPE_VARTEXT2
|| key_part->type == HA_KEYTYPE_VARBINARY2) {
key_size = *(uint16_t *)&buf[offset];
offset += 2;
}
ups_key_t key = ups_make_key((uchar *)buf + offset, key_size);
return key;
}
當(dāng)解壓了檢索 key 之后芥喇,你可以在你的文件中存儲行數(shù)據(jù)。同時你將需要解決三個問題:
- 用戶沒有指定任何的索引或者主鍵凰萨。
- 用戶指定了主鍵但是沒有制定其他的索引继控。
- 用戶制定了主鍵和額外的索引械馆。
CREATE TABLE test (
id INT NOT NULL,
last_name CHAR(30) NOT NULL,
first_name CHAR(30) NOT NULL,
PRIMARY KEY (id),
INDEX name (last_name, first_name) -- creates a virtual index!
);
可以參照作者的相關(guān)實現(xiàn): UpscaledbHandler::write_row 。
<h3deleteing rows="">
在 Handler 中武通,DELETE SQL 命令以調(diào)用 delete_row()方法作為收尾霹崎。你需要確保 所有 的部分都被刪除了,不僅僅是主要的冶忱。同時尾菇,刪除主鍵是非常簡單的,因為 MySQL 將會使用一個數(shù)據(jù)庫游標(biāo)來定位它朗和。 可以參考 [UpscaledbHandler::delete_row()][https://github.com/cruppstahl/upscaledb-mysql/blob/d4c2744e612616efc2deeeaf4ccd3132959c0e14/storage/upscaledb/ha_upscaledb.cc#L1163-L1217] 的實現(xiàn)。
更新行(UPDATEing rows)
這是最復(fù)雜的一部分 - 至少如果你希望讓它能夠比較快的話簿晓。 updata_row() 方法接受兩個參數(shù):舊的行的值和新行的值眶拉。最原始的方法就是調(diào)用 delete_row() 來刪除原始的行,然后調(diào)用 write_row() 來寫入新行憔儿。這樣會運行的非常慢忆植,因為即使只更新一列,你實際上也更新了所有的列谒臼。只更新修改了的列會快的多朝刊。
游標(biāo)
對于大部分的任務(wù),MySQL 核心僅僅會創(chuàng)建一個數(shù)據(jù)庫游標(biāo)蜈缤,定位一個 key(不論是在主鍵或者第二檢索上)拾氓,然后在實際數(shù)據(jù)上工作的時候向前移動。下面是一些為了支撐游標(biāo)的功能你需要實現(xiàn)的一些方法底哥。
- index_init(): creates a cursor for a secondary index
- index_end(): can close the cursor
- index_read_map(): positions the cursor on a row
- index_next(): moves cursor to the next key, retrieves the row
- index_prev(): moves cursor to the previous key, retrieves the row
- index_last(): moves cursor to the last key, retrieves the row
- index_first(): moves cursor to the first key, retrieves the row
- index_next_same(): moves cursor to the next duplicate of the current key
- rnd_init(): creates a cursor for the primary index
- rnd_end(): closes the cursor
- rnd_next(): moves cursor to the next key, retrieves the row
- rnd_pos(): moves cursor to a specified row
其他的方法
接下來會提到一些其他重要的或者有趣的方法咙鞍。
rename_table():對表進行重命名(以及它所有的文件)。每當(dāng)你的 schema 改變時都會調(diào)用這個方法趾徽,例如续滋,當(dāng)你增加一列時。MySQL 會將所有的數(shù)據(jù)復(fù)制到一個臨時的表中孵奶,然后使用這個方法將你臨時表中的數(shù)據(jù)重命名到你原始的表中疲酌。
delete_table(): 刪除一個表(以及所有包含的文件)
table_flags(): 返回一個描述了你的 Handler 能力的 flag 集。這些 flag 很多都沒有很好的文檔了袁,并且很多都是針對 InnoDB 生成的文檔朗恳。我的猜測是只有 InnoDB 實現(xiàn)了所有的功能。
index_flags(): 返回一個描述了你的 Handler 能力的 flag 集载绿。例如僻肖,你的 index_prev() 和 index_next() 提供的功能。
總結(jié)
編寫自己的存儲引擎聽起來像一個很復(fù)雜的任務(wù)卢鹦,但是實際上并不是臀脏。你不需要實現(xiàn)我上面說的所有方法劝堪。對于有些方法,MySQL將會找出缺失的實現(xiàn)并且提供自己的方法揉稚,但是對于其他的秒啦,將會有一些報錯。ExampleHandler 本來是空的并且不提供任何功能搀玖。但是你能夠加載并且為它加上斷電余境,一個接一個地實現(xiàn)功能。如果你遇到了問題灌诅,那么你可以參考 MySQL 已有的存儲引擎或者是 MariaDB 芳来。mysql-internals 郵件列表里的開發(fā)者們也能提供一定的幫助。
一些關(guān)于有趣的存儲引擎的想法浮現(xiàn)在我腦海中:
· 一個將數(shù)據(jù)存儲在 HDFS 中的只增數(shù)據(jù)庫猜拾;使用者能夠使用 Spark 或者 Hadoop 的 map/reduce 方法來進行進一步的數(shù)據(jù)處理即舌。
· 基于 std::map 或者 std::multi_map 的存儲中的表。
· 使用 XML 文件后端的存儲引擎挎袜。
你還有什么其他的想法呢顽聂?
License
This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)