GDB + bpftrace 分分钟入门 MySQL 源码

2023-04-20 19:07:59 浏览数 (1)

背景

以前我都是通过 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 相关内容可以看这本书。


0 人点赞