原文: Writing a MySQL storage engine from scratch
本文主要描述了如何寫一個MySQL 存儲引擎 - 一個可以持久保存 MySQL 表數據的插件
簡介
這篇文章總結了我在寫一個新的 MySQL 存儲引擎時學到的一些事情茂契。MySQL 的存儲引擎是一個能夠提供 在磁盤中存儲數據,并且能夠提供對數據的鏈接的插件篇梭。存儲引擎通常被作為 key/value 數據庫實現镐确,但是并不一定是這樣性穿。Oracle 的 MySQL 提供了多種存儲引擎结闸,并且所有的存儲引擎都能夠被重新編寫佣渴。默認的存儲引擎是 InnoDB剥险,一種 key/value 庫透硝。其他的存儲引擎有的能夠寫入 csv 文件(‘Tina’)或者專門用來歸檔數據('archive').Maria DB狰闪,作為一個分支,是一個更加先進并且提供了 Cassandra NoSQL 數據庫接口的存儲引擎濒生,Percona 的 TokuDB key/value 庫 和 xtradb埋泵,則是 InnoDB 的改進版本。
總的來說罪治,MySQL 有13種存儲引擎丽声,MariaDB 有大約20 種。但是這些仍然不夠觉义,讓我們再來添加一種雁社。
下載源碼以及第一次接觸
你能夠在 GitHub 上面下載 MySQL 的源碼,我的工作版本是 5.7.12晒骇。
git clone https://github.com/mysql/mysql-server.git
cd mysql-server
git checkout mysql-5.7-12
讓我們簡單看一下代碼的整體結構霉撵。文件和目錄的數量有點多,但是大部分和我們的工作無關洪囤,只有少部分的目錄值得我們仔細查看:
include 包含全局 include 文件徒坡,你能在里面找到 mysql.h,其中包含了很多重要的宏和聲明箍鼓。
sql 保護了 sql 相關的代碼:解析器,查詢引擎等呵曹。
sql/field.h Field class 描述了一個 MySQL 行款咖。
sql/item.h Item class 是一個解析器生成的抽象語法樹的節(jié)點。
sql/sql_*.cc 這些文件實現了實際上的 SQL 操作奄喂。
sql/handler.h 存儲引擎的基類
storage 這個目錄包含了所有的存儲引擎铐殃。
storage/innobase InnoDB 存儲引擎
storage/example 一個存儲引擎實例項目。
通過瀏覽這些代碼跨新,我們可以清晰地看出 mysql 代碼的基礎是相對較老的富腊。雖然大部分的代碼是通過 C++ 實現的,但是實際上僅僅是 “c 和 類”的實現方式域帐。更多的高級 c++ 特性赘被,例如 模板是整,exceptions 或者標準庫都沒有被使用。連接列表被實現成包裹在 "void *" 中的結構體對象(my_list.h)民假。mysql 還有它自己的 string 實現(string.c) 而不是使用 std::string浮入。RAII 也沒有被使用。更甚的是羊异,你能夠找到很多用于清理內存分配資源的 goto 語句事秀。
功能的實現會非常的長(超過 1000 行代碼,并且包含了多層的 if 語句 mysql_prepare_create_table)野舶,并且有一些類也非常的大易迹。很多方法被存儲在基類中而不是繼承類,并且類中存儲了很多的狀態(tài)平道。
MySQL 用了幾種內存自動分配(memroot_allocator.h)睹欲,它們不是標準的實現。很多類使用自己的內存分配來重寫了操作符 new 和 delete巢掺。
同時MySQL 的代碼沒有統一的編碼風格句伶。首行縮進都有多種風格,結構體的命名風格也有多種方式(陆淀。考余。。)轧苫,空格的使用也非常隨意(楚堤。。含懊。)身冬。
我沒有和 Oracle 或者 Mysql AB 工作的人討論過,但是我們可以基于這些代碼看出他們的合作風格岔乔。缺乏同一的代碼風格意味著每一位工程師都能選擇他最熟悉的方式酥筝。代碼風格有很大的差異,甚至在同一個文件中雏门,每個人也可以對任意部分的代碼進行修改嘿歌。我曾經在做過類似的工作,所以我明白這其中有非常多的工作需要做茁影。
如果 MySQL希望吸引新的開發(fā)者宙帝,“c 加 類”的使用,不使用標準庫并且非常長且復雜的功能實現都是需要重構的募闲。
編譯步脓,安裝,運行,debug
讓我們繼續(xù)安裝靴患。如果你運行了之前的步驟仍侥,那么接下來你需要檢查 mysql-server 目錄中的代碼。我們將在這個目錄中運行 cmake 來生成 Makefile蚁廓。我們同時將會創(chuàng)建一個 Debug build访圃,雖然它會有一點慢,但是在 debug 的過程中我們會用到它相嵌。
cd mysql-server
cmake -DCMAKE_BUILD_TYPE=Debug
make -j 5
sudo make install
你的編譯文件現在安裝在了 /usr/local/mysql 中腿时。你能通過在子目錄中運行 cmake 來生成獨立的 Debug 和 Release builds,并且你能夠選擇不同的安裝目錄饭宾,運行 “cmake --help”來獲得選項的列表批糟。在這個文章中我僅僅會保持所有事情盡可能簡單。
為了成功啟動 MySQL 客戶端看铆,我們還需要做以下事情徽鼎。我們需要創(chuàng)建一個數據目錄(用來存放 表)并且初始化它們(注意你可能需要改變下面代碼的文件路徑):
mkdir /home/alan/tmp/mysql-data # must be empty
cd /usr/local/mysql/bin
./mysqld --initialize --datadir=/home/alan/tmp/mysql-data
最后一個命令將會生成一個 root 密碼。記下它弹惦,接下來會用到否淤。我將我的開發(fā)環(huán)境設置為空的 root 密碼 - 在測試時它將會更加安全。你可以使用接下來的命令重新設置密碼(用上面生成的 root 密碼來替換 <PASSWORD>):
-- start the server
./mysqld --datadir=/home/alan/tmp/mysql-data
-- in a separate terminal we can now change the password
./mysqladmin password --user=root --password=<password>
</password>
然后我們可以創(chuàng)建一個新的數據庫:
./mysqladmin create test --user=root --password
客戶端能夠被正常啟動棠隐,并且之后你將可以創(chuàng)建表石抡,插入數據等等。
./mysql --user=root test
逐步構建一個新的存儲引擎
存儲引擎被實現為一個動態(tài)的庫(一個 .so 文件)助泽,并且他的源文件被存儲在 msyql-server/storage 目錄中啰扛。如果你僅僅希望玩一下那么你可以修改已有的 'example ' 存儲引擎。我選擇通過以下步驟創(chuàng)建我自己的 'upscaledb'嗡贺。
cd mysql-server/storage
-- copy the 'ha_example' directory to 'ha_upscaledb'
cp -r ha_example ha_upscaledb
-- rename the files
cd ha_upscaledb
mv ha_example.h ha_upscaledb.h
mv ha_example.cc ha_upscale.cc
最有一步就是隐解,使用你最喜歡的 IDE 來將 'example' 替換為 'upscaledb'以及 'EXAMPLE' 替換為 'UPSCALEDB'。不要忘記同樣更改你的 CMakeLists.txt 诫睬。然后進入 mysql-server 的根目錄并且再一次運行 'cmake' 和 ‘make’ 命令煞茫。新的 upscaledb 引擎現在構建好了,它的文件名是 mysql-server/storage/upscaledb/ha_upscaledb.so摄凡。
現在我們需要提示 MySQL 關于我們的新存儲引擎续徽。首先我們創(chuàng)建一個 安裝目錄 到新的 .so 文件的 symbolic link(軟連接?)架谎。通過這個連接我們的服務器將會總是使用最新版本的 .so 文件炸宵。不論我們何時修改了代碼辟躏,我們只需要簡單的重新編譯存儲引擎并且重啟 MySQL 服務器就行了谷扣。
cd /usr/local/mysql/lib/plugin
sudo ln -s ~/prj/msyql-server/storage/upscaledb/ha_upscaledb.so ha_upscaledb.so
最后的步驟就是更新 MySQL 的內部系統表。我們能夠使用 MySQL 的客戶端完成這些。同時確保 MySQL 的服務器端仍在運行会涎。
cd /usr/local/mysql/bin
./mysql --user=root test
mysql> INSTALL PLUGIN upscaledb SONAME 'ha_upscaledb.so';
現在你能夠使用最新的存儲引擎并且嘗試構建表 (create table test(value INTEGER) ENGINE=upscaledb;)」祝現在我們的存儲引擎只是一個框架并且沒有任何的實現。我們將會得到一個報錯末秃。在我們開始增添程序邏輯之前我會教你如何 Debug MySQL 服務器概页。接下來的代碼將會在 gdb 中啟動服務器,并且將會讓 gdb 獲取所有的信號练慕。(i.e. ctrl-c 可以結束 debugger)惰匙。
cd /usr/local/mysql/bin
gdb --args ./mysqld --gdb --datadir=/home/alan/tmp/mysql-data
嘗試在你的存儲引擎中的 'create()' 方法中設置一個斷點,并且運行一次上述的 CREATE TABLE 命令铃将。