新手如何調試 MySQL
前幾天看到姜老師的舊文用 VSCode 編譯和調試 MySQL,每個 DBA 都應 get 的小技能 [1], 文末留了一個思考題,如何修改源碼,自定義版本,使得 select version()
輸出自定義內容
調試過程參考 macOS VSCode 編譯調試 MySQL 5.7[2]
內部 Item
對象參考從 SQL 語句到 MySQL 內部對象 [3]
源碼面前沒有祕密,建義對 DB 感興趣的嘗試 debug 調試。本文環境爲 mac + vscode + lldb
依賴及插件
vscode 插件:
-
C/C++
-
C/C++ Clang Command Adapter
-
CodeLLDB
-
CMake Tools
mysql 源碼:
- mysql-boost-5.7.35.tar.gz
補丁:MySQL <= 8.0.21
需要對 cmake/mysql_version.cmake 文件打補丁 (沒有嚴格測試所有版本)
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
創建 cmake-build-debug
目錄,後續 mysql 編譯結果,以及啓動後生成的文件都在這裏
mkdir -p cmake-build-debug/{data,etc}
配置 CMake 與編譯
在 mysql 工程目錄下面創建 .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"
}
}
內容沒啥好說的,都是指定目錄及 boost 配置,其中 WITH_DEBUG
打開 debug 模式,會在 /tmp/debug.trace 生成 debug 信息
View
-> Command Palette
-> CMake: Configure
執行後生成 cmake 配置
View
-> Command Palette
-> CMake: Build
編譯生成最終 mysql 相關命令
發現老版本編譯很麻煩,各種報錯,mysql 5.7 代碼量遠超過 5.5, 只能硬着頭皮看 5.7
初始化數據庫
首先初始化 my.cnf 配置,簡單的就可以,共它均默認
cd cmake-build-debug
cat > etc/my.cnf <<EOF
[mysqld]
port=3307
socket=mysql.sock
innodb_file_per_table=1
log_bin = on
server-id = 10086
binlog_format = ROW
EOF
初始化數據文件,非安全模式,調試用
./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
運行 MySQL
由於用 vscode 接管 mysql, 所以需要配置 .vscode/launch.json
{
// 使用 IntelliSense 瞭解相關屬性。
// 懸停以查看現有屬性的描述。
// 欲瞭解更多信息,請訪問: 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}"
}
]
}
然後點擊 run and debug mysqld
mysql 啓動,看到輸出日誌無異常,此時可以用 mysql-client 連接
mysql -uroot -S ./data/mysql.sock
調試
首先在 sql_parser.cc:5435 處打斷點
void mysql_parse(THD *thd, Parser_state *parser_state)
mysql_parse
是 sql 處理的入口,至於 tcp connection 連接先可以忽略
mysql> select version();
執行上述 sql 自動跳轉到斷點處,Step Into
, Step Over
, Step Out
這些調試熟悉下即可
接下來分別調用主要函數: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
修改源碼
啓動 mysql 時 init_common_variables
會初始化一堆變量,其中會調用 set_server_version
生成版本信息,修改這個就可以
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
編譯再運行
mysql> select version();
+----------------------+
| version() |
+----------------------+
| 5.7.35-dongzerun-log |
+----------------------+
1 row in set (0.00 sec)
Item Class
這裏不做過深分析,簡單講
-
select version()
,select now()
這些簡單 sql 在 server 層就能得到結果,無需進入引擎層 -
version()
,now()
這些函數在 yacc&lex 詞法解析時就會解析成對應的Item
類 -
最後 mysql 渲染結果時,就是由
Item::itemize
寫到 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
函數 PTI_function_call_generic_ident_sys
解析 sql, 識別出 version()
是一個函數調用
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 <use> database)
This will be revised with WL#2128 (SQL PATH)
*/
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 表,找到對應 version
函數註冊的單例工廠函數
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 啓動時調用 item_create_init
將這些函數 builder 註冊到 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
函數創建時傳參即爲 mysql server_version
版本信息
董澤潤的技術筆記 十一年 IT 老兵,運維架構好把式,長期分享乾貨
小結
MySQL 代碼太龐大,5.1 大約 100w 行,5.5 130w 行,5.7 以後 330w 行,只能挑重點讀源碼。最近很多羣裏的人在背八股,沒必要,有那時間學着調試下源碼,讀讀多好
參考資料
[1]
用 VSCode 編譯和調試 MySQL,每個 DBA 都應 get 的小技能: https://www.modb.pro/db/112992,
[2]
macOS VSCode 編譯調試 MySQL 5.7: https://shockerli.net/post/mysql-source-macos-vscode-debug-5-7/,
[3]
從 SQL 語句到 MySQL 內部對象: https://www.orczhou.com/index.php/2012/11/mysql-innodb-source-code-optimization-1/#21_Item,
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/lJqb0kMtnAUmqUIWCShkIQ