背景
以前我都是通过 MySQL 官方文档来学习 MySQL 的相关知识;入行久了之后发现有些问题在官方文档上是找不到答案的。如果想更进一步,就只能是学习源代码了。君子善假于物,有一些小工具可以让我们的整个学习过程平滑不少。下面我就来深入介绍一下。
工具一 GDB
MySQL 源代码压缩后都有几百MB ,文件数量又是个天文数字。我们又怎么确认数据库服务端的第一行代码在哪里呢?
这个问题对于 gdb 来讲就是 p !!! 理由有两个 1. gdb 可以根据函数名来设置断点, 2. MySQL 是一个 C 程序它的入口函数是 main 。也就是说我们只要在 main 函数上打断点,就能直接找到 MySQL 服务端启动后的第一行代码了。
1.1 用 gdb 拉起 MySQL
代码语言:javascript复制gdb --args /usr/local/mysql/bin/mysqld --defaults-file=/usr/local/mysql/my.cnf
1.2 在 main 函数上打上断点,这个时候 gdb 会自动打印 main 函数所在的位置
代码语言:javascript复制(gdb) b main
Breakpoint 1 at 0x3326cb5:
file /data/repos/mysql-server-mysql-8.0.32/sql/main.cc, line 25.
可以看到 gdb 告诉我们 main 函数在 main.cc 的第 25 行处。
工具二 bpftrace
用 gdb 来分析 MySQL 的启动流程一点问题都没有,如果是想用它来分析特定 SQL 的执行流程,那我们就会发现自己做了太多的无用功。这个场景 bpftrace 就会非常直接。
要用 bpftrace 来确定 SQL 的执行流程,技术上讲是要拿到 MySQL 执行过程中的函数堆栈;也就是说我们只需要在 MySQL 发送执行结果(基本执行完成)的时候,打印一下当前线程的堆栈。在真正定代码之前我们还要补充另外一个知识,就是 send_result_set_row 函数负责发送数据给客户端。
2.1 确认 send_result_set_row 函数的探针名
代码语言:javascript复制bpftrace -l 'uprobe:/usr/local/mysql/bin/mysqld:*send_result_set_row*'
uprobe:/usr/local/mysql/bin/mysqld:_ZN3THD19send_result_set_rowERK14mem_root_dequeIP4ItemE
2.2 有了探针就可以给这个探针加 “监控” 了,专业一点叫追踪。下面的代码可以让我们打印 send_result_set_row 函数的堆栈。
代码语言:javascript复制#!/usr/bin/env bpftrace
/**
MySQL 前端线程堆栈追踪
作者: 蒋乐兴|neeky
时间: 2022-05
*/
BEGIN
{
print("MySQL 前端线程堆栈追踪 n")
}
uprobe:/usr/local/mysql/bin/mysqld:_ZN3THD19send_result_set_rowERK14mem_root_dequeIP4ItemE
{
printf("%sn", ustack(perf));
}
END
{
print("exit n")
}
2.3 启动追踪程序
代码语言:javascript复制bpftrace trace-mysql-thread-stack.bt
Attaching 3 probes...
MySQL 前端线程堆栈追踪
2.4 执行一条 select 观察语句,用来观察 select 的执行流程
代码语言:javascript复制mysql> select user,host from mysql.user limit 1;
------------------ -----------
| user | host |
------------------ -----------
| mysql.infoschema | localhost |
------------------ -----------
1 row in set (0.00 sec)
2.5 这个时候我们的 bpftace 程序就能看到完整的执行流程了
代码语言:javascript复制3419e3e THD::send_result_set_row(mem_root_deque<Item*> const&) 0 (/usr/local/mysql-8.0.32/bin/mysqld)
36327e8 Query_expression::ExecuteIteratorQuery(THD*) 1648 (/usr/local/mysql-8.0.32/bin/mysqld)
3632a36 Query_expression::execute(THD*) 252 (/usr/local/mysql-8.0.32/bin/mysqld)
358199c Sql_cmd_dml::execute_inner(THD*) 206 (/usr/local/mysql-8.0.32/bin/mysqld)
3580f06 Sql_cmd_dml::execute(THD*) 1524 (/usr/local/mysql-8.0.32/bin/mysqld)
34ff4cc mysql_execute_command(THD*, bool) 21683 (/usr/local/mysql-8.0.32/bin/mysqld)
35016b3 dispatch_sql_command(THD*, Parser_state*) 1878 (/usr/local/mysql-8.0.32/bin/mysqld)
34f7609 dispatch_command(THD*, COM_DATA const*, enum_server_command) 5544 (/usr/local/mysql-8.0.32/bin/mysqld)
34f569a do_command(THD*) 1469 (/usr/local/mysql-8.0.32/bin/mysqld)
3714f7b handle_connection 479 (/usr/local/mysql-8.0.32/bin/mysqld)
5785c05 pfs_spawn_thread 336 (/usr/local/mysql-8.0.32/bin/mysqld)
7f370d88daaf start_thread 671 (/usr/lib64/libc.so.6)
bpftrace 实时追踪
让我们加大点难度,把 SQL 语句与当时的堆栈都拿出来,时间点也可以带出来这里我留白吧。
2.1 编写 bpftrace 追踪代码
代码语言:javascript复制#!/usr/bin/env bpftrace
/**
追踪前端线程执行的 SQL 语句与错误
作者: 蒋乐兴|neeky
时间: 2022-05
*/
/* 引入头文件 */
struct PS_PARAM {
unsigned char null_bit;
unsigned char unsigned_type;
const unsigned char *value;
unsigned long length;
const unsigned char *name;
unsigned long name_length;
};
struct COM_QUERY_DATA {
const char *query;
unsigned int length;
struct PS_PARAM *parameters;
unsigned long parameter_count;
};
BEGIN
{
print("MySQL 前端线程 SQL 追踪 n")
}
uprobe:/usr/local/mysql/bin/mysqld:dispatch_command
{
$parser_state = (struct COM_QUERY_DATA*)arg1;
printf("sql = %sn", str($parser_state->query));
printf("stack info:");
printf("%sn", ustack(perf));
print("---------------------------------nn");
}
uprobe:/usr/local/mysql/bin/mysqld:my_message_sql
{
printf("errno = %dn", arg0);
printf("errmsg = %sn", str(arg1));
print("---------------------------------nn");
}
END
{
print("exit n")
}
3.2 运行 bpftrace 追踪程序
代码语言:javascript复制bpftrace mysql-sql-stmt-trace.bt
Attaching 5 probes...
MySQL 前端线程 SQL 追踪
3.3 执行 sql 查询
代码语言:javascript复制mysql> select user,host from mysql.user limit 1;
------------------ -----------
| user | host |
------------------ -----------
| mysql.infoschema | localhost |
------------------ -----------
1 row in set (0.00 sec)
3.4 观察 bpftrace 的输出
代码语言:javascript复制sql = select user,host from mysql.user limit 1
stack info:
34f6061 dispatch_command(THD*, COM_DATA const*, enum_server_command) 0 (/usr/local/mysql-8.0.32/bin/mysqld)
3714f7b handle_connection 479 (/usr/local/mysql-8.0.32/bin/mysqld)
5785c05 pfs_spawn_thread 336 (/usr/local/mysql-8.0.32/bin/mysqld)
7f370d88daaf start_thread 671 (/usr/lib64/libc.so.6)
---------------------------------
可以看到我们已经“抓到”了 MySQL 当前执行的 SQL 语句,就是这么简单。更多的 bpftrace 相关内容可以看这本书。