新手如何用 VSCode 調(diào)試 MySQL
前幾天看到姜老師的舊文用 VSCode 編譯和調(diào)試 MySQL,每個(gè) DBA 都應(yīng) get 的小技能[1], 文末留了一個(gè)思考題,如何修改源碼,自定義版本,使得 select version() 輸出自定義內(nèi)容
調(diào)試過(guò)程參考macOS VSCode 編譯調(diào)試 MySQL 5.7[2]
內(nèi)部 Item 對(duì)象參考從SQL語(yǔ)句到MySQL內(nèi)部對(duì)象[3]
源碼面前沒(méi)有秘密,建義對(duì) DB 感興趣的嘗試 debug 調(diào)試。本文環(huán)境為 mac + vscode + lldb
依賴(lài)及插件
vscode 插件:
C/C++ C/C++ Clang Command Adapter CodeLLDB CMake Tools
mysql 源碼:
mysql-boost-5.7.35.tar.gz
補(bǔ)?。?code style="font-size: 14px;overflow-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin-right: 2px;margin-left: 2px;color: rgb(30, 107, 184);background-color: rgba(27, 31, 35, 0.05);font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace;word-break: break-all;">MySQL <= 8.0.21 需要對(duì) cmake/mysql_version.cmake 文件打補(bǔ)丁 (沒(méi)有嚴(yán)格測(cè)試所有版本)
tar?-zxf?mysql-boost-5.7.35.tar.gz
cd?mysql-5.7.35
mv?VERSION?MYSQL_VERSION
sed?-i?''?'s|${CMAKE_SOURCE_DIR}/VERSION|${CMAKE_SOURCE_DIR}/MYSQL_VERSION|g'?cmake/mysql_version.cmake
創(chuàng)建 cmake-build-debug 目錄,后續(xù) mysql 編譯結(jié)果,以及啟動(dòng)后生成的文件都在這里
mkdir?-p?cmake-build-debug/{data,etc}
配置 CMake 與編譯
在 mysql 工程目錄下面創(chuàng)建 .vscode/settings.json 文件
{
????"cmake.buildBeforeRun":?true,
????"cmake.buildDirectory":?"${workspaceFolder}/cmake-build-debug/build",
????"cmake.configureSettings":?{
????????"WITH_DEBUG":?"1",
????????"CMAKE_INSTALL_PREFIX":?"${workspaceFolder}/cmake-build-debug",
????????"MYSQL_DATADIR":?"${workspaceFolder}/cmake-build-debug/data",
????????"SYSCONFDIR":?"${workspaceFolder}/cmake-build-debug/etc",
????????"MYSQL_TCP_PORT":?"3307",
????????"MYSQL_UNIX_ADDR":?"${workspaceFolder}/cmake-build-debug/data/mysql-debug.sock",
????????"WITH_BOOST":?"${workspaceFolder}/boost",
????????"DOWNLOAD_BOOST":?"0",
????????"DOWNLOAD_BOOST_TIMEOUT":?"600"
????}
}
內(nèi)容沒(méi)啥好說(shuō)的,都是指定目錄及 boost 配置,其中 WITH_DEBUG 打開(kāi) debug 模式,會(huì)在 /tmp/debug.trace 生成 debug 信息

View -> Command Palette -> CMake: Configure 執(zhí)行后生成 cmake 配置

View -> Command Palette -> CMake: Build 編譯生成最終 mysql 相關(guān)命令

