首页
学习
活动
专区
圈层
工具
发布
清单首页基础文章详情

使用 GDB 查看Mysql5.7.38的COMMIT过程(两阶段提交)

看源码太费眼睛了. 还是gdb方便点, 为了方便低版本gdb的环境, 这里就不升级gdb了.

高版本的是彩色的, 方便看, 有条件的可以安装下:https://www.sourceware.org/gdb/

环境:

gdb:7.6.1-120.el7

mysql:5.7.38

GDB 常用命令

其实还有其它常用的, 但是不在本次调试的使用范围

直接回车就是上一条命令

简写

全称

解释

p

print

打印变量

b

break

在某处打断点

i b

info break

查看断点. 还可以delete/disable break

s

step

下一条代码, 函数也会进入(详细的看,贼TM多的堆栈...)

n

next

下一条代码, 不会进入函数(粗略的看)

l N

list N

显示源码指定位置(默认当前)源码前后内容. 默认10行,嫌少的话, 可以设置下 set listsize 30

fi

finish

结束当前正在执行的frame(就是bt最上面那个)

bt

backtrace

查看函数调用,frame

frame n

frame n

切换到某个frame去.(backtrace查看)

set

set

设置变量的值

attach

attach

接管某个进程(gdb -p pid)

c

continue

继续运行,知道下一个断点

准备MYSQL源码

编译的时候有些没有指定 -g

5.7.x 后面几个版本都差不多, 随便哪个都行, 8.0.x的也行. 源码环境和二进制mysqld是一样的版本就行

代码语言:shell
复制
wget https://downloads.mysql.com/archives/get/p/23/file/mysql-5.7.38.tar.gz

开始GDB调试

启动mysqld

略. mysqld --defaults-file=/data/mysql_3308/conf/mysql_3308.cnf

使用GDB接管mysqld

代码语言:shell
复制
gdb -p `pidof mysqld`

设置mysql源码路径(可选)

不设置的话, 有部分源码就看不到. 不过影响也不大

其它版本的mysql,自己换名字哈

代码语言:shell
复制
set substitute-path /var/lib/pb2/sb_1-6473437-1647886122.65/mysql-5.7.38 /root/mysql_source/mysql-5.7.38

设置断点

本次主要是看提交过程, 所以断点给 trans_commit

你也可以看看mysql命令执行过程,就可以设置mysql_execute_command, 也可以设置其它的, 也可以都设置.

设置完后,记得continue, 不然是阻塞着的.

代码语言:javascript
复制
break trans_commit
info break
continue

登录Mysql,发起一个事务并提交

提交的时候,看到在等待着server端反馈

代码语言:sql
复制
begin;
insert into db1.t1(id) values(20230225);
commit;

gdb 开始查看提交过程

我们看到自动切换到了新连接的线程,并触发了breakpoint 1

此时, 代码就停在trans_commit处, 可以使用bt查看frame信息, 也可以step/next运行代码, 也可以finish完成当前frame代码.

上层的frame就是mysql_execute_command解析mysql命令, 再上层就是sql_parse了

frame信息是从下往上的, 就是栈, 也叫栈帧

使用 list查看下当前的源码信息(有点少, 可以使用set listsize 30设置显示多点,也可以手动指定起始范围)

使用 step 进入函数执行

然后就进入了trans_check_state , (检查事务能否提交或者回滚的)

我们并不关心它的检查过程, 直接 finish(返回了false,表示可以提交或者回滚), 就又回到了trans_commit

继续step, 到了ha_commit_trans. 后面就已经是提交完成的了, 所以我们使用step进入这个函数, 看下具体的提交过程

再使用list看下代码, 发现commit_owned_gtids, 在事务prepare之前,保存gtid信息, 我们并不关系它怎么保存的, 所以使用next执行代码(直接执行完函数,不会进入函数)

获取事务之类的我们也不关心, 都直接next了, (需要看详情的话,就使用step进去)

这是个很枯燥的过程(太多的if).... 截图太细也没得意义. 后面我就都直接跳到关键地方. 忘记自己在哪的时候使用 bt(backtrace)就知道了

tc_log->prepare

终于到了tc_log->prepare了, 有必要使用step进去瞧瞧. 不想瞧的直接next去看commit也可以的.

进去后是MYSQL_BIN_LOG (两阶段提交的CN角色)

注:没开启binlog的时候, CN是tc_log_mmap/tc_log_dummy(都是继承自tc_log)

查看源码,发现又是调用的ha_prepare_low, 就使用step一步步进去吧...

进去后又是一堆.....

又是获取事务信息之类的,太多了,直接跳过

终于到了binlog的prepare了(起始它基本上啥也没做....)

注:innobase/binlog的prepare代码里都是ht->prepare, 前面有ht哪来的,只是我没截图而已.

很快就到innobase的prepare阶段了(毕竟binlog->prepare啥也没干...)

不看细节了(比如check_trx_exists,innobase_trx_init之类的,还有一大堆锁,尤其是mutex.... 还有undo状态设置). 有兴趣的自己去看吧.

你也可以把其中涉及到的变量之类的打印出来瞧瞧. (也可以修改,不过不建议. 连接数满的时候,就可以通过这种方法修改最大连接数)

LSN在这

tc_log->commit

终于到commit阶段了

binlog生成之类的都不看了(要解析Binlog的话,可以使用mysql-replication)...

直接到flush阶段(刷redo, 是在commit阶段的flush阶段), 详情就不看了, 太多了

刷完redo,刷binlog (可以确认下binlog的时间戳是否变化)

刷完之后还会调用fsync

刷机都刷完后,就该commit了. 提交阶段都是更新一些状态信息之类的(会在存储引擎提交,还会刷redo. ha_commit_low).

后面还有process_after_commit_stage_queue,finish_commit.....

总结

这居然只是一个提交, 修改的东西已经很多了, 总结下过程吧

用户发起 'commit' --> 服务器解析sql --> tc_log->prpare (binlog->prepare,innobase->prepare) --> tc_log->commit(flush,刷redo, binlog, sync,commit)

代码语言:txt
复制
COMMIT_SQL
  >trans_commit
    >ha_commit_trans
      >tc_log->prepare(这里是MYSQL_BINLOG::prepare)
        >ha_prepare_low
          >binlog_prepare(啥也不干)
          >innobase_xa_prepare
      >tc_log->commit(MYSQL_BINLOG::commit)
        >process_flush_stage_queue(ha_flush_logs)(刷redo)
        >flush_cache_to_file(刷binlog)
        >sync_binlog_file
        >process_commit_stage_queue
举报
领券