看源码太费眼睛了. 还是gdb方便点, 为了方便低版本gdb的环境, 这里就不升级gdb了.
高版本的是彩色的, 方便看, 有条件的可以安装下:https://www.sourceware.org/gdb/
gdb:7.6.1-120.el7
mysql:5.7.38
其实还有其它常用的, 但是不在本次调试的使用范围
直接回车就是上一条命令
简写 | 全称 | 解释 |
---|---|---|
p | 打印变量 | |
b | break | 在某处打断点 |
i b | info break | 查看断点. 还可以delete/disable break |
s | step | 下一条代码, 函数也会进入(详细的看,贼TM多的堆栈...) |
n | next | 下一条代码, 不会进入函数(粗略的看) |
l N | list N | 显示源码指定位置(默认当前)源码前后内容.
默认10行,嫌少的话, 可以设置下 |
fi | finish | 结束当前正在执行的frame(就是bt最上面那个) |
bt | backtrace | 查看函数调用,frame |
frame n | frame n | 切换到某个frame去.(backtrace查看) |
set | set | 设置变量的值 |
attach | attach | 接管某个进程(gdb -p pid) |
c | continue | 继续运行,知道下一个断点 |
编译的时候有些没有指定 -g
5.7.x 后面几个版本都差不多, 随便哪个都行, 8.0.x的也行. 源码环境和二进制mysqld是一样的版本就行
wget https://downloads.mysql.com/archives/get/p/23/file/mysql-5.7.38.tar.gz
略. mysqld --defaults-file=/data/mysql_3308/conf/mysql_3308.cnf
gdb -p `pidof mysqld`
不设置的话, 有部分源码就看不到. 不过影响也不大
其它版本的mysql,自己换名字哈
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, 不然是阻塞着的.
break trans_commit
info break
continue
提交的时候,看到在等待着server端反馈
begin;
insert into db1.t1(id) values(20230225);
commit;
我们看到自动切换到了新连接的线程,并触发了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了, 有必要使用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状态设置). 有兴趣的自己去看吧.
你也可以把其中涉及到的变量之类的打印出来瞧瞧. (也可以修改,不过不建议. 连接数满的时候,就可以通过这种方法修改最大连接数)
终于到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)
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