發(fā)現(xiàn)老版本編譯很麻煩,各種報(bào)錯(cuò),mysql 5.7 代碼量遠(yuǎn)超過(guò) 5.5, 只能硬著頭皮看 5.7
初始化數(shù)據(jù)庫(kù)
首先初始化 my.cnf 配置,簡(jiǎn)單的就可以,共它均默認(rèn)
cd?cmake-build-debug
cat?>?etc/my.cnf?<[mysqld]
port=3307
socket=mysql.sock
innodb_file_per_table=1
log_bin?=?on
server-id?=?10086
binlog_format?=?ROW
EOF
初始化數(shù)據(jù)文件,非安全模式,調(diào)試用
./build/sql/mysqld?--defaults-file=etc/my.cnf?--initialize-insecure
tree?-L?1?data
data
├──?auto.cnf
├──?ca-key.pem
├──?ca.pem
├──?client-cert.pem
├──?client-key.pem
├──?ib_buffer_pool
├──?ib_logfile0
├──?ib_logfile1
├──?ibdata1
├──?mysql
├──?on.000001
├──?on.index
├──?performance_schema
├──?private_key.pem
├──?public_key.pem
├──?server-cert.pem
├──?server-key.pem
└──?sys
運(yùn)行 MySQL
由于用 vscode 接管 mysql, 所以需要配置 .vscode/launch.json
{
????//?使用 IntelliSense 了解相關(guān)屬性。?
????//?懸停以查看現(xiàn)有屬性的描述。
????//?欲了解更多信息,請(qǐng)?jiān)L問(wèn):?https://go.microsoft.com/fwlink/?linkid=830387
????"version":?"0.2.0",
????"configurations":?[
????????{
????????????"type":?"lldb",
????????????"request":?"launch",
????????????"name":?"Debug?mysqld",
????????????"program":?"${workspaceFolder}/cmake-build-debug/build/sql/mysqld",
????????????"args":?[
????????????????"--defaults-file=${workspaceFolder}/cmake-build-debug/etc/my.cnf",?"--debug"
????????????],
????????????"cwd":?"${workspaceFolder}"
????????},
????????{
????????????"type":?"lldb",
????????????"request":?"launch",
????????????"name":?"Debug?mysql",
????????????"program":?"${workspaceFolder}/cmake-build-debug/build/client/mysql",
????????????"args":?[
????????????????"-uroot",
????????????????"-P3307",
????????????????"-h127.0.0.1"
????????????],
????????????"cwd":?"${workspaceFolder}"
????????}
????]
}
然后點(diǎn)擊 run and debug mysqld


mysql 啟動(dòng),看到輸出日志無(wú)異常,此時(shí)可以用 mysql-client 連接
mysql?-uroot?-S?./data/mysql.sock
調(diào)試
首先在 sql_parser.cc:5435 處打斷點(diǎn)
void?mysql_parse(THD?*thd,?Parser_state?*parser_state)
mysql_parse 是 sql 處理的入口,至于 tcp connection 連接先可以忽略

