通过上面的分析可以看到,不管是在 case 1 还是 case 2,备份线程和用户线程都不再接受响应了,要解决死锁的话,只能 kill 掉 SQL 线程了,那么直接 kill 是否有风险呢?
SQL 线程能执行 Xid event,说明是在更新事务引擎表,kill 掉应该没问题(事务可以回滚,之后可以重做),但是5.6有这样的一个bug,会导致SQL线程在等待 COMMIT 锁的时候被kill,直接跳过事务,这样备库会比主库少一个事务,因此 kill 后需要对比主备数据,把少的事务补上。
如果你使用的 MySQL 版本已经修掉这个bug,也就是在 5.6.21 版本及之后,那么 kill SQL 线程是安全的。
死锁重现
如果为了测试或研究代码,要想复现死锁该怎么办呢?如果直接在备库执行一个 FTWRL,很可能是复现不了的,因为FTWRL是获取2个锁,全局读锁和全局 COMMIT 锁,SQL 线程非常可能被全局读锁堵到(Waiting for global read lock),而不是被 COMMIT 锁堵(Waiting for commit lock)。
一种方法是写 testcase,用 dubug sync 功能设置同步点,让线程停在指定的地方,但这要求 mysqld 跑在deubg模式下,并且要求有一定的MySQL 源码开发基础;
另一种方法是改代码,延长do_commit的时间,比如 sleep 一段时间,这样就给我们足够的时间让 FTWRL 在 SQL 线程请求 COMMIT 锁前执行完成,但是这需改代码,然后重新编译安装;
如果我们不会用debug sync,又不想改代码重新编译安装,就想在已有的环境测,改怎么办呢?SYSTEMTAP!
systemtap 起初只支持在内核空间进行探测,0.6 版本之后可以在用户空间进行探测,使用 systemtap 需要程序中包含 debug 信息(程序编译时加上 -g 选项)。
列出所有我们可以对 mysqld 进行探测的地方。
sudo stap -L 'process("/usr/sbin/mysqld").function("*")'
列出所有可以对 Xid_log_event 类进行探测的地方。
sudo stap -L 'process("/usr/sbin/mysqld").function("*Xid_log_event::*")'
如果我们想让 Xid_log_event::do_commit 执行有点延迟,可以这样做:
sudo stap -v -g -d /usr/bin/mysqld --ldd -e 'probe process(16011).function("Xid_log_event::do_commit") { printf("got it\n") mdelay(3000) }'
16011 是正在跑的备库进程PID,执行上面的 stap 命令后,每当备库执行到 Xid_log_event::do_commit 时,stap 就会打出个 “got it”,然后 SQL 线程暂停3s,这就给了我们充足的时间去执行 FTWRL,在SQL线程 commit 前拿到 COMMIT 锁。