MariaDB 源碼調試
MariaDB 源碼編譯[root@jg-72 source]# pwd
/data/source
[root@jg-72 source]# ls
mariadb-10.1.11.tar.gz
先將源碼壓縮包解壓縮
tar -zxvf mariadb-10.1.11.tar.gz
進入到BUILD子目錄,它已經提供了一些一鍵編譯的腳本
cd mariadb-10.1.11/BUILD
選擇執行 compile-amd64-debug-all 腳本,因為我們要編譯X86_64平台上帶DEBUG調試信息的mysql server。
[root@jg-72 BUILD]# ./compile-amd64-debug-all
You must run this script from the MySQL top-level directory
cd mariadb-10.1.11
BUILD/compile-amd64-debug-all
靜靜等待編譯結束,大概幾分鍾
可以看到編譯生成的mysqld 文件大小 261 M,而實際生產環境中使用的不帶調試信息的mysqld文件,隻有約89M。
client 和 extra目錄下的可執行文件是編譯生成的mysql自帶的工具集,這裏我們隻關注mysqld。
使用編譯的mysqld啟動mysql實例
最簡單的方法,在已安裝好的mariadb的安裝目錄下,把mysqld用編譯出來的版本替換掉即可。
但是我們不想破壞已經安裝好的用於生產的MariaDB,所以這裏我們在它的源碼目錄下構建屬於它自己的mysql 基礎目錄(mysql basedir)
實際上就是參照安裝的mysql的目錄結構組織一下文件即可
把編譯生成的可執行文件都copy到創建的bin目錄下
(下麵所有操作都是在MariaDB的源代碼根目錄下)
[root@jg-72 mariadb-10.1.11]# mkdir bin
cp sql/mysqld bin/
cp scripts/mysqld_safe bin/
cp sql/{add_errmsg,gen_lex_hash,gen_lex_token,mysql_tzinfo_to_sql} bin/
cp client/{async_example,mysqlbinlog,mysql,mysqladmin,mysqlcheck,mysqldump,mysql_plugin,mysqlslap,mysql_upgrade,mysqltest,mysqlshow,mysqlimport} bin/
cp extra/{comp_err,my_print_defaults,mysql_waitpid,perror,replace,resolveip,resolve_stack_dump} bin/
cp -r sql/share/ .
cp scripts/*.sql share/
創建一個 my.cnf 文件, 其中basedir 設置為MariaDB源碼的根目錄
[mysqld]
user=mysql
port = 3310
basedir = /data/source/mariadb-10.1.11
socket = /data/source/3310/mysql.sock
datadir = /data/source/3310/data
log-error = /data/source/3310/mysqld.err
pid-file = /data/source/3310/mysqld.pid
character-set-server=utf8
… …
然後,初始化MySQL係統數據庫,可以看到,它使用我們編譯的mysqld來做初始化
[root@jg-72mariadb-10.1.11]# scripts/mysql_install_db –defaults-file=/data/source/3310/my.cnf –user=mysql
Installing MariaDB/MySQL system tables in ‘/data/source/3310/data’ …
2016-02-25 0:58:35 139708455159584 [Note] /data/source/mariadb-10.1.11/bin/mysqld (mysqld 10.1.11-MariaDB-debug) starting as process 21624 …
OK
Filling help tables…
2016-02-25 0:59:00 140501225490208 [Note] /data/source/mariadb-10.1.11/bin/mysqld (mysqld 10.1.11-MariaDB-debug) starting as process 21664 …
OK
Creating OpenGIS required SP-s…
2016-02-25 0:59:07 140226519934752 [Note] /data/source/mariadb-10.1.11/bin/mysqld (mysqld 10.1.11-MariaDB-debug) starting as process 21703 …
OK
… …
啟動實例:
可以看到是使用我們編譯的mysqld啟動的
[root@jg-72mariadb-10.1.11]# bin/mysqld_safe –defaults-file=/data/source/3310/my.cnf –user=mysql &
[1] 21808
[root@jg-72 mariadb-10.1.11]# ps -ef|grep mysql |grep 3310
root 21808 19360 0 01:04 pts/5 00:00:00 /bin/sh bin/mysqld_safe –defaults-file=/data/source/3310/my.cnf –user=mysql
mysql 22096 21808 28 01:04 pts/5 00:00:03 /data/source/mariadb-10.1.11/bin/mysqld –defaults-file=/data/source/3310/my.cnf –basedir=/data/source/mariadb-10.1.11 –datadir=/data/source/3310/data –plugin-dir=/usr/local/mysql/lib/plugin –user=mysql –log-error=/data/source/3310/mysqld.err –pid-file=/data/source/3310/mysqld.pid –socket=/data/source/3310/mysql.sock –port=3310
使用gdb調試mysql進程
gdb 連接到要調試的進程的命令很簡單,上麵知道這個進程的id是22096
命令: gdb – 22096
Loaded symbols for /usr/lib64/libltdl.so.7
Reading symbols from /lib64/libfreebl3.so…(no debugging symbols found)…done.
Loaded symbols for /lib64/libfreebl3.so
Reading symbols from /lib64/libnss_files.so.2…(no debugging symbols found)…done.
Loaded symbols for /lib64/libnss_files.so.2
0x0000003893adf1b3 in poll () from /lib64/libc.so.6
Missing separate debuginfos, use: debuginfo-install bzip2-libs-1.0.5-7.el6_0.x86_64 glibc-2.12-1.149.el6.x86_64 libaio-0.3.107-10.el6.x86_64 libgcc-4.4.7-11.el6.x86_64 libstdc++-4.4.7-11.el6.x86_64 libtool-ltdl-2.2.6-15.5.el6.x86_64 libxml2-2.7.6-14.el6_5.2.x86_64 nss-softokn-freebl-3.14.3-17.el6.x86_64 snappy-1.1.0-1.el6.x86_64 unixODBC-2.2.14-14.el6.x86_64 xz-libs-4.999.9-0.5.beta.20091007git.el6.x86_64 zlib-1.2.3-29.el6.x86_64
(gdb)
最後會看到如上的輸出,此時這個mysql進程已經被gdb掛起,用客戶端連接是沒有響應的。
設置函數斷點,這樣當mysql進程執行到這個函數的時候,就會被gdb捕獲到並且停在函數的入口處。
(gdb) b dict_index_too_big_for_tree
Breakpoint 1 at 0xdd291b: file /data/source/mariadb-10.1.11/storage/xtradb/dict/dict0dict.cc, line 2390.
(gdb)
輸入c 命令,讓程序正常運行
(gdb) c
Continuing.
此時在另一個終端使用mysql客戶端進行連接
[root@jg-72 mariadb-10.1.11]# ./bin/mysql -uroot -h168.168.207.72 -P3310
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 2
Server version: 10.1.11-MariaDB-debug Source distribution
Copyright (c) 2000, 2015, Oracle, MariaDB Corporation Ab and others.
Type ‘help;’ or ‘\h’ for help. Type ‘\c’ to clear the current input statement.
MariaDB [(none)]>
我們可以正常的執行一些SQL
MariaDB [(none)]> use test;
Database changed
MariaDB [test]> show variables like ‘char%';
+————————–+———————————————-+
| Variable_name | Value |
+————————–+———————————————-+
| character_set_client | utf8 |
| character_set_connection | utf8 |
| character_set_database | utf8 |
| character_set_filesystem | binary |
| character_set_results | utf8 |
| character_set_server | utf8 |
| character_set_system | utf8 |
| character_sets_dir | /data/source/mariadb-10.1.11/share/charsets/ |
+————————–+———————————————-+
8 rows in set (0.00 sec)
嚐試創建一個表,發現hang住了,
MariaDB [test]> create table testtable ( c1 varchar(100), c2 varchar(100), c3 varchar(100));
切換到gdb所在的終端,
可以看到,gdb在設置斷點的函數的入口處停了一下,等待我們單步調試
L 命令可以看接下來10行的代碼,後麵可以跟數字
l 100就是看100行代碼, l -10 就是看上麵10行的代碼
想看某個變量的值,使用 p 命令(print)
在執行到某函數調用處,比如上圖的2398行,也可以使用 s (step)命令,這個時候會進入被調用的函數dict_table_is_comp()內部,繼續單步執行
comp = 1這說明,當前創建的表的格式是 compact的。
我們這個表有3個nullable的列,每條記錄的長度限製,page_rec_max 是 8126,是通過
2425行的函數計算出來的。
接下來進入計算每一個字段占用空間的計算。這裏我們看到,
這個表總共有 6 個 fields,而不是我們定義的三個,這是因為,mysql隱含的會添加三個字段
- Records in the clustered index contain fields for all user-defined columns. In addition, there is a 6-byte transaction ID field and a 7-byte roll pointer field.
- If no primary key was defined for a table, each clustered index record also contains a 6-byte row ID field.
我們用 p 看第一個field的信息,確實是 ROW_ID,長度確實是6 byte,和 文檔互相印證。
對於隱含列的處理,我們可能不關心,所以想直接跳過接下來的代碼,那可以設置一個行斷點,
b 命令不加參數就是在當前行設置斷點, b 就是在當前文件的指定行設置行斷點, b : 就是在指定文件的指定行設置行斷點
接下來 continue,我們發現它運行到 2451行後再次停下,說明循環進入了第二次迭代(i已經變成1)
可以看到,第二個字段也是默認添加的 TRX_ID 列,長度是6個字節,
第三個字段是默認添加的ROLL_PTR列,長度為7字節
可以預期,接下來就是我們建表的真實的字段信息。
如果想看當前程序的函數調用棧,可以使用 bt 或者where 命令
Detach命令使gdb釋放對mysql進程的連接,quit命令退出gdb
如何從MySQL的一條報錯信息定位到源代碼
以下麵這個問題舉例:
297個字段 varchar(73)字段的表
create table t_f73_ag_xjllb_yh
(
jydm VARCHAR(73),
rq VARCHAR(73),
cb VARCHAR(73),
khdkjdkjjse VARCHAR(73),
xyhjkjzje VARCHAR(73),
… …
xjdjwdqmye_yoy VARCHAR(73),
jxjdjwdqcye_yoy VARCHAR(73),
xjjxjdjwjzje1_yoy VARCHAR(73)
);
報如下錯誤
ERROR 1118 (42000): Row size too large (> 8126). Changing some columns to TEXT or BLOB or using ROW_FORMAT=DYNAMIC or ROW_FORMAT=COMPRESSED may help. In current row format, BLOB prefix of 768 bytes is stored inline.
在源碼根目錄搜索消息的部分內容
[root@jg-72 mariadb-10.1.11]# find * |xargs grep ‘Row size too large’
輸出很多,但是我們應該重點關注源代碼和文本文件,所以
share/errmsg-utf8.txt sql/share/errmsg-utf8.txt
storage/innobase/handler/ha_innodb.cc
storage/xtradb/handler/ha_innodb.cc
這幾個文件需要重點關注
因為MariaDB的INNODB存儲引擎實際上是xtraDB,所以先看
繼續尋找這個函數調用的地方
jydm VARCHAR(73),
rq VARCHAR(73),
cb VARCHAR(73),
khdkjdkjjse VARCHAR(73),
xyhjkjzje VARCHAR(73),
… …
xjdjwdqmye_yoy VARCHAR(73),
jxjdjwdqcye_yoy VARCHAR(73),
xjjxjdjwjzje1_yoy VARCHAR(73)
);
報如下錯誤
ERROR 1118 (42000): Row size too large (> 8126). Changing some columns to TEXT or BLOB or using ROW_FORMAT=DYNAMIC or ROW_FORMAT=COMPRESSED may help. In current row format, BLOB prefix of 768 bytes is stored inline.
在源碼根目錄搜索消息的部分內容
[root@jg-72 mariadb-10.1.11]# find * |xargs grep ‘Row size too large’
輸出很多,但是我們應該重點關注源代碼和文本文件,所以
share/errmsg-utf8.txt sql/share/errmsg-utf8.txt
storage/innobase/handler/ha_innodb.cc
storage/xtradb/handler/ha_innodb.cc
因為MariaDB的INNODB存儲引擎實際上是xtraDB,所以先看
storage/xtradb/handler/ha_innodb.cc
發現兩個關鍵的宏定義 ER_TOO_BIG_ROWSIZE 和DB_TOO_BIG_RECORD
從errmsg-utf8.txt文件的內容ER_TOO_BIG_ROWSIZE 42000 和錯誤消息頭 1118 (42000),可以推測出ER_TOO_BIG_ROWSIZE 應該就是錯誤消息號 1118,但是errmsg-utf.txt中的1118錯誤的內容,不包含 (> xxx),進一步發現,包含這個錯誤內容的隻有上麵的 ha_innodb.cc的2058行, 函數是 convert_error_code_to_mysql()
繼續尋找這個函數調用的地方
find * |xargs grep ‘convert_error_code_to_mysql’
發現,全部都在ha_innodb.cc 文件中(大概有幾十處調用)
通過遍曆這個文件調用這個函數的地方,可以看到很多地方,error號都是通過
row_create_index_for_mysql()函數的返回值得到,我們更是找到一個函數
這裏,首先需要知道的一個概念
從文檔中可以知道,InnoDB的表是一種索引組織表,也就是說,它的表實際上就是索引(clustered index),他的索引也就是表,它的clustered index實際上包含了所有用戶定義的字段。
另外,可以在表的選定的字段上創建二級索引,如果表中定義了主鍵,那麼二級索引將隱含的包括主鍵這一字段。
The data in each InnoDB table is divided into pages. The pages that make up each table are arranged in a tree data structure called a B-tree index. Table data and secondary indexes both use this type of structure. The B-tree index that represents an entire table is known as the clustered index, which is organized according to the primary key columns. The nodes of the index data structure contain the values of all the columns in that row (for the clustered index) or the index columns and the primary key columns (for secondary indexes).
所以,基本可以斷定,這個函數create_clustered_index_when_no_primary()實際上就是我們創建一個不包含主鍵的InnoDB表主要要調用的函數。而它的主要工作,在
row_create_index_for_mysql()中完成。
同樣的方式,在storage/innobase/row/row0mysql.cc中找到 row_create_index_for_mysql() 的定義
通過大概瀏覽代碼知道這個函數會調用que_run_threads()去做實際的工作。
找到它的定義處 storage/xtradb/que/que0que.cc 1183,發現它實際調用的是
que_run_threads_low(), 這個也定義在相同的文件裏,進入這個函數
在同一個文件裏,我們也找到que_thr_step()函數的定義,實際上可以推測出,它裏麵會根據不同的語句調用不同的入口函數
很幸運,它用了老土的 if else條件判斷,而不是函數指針,所以可以知道調用了
dict_create_index_step()
這裏也可能是dict_create_table_step()呀? 一方麵,我們是從create_index一路跟下來的,
另一方麵,不放心的話,搜一下 QUE_NODE_CREATE_INDEX這個type,
發現是在storage/xtradb/dict/dict0crea.cc ind_create_graph_create()函數中賦值的,而這個函數,被 row_create_index_for_mysql()在調用que_run_threads()之前調用了,所以,可以確定,接下來執行的函數是dict_create_index_step()
在storage/innobase/dict/dict0crea.cc 中找到這個函數的定義
這個時候發現這個函數真TM長,分了好幾個階段,
它調用的函數有
dict_build_index_def_step()
dict_build_field_def_step()
dict_index_add_to_cache()
dict_index_get_if_in_cache_low()
dict_create_index_tree_step()
一個個函數跟進去相當於嚐試好幾條岔路,一般這種情況,每個函數都跟進去會掉進無限的調用陷阱裏,顯然不是個好辦法。
於是回到最開始,想辦法自底向上確定函數調用棧,之前是查找ER_TOO_BIG_ROWSIZE從上往下找,現在查找 DB_TOO_BIG_RECORD 這個關鍵字
重點關注其中返回 DB_TOO_BIG_RECORD的函數
在storage/xtradb/btr/btr0cur.cc 文件中,幾處返回DB_TOO_BIG_RECORD的函數從名字看不是update就是insert,顯然不是我們的create table
於是在storage/xtradb/dict/dict0dict.cc 中,發現返回DB_TOO_BIG_RECORD的函數
恰好是dict_index_add_to_cache(), 是被dict_create_index_step()調用的函數之一。
到此,基本可以確定,最後通過函數dict_index_too_big_for_tree()來檢查創建一個innodb表是否會超出innodb表的一些長度限製。
通過前麵的gdb調試中的bt命令,也可以印證這一點,可以看到函數調用的堆棧
Create innodb table without primary key …
->create_clustered_index_when_no_primary()
->row_create_index_for_mysql()
->que_run_threads()
->que_run_threads_low()
->que_thr_step()
->dict_create_index_step()
->dict_index_add_to_cache()
->dict_index_too_big_for_tree()
Tip:
當然,這個查找過程可以不通過命令行,也可以使用一些工具,比如sourceinsight把整個mariadb的源代碼導入,然後在sourceinsight裏執行類似的搜索過程。Source Insight的安裝使用這裏就不說了。
最後更新:2017-05-19 17:02:36
上一篇:
《Apache Zookeeper 官方文檔》-4 ZooKeeper編程指南
下一篇:
《雲數據管理:挑戰與機遇》分布式數據管理
Spring-Bean的銷毀使用destroy-method()方法無效解決方案(容器!附源碼)
在紐約,與世界握手
CCAI | 如何能既便宜又快速地獲取大數據?這位微軟研究員設計了兩個模型,幫你省錢省時間
響應式網站優化的主要環節
CareerCup之1.1字符串中字符判重
Day5---D4:合規和審計管理
obj-c編程10:Foundation庫中類的使用(6)[線程和操作隊列]
PostgreSQL flashback(閃回) 功能實現與介紹
10 Must-Know Topics for Software Architects in 2009
poj 2090 Two-Stacks Solitaire