mysql>?select?version();
執(zhí)行上述 sql 自動(dòng)跳轉(zhuǎn)到斷點(diǎn)處,Step Into, Step Over, Step Out 這些調(diào)試熟悉下即可
接下來(lái)分別調(diào)用主要函數(shù):mysql_execute_command, execute_sqlcom_select, handle_query, select->join->exec(), Query_result_send::send_data, Item::send, Item_string:val_str, Protocol_text::store, net_send_ok
修改源碼
啟動(dòng) mysql 時(shí) init_common_variables 會(huì)初始化一堆變量,其中會(huì)調(diào)用 set_server_version 生成版本信息,修改這個(gè)就可以
static?void?set_server_version(void)
{
??char?*end=?strxmov(server_version,?MYSQL_SERVER_VERSION,
?????????????????????MYSQL_SERVER_SUFFIX_STR,?NullS);
......
#ifndef?NDEBUG
??if?(!strstr(MYSQL_SERVER_SUFFIX_STR,?"-debug"))
????end=?my_stpcpy(end,?"-dongzerun");
#endif
??if?(opt_general_log?||?opt_slow_log?||?opt_bin_log)
????end=?my_stpcpy(end,?"-log");??????????//?This?may?slow?down?system
......
}
看好條件編譯的是哪塊,修改即可,重新 CMake: Build 編譯再運(yùn)行
mysql>?select?version();
+----------------------+
|?version()????????????|
+----------------------+
|?5.7.35-dongzerun-log?|
+----------------------+
1?row?in?set?(0.00?sec)
Item Class
這里不做過(guò)深分析,簡(jiǎn)單講
select version(),select now()這些簡(jiǎn)單 sql 在 server 層就能得到結(jié)果,無(wú)需進(jìn)入引擎層version(),now()這些函數(shù)在 yacc&lex 詞法解析時(shí)就會(huì)解析成對(duì)應(yīng)的Item類(lèi)最后 mysql 渲染結(jié)果時(shí),就是由 Item::itemize寫(xiě)到 result 中
function_call_generic:
??????????IDENT_sys?'('?opt_udf_expr_list?')'
??????????{
????????????$$=?NEW_PTN?PTI_function_call_generic_ident_sys(@1,?$1,?$3);
??????????}
????????|?ident?'.'?ident?'('?opt_expr_list?')'
??????????{
????????????$$=?NEW_PTN?PTI_function_call_generic_2d(@$,?$1,?$3,?$5);
??????????}
????????;
sql_yacc.cc 函數(shù) PTI_function_call_generic_ident_sys 解析 sql, 識(shí)別出 version() 是一個(gè)函數(shù)調(diào)用
??virtual?bool?itemize(Parse_context?*pc,?Item?**res)
??{
????if?(super::itemize(pc,?res))
??????return?true;
......
????/*
??????Implementation?note:
??????names?are?resolved?with?the?following?order:
??????-?MySQL?native?functions,
??????-?User?Defined?Functions,
??????-?Stored?Functions?(assuming?the?current?
????Create_func?*builder=?find_native_function_builder(thd,?ident);
????if?(builder)
??????*res=?builder->create_func(thd,?ident,?opt_udf_expr_list);
......
????return?*res?==?NULL?||?(*res)->itemize(pc,?res);
??}
find_native_function_builder 查找 hash 表,找到對(duì)應(yīng) version 函數(shù)注冊(cè)的單例工廠函數(shù)
static?Native_func_registry?func_array[]?=
{
??{?{?C_STRING_WITH_LEN("ABS")?},?BUILDER(Create_func_abs)},
??{?{?C_STRING_WITH_LEN("ACOS")?},?BUILDER(Create_func_acos)},
......
??{?{?C_STRING_WITH_LEN("VERSION")?},?BUILDER(Create_func_version)},
......
??{?{0,?0},?NULL}
};
mysql 啟動(dòng)時(shí)調(diào)用 item_create_init 將這些函數(shù) builder 注冊(cè)到 hash 表 native_functions_hash
Create_func_version?Create_func_version::s_singleton;
Item*
Create_func_version::create(THD?*thd)
{
??return?new?(thd->mem_root)?Item_func_version(POS());
}
class?Item_func_version?:?public?Item_static_string_func
{
??typedef?Item_static_string_func?super;
public:
??explicit?Item_func_version(const?POS?&pos)
????:?Item_static_string_func(pos,?NAME_STRING("version()"),
??????????????????????????????server_version,
??????????????????????????????strlen(server_version),
??????????????????????????????system_charset_info,
??????????????????????????????DERIVATION_SYSCONST)
??{}
??virtual?bool?itemize(Parse_context?*pc,?Item?**res);
};
可以看到 Item_func_version 函數(shù)創(chuàng)建時(shí)傳參即為 mysql server_version 版本信息
小結(jié)
MySQL 代碼太龐大,5.1 大約 100w 行,5.5 130w 行,5.7 以后 330w 行,只能挑重點(diǎn)讀源碼。最近很多群里的人在背八股,沒(méi)必要,有那時(shí)間學(xué)著調(diào)試下源碼,讀讀多好
分享知識(shí),長(zhǎng)期輸出價(jià)值,這是我做公眾號(hào)的目標(biāo)。同時(shí)寫(xiě)文章不容易,如果對(duì)大家有所幫助和啟發(fā),請(qǐng)幫忙點(diǎn)擊在看,點(diǎn)贊,分享 三連
參考資料
用 VSCode 編譯和調(diào)試 MySQL,每個(gè) DBA 都應(yīng) get 的小技能: https://www.modb.pro/db/112992,
[2]macOS VSCode 編譯調(diào)試 MySQL 5.7: https://shockerli.net/post/mysql-source-macos-vscode-debug-5-7/,
[3]從SQL語(yǔ)句到MySQL內(nèi)部對(duì)象: https://www.orczhou.com/index.php/2012/11/mysql-innodb-source-code-optimization-1/#21_Item,
推薦閱讀
