新手如何除錯 MySQL
前幾天看到姜老師的舊文 用 VSCode 編譯和除錯 MySQL,每個 DBA 都應 get 的小技能 , 文末留了一個思考題,如何修改原始碼,自定義版本,使得 select version()
輸出自定義內容
除錯過程參考 macOS VSCode 編譯除錯 MySQL 5.7
內部 Item
物件參考 從SQL語句到MySQL內部物件
原始碼面前沒有祕密,建義對 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 瞭解相關屬性。 // 懸停以檢視現有屬性的描述。 // 欲瞭解更多資訊,請訪問: http://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
版本資訊
小結
MySQL 程式碼太龐大,5.1 大約 100w 行,5.5 130w 行,5.7 以後 330w 行,只能挑重點讀原始碼。最近很多群裡的人在背八股,沒必要,有那時間學著除錯下原始碼,讀讀多好
分享知識,長期輸出價值,這是我做公眾號的目標。同時寫文章不容易,如果對大家有所幫助和啟發,請幫忙點選 在看
, 點贊
, 分享
三連
- 新手如何除錯 MySQL
- 為什麼泛型使你的程式變慢
- 每個 gopher 都需要了解的 Go AST
- 體驗 http3: 基於 nginx quic 分支
- 如何應對不斷膨脹的介面
- Gopher 需要知道的幾個結構體騷操作
- 聊聊為什麼 IDL 只能擴充套件欄位而非修改
- 閱讀 redis 原始碼,學習快取淘汰演算法 W-TinyLFU
- Go 操作 Kafka 如何保證無訊息丟失
- 強烈推薦| 郝大分享 GraphQL 實踐的那些經歷
- 實踐出真知,聊聊 HTTP 鑑權那些事
- 做業務真的沒有技術含量嘛?不想做 crud boy 的可以好好讀讀
- 你真的瞭解 CDC 嘛
- 藉助 Pod 刪除事件的傳播實現 Pod 摘流
- Go timer 是如何被排程的?
- Go Context 最佳實踐
- PingCAP 故障注入利器 fail-rs
- Fail at Scale 讀後感
- 聊聊 rust trait
- 你真的瞭解 JWT 嘛