在TDSQL这两年多的开发工作中,我感觉很自豪的一件事是我修复了不少mysql-5.7.17和mariadb-10.1.9的内核bug,这些bug大多已经报告给了MySQL/MariaDB官方开发团队,在每个bug描述中我会贴出来bug报告的连接。本文将大略介绍这些bug的概况,我在将来会写更多文章详细介绍每个bug的具体问题分析以及解决思路。本文列出的所有bug都已经修复,经过验证可以正确工作并解决相关问题。
这里先说一下为什么我要提交代码给mysql/mariadb官方开发团队,主要有一下几个好处:
- 官方开发者可以review我提交的patch,帮助完善patch,发现和解决之前可能没有想到的问题。我自己做mysql内核开发和bug 修复时会跑mtr(mysql自带的测试包),确保所有测例通过,并且新增测例覆盖新增的功能和代码。不过即使如此,也可能有未考虑到的点,因此可以借助mysql/mariadb官方的力量。
- 将来在新版本合并代码会很容易,因为这些patch的改动已经在官方发布的新版本中了,就不需要再次合并了。
- 为开源软件做出贡献,这样的一种社区氛围长期来看对所有mysql用户都是好事。
下面就是每个bug的简介,主要介绍其症状和危害性。具体解决方法和分析过程以及修复代码patch请看bug链接的页面,我未来也会写文章做出更详细的讲解和分析。下文中经常提到的英文术语简介如下:
XA: 分布式事务处理
DML: 数据操作语言,比如SQL语言中的INSERT,UPDATE,DELETE,SELECT等语句。
Mutex:互斥量,操作系统基础功能
安装插件流程中的mutex死锁
执行install plugin语句时,负责插件状态互斥的一个mutex与负责全局变量状态互斥的mutex可能发生死锁。死锁一旦发生,mysqld就hang住了,所有连接都无法继续工作。我提交给mysql官方开发团队的bug报告如下:
Bug?#88693Mutex deadlock causing mysqld to hang and cease to work
https://bugs.mysql.com/bug.php?id=88693
Optimize table与DML语句的冲突
当执行optimize table时由大量的DML语句(比如delete)在执行时,innodb有可能crash在一个assert中,导致mysqld进程崩溃退出。详情请见我的bug报告:
Bug #88511 innodb assertion failure in table rename
https://bugs.mysql.com/bug.php?id=88511
percona 备机复制在特定事件序列情况下卡住
对于mysql-5.7的XA事务来说,在一个XA事务的任何位置中断slave IO线程的连接后都会出问题,导致备机sql线程陷入死循环,备机复制无法继续下去。我提交给mysql官方开发团队的bug报告如下:
Bug #87385 Partial external XA transactions are not rolled back correctly
https://bugs.mysql.com/bug.php?id=87385
MYSQL 客户端的AUTOCOMMIT状态位设置不准确
当global autocommit是0 时候 新开启一个mysql连接时,客户端返回的autocommit状态位是1,应该是0. mariadb和percona都有这个问题。这个bug对于绝大多数最终用户而言可能是无感知的,不过TDSQL的网关模块依赖于这个状态位来正确工作,因而我们发现并修复了这个bug。
我提交给mysql官方开发团队的bug报告如下:
Bug #87813 Incorrect autocommit status bit in server_status at connection startup
https://bugs.mysql.com/bug.php?id=87813
无法在AUTOCOMMIT=0时结束PREPARED状态的XA事务
如果一个连接的autocommit=0,然后执行XA COMMIT/XA ROLLBACK来结束一个事务,那么Mysql会报错并拒绝操作。报错信息:
ERROR 1399 (XAE07): XAER_RMFAIL: The command cannot be executed when global transaction is in the NON-EXISTING state
这样导致PREPARED状态的XA事务在非autocommit 设置下无法被提交或者回滚,分布式事务处理机制无法正确工作。
我提交给mysql官方开发团队的bug报告如下:
Bug #87836 XA COMMIT/ROLLBACK rejected by non-autocommit session with no active transaction
https://bugs.mysql.com/bug.php?id=87836
percona XA事务提交和回滚的恢复机制
官方mysql-5.7的实现中,没有处理XA 事务分支的binlog + engine 恢复,包括XA PREPARE,XA COMMIT ... ONE PHASE以及XA COMMIT/XA ROLLBACK 提交的事务的恢复,mysql-5.7都没有实现binlog事务恢复机制,因而在各种灾难情况下会发生丢失XA事务,QQ号卖号平台数据不一致等致命问题。这些bug我们在TDSQL中都已经修复了。
这是一个巨大的开发任务,新增代码几千行,我提交给mysql官方开发团队的两个bug报告如下:
Bug #87560 XA PREPARE log order error in replication and binlog recovery
https://bugs.mysql.com/bug.php?id=87560
Bug #84297 Engine prepare executed after flush stage
https://bugs.mysql.com/bug.php?id=84297
Percona XA COMMIT/ROLLBACK事务的gtid管理
这类事件组(也就是一个xa事务的binlog的后半部分)在备机上面可能无法执行,因为主备切换的特殊时机下当前备曾是主机,已经执行过了。但是官方默认实现未考虑到这一点,就会导致备机可能无法连接到主机因为主机没有备机需要的gtid。此时的症状表现就是主备切换后,备机无法连上主机,产生反复连接和报错的循环,TDSQL在强同步模式下就无法运行了。这个bug已经修复,经过验证可以正确工作并解决相关问题。
Percona备机SQL线程执行XA_PREPARE_EVENT时,复制位置没有持久化
Percona备机SQL线程执行XA_PREPARE_EVENT时,复制位置没有持久化,于是备机crash然后重启之后,开始复制的位置就是错误的(会从更早的位置开始复制),就会导致复制卡住(比如要删除一个已经删除的行,或者更新时间找不到目标行,或者插入发生主键冲突)和数据不一致等严重问题。
我提交给mysql官方开发团队的bug报告如下:
Bug?#87389Replication position not persisted correctly for XA transactions
https://bugs.mysql.com/bug.php?id=87389
XA COMMIT事件没有被作为事务边界
MySQL-5.7的replication实现,依赖于一个事务状态分析对象,它根据扫描到的事件,维持一个当前事务状态,这个状态会被备机的复制机制使用。由于XA COMMIT这个事件没有被作为事务边界,就会导致很多XA相关的复制问题和bug。
我提交给mysql官方开发团队的bug报告如下:
Bug #87130 XA COMMIT not taken as transaction boundary
https://bugs.mysql.com/bug.php?id=87130
XA PREPARE与xtrabackup不能协同工作
主要原因是mysql执行XA PREPARE语句期间没有获取COMMIT全局意向锁,导致它不能被xtrabackup阻塞住,就可能发生xtrabackup备份的数据丢失了prepared事务的严重问题,导致数据不一致。
我提交给mysql官方开发团队的bug报告如下:
Bug #84442 XA PREPARE inconsistent with XTRABACKUP
https://bugs.mysql.com/bug.php?id=84442
XA COMMIT在其他连接中执行时与xtrabackup不能协同工作
如果XA COMMIT语句不是在当前XA事务所在的连接中执行的,而是当前连接断开后重新连接mysqld然后执行XA COMMIT,那么此时XA COMMIT不会获取全局COMMIT意向锁,也就不能被FTWRL阻挡住,于是就不能与xtrabackup正确地协同工作,导致数据不一致的严重问题。
我提交给mysql官方开发团队的bug报告如下:
Bug #84323 XA-COMMIT may not be blocked by FTWRL
https://bugs.mysql.com/bug.php?id=84323
mysqld 被mysqld.sock.lock阻挡而无法启动
mysql实现了一个防止重复启动mysqld的机制,就是在防止mysqld.sock的路径下面增加了一个mysql.sock.lock,这个文件里面记录着mysqld的进程号。当mysqld启动时刻,会检查这个文件内存储的进程号,向该进程发送一个信号0,如果发送成功,就认为这个进程是已经在运行的mysqld进程,于是当前正在启动的mysqld进程就退出了。
我们在做分布式事务的容灾测试期间,需要无数次反复kill掉和重启mysqld,偶尔会遇到mysqld无法启动的情况,报错就是 Another process with pid xxx is using unix socket file.
不过这个方法有个明显的漏洞,就是新的mysqld进程号向mysql.sock.lock里面存储的进程号XXX发送信号时,可能刚好有个进程的pid是这个数字XXX。这种事情发生的几率极低(因为linux 分配进程号的方式,并且我们的mysqld重新启动的间隔很短),但是不是不可能。所以我的改进方案就是在发送信号0 成功后,确认该进程的binary文件名是'mysqld'。如果检查仍然通过,那么还有可能这是另一个端口下的mysqld进程,尽管几率极低。所以我会做进一步的检查: 检查这个mysqld进程的启动参数中的--port=NUM 的NUM是当前正在启动中的mysqld进程的端口号。只有这些检查都通过了,才认为当前mysqld进程号确实是重复启动了,应该退出。
mariadb sleep时长不精确
这个是TDSQL的自研功能的bug修复。tdsql-mariadb-10.1.9 中,我们为了使sleep()函数避免出现因为机器时间被调整而导致sleep()无法按时返回的问题,重新实现了sleep()函数.
修改后,tdsql-mariadb-10.1.9提供了两种sleep()函数的实现,第一种是我的新的实现方案,可以避免机器时间被调整而导致sleep()无法按时返回的问题,并且精度与官方实现相同,默认使用该方案;另外,可以通过动态设置全局变量use_tdsql_sleep_func=0,来使用mariadb官方的默认sleep()函数实现。
MariaDB InnoDB表空间文件头标志字段bug
这是我在MariaDB官网上面提交的bug报告:https://jira.MariaDB.org/browse/MDEV-9581
这个bug的症状是MariaDB-10.1.9无法使用MariaDB-10.0.10产生的数据目录:当指定datadir为这样一个目录时,mysqld进程在启动过程中发现错误而退出了。也就是说,如果你之前在使用MariaDB-10.0.x,有一个数据目录,里面有你珍贵的数据,然后现在你打算使用MariaDB-10.1.x,你会痛苦地发现,MariaDB-10.1.x无法用原来的数据目录启动,也就无法使用mysql_upgrade来升级数据目录。虽然可以使用dump/load的方法来规避问题,但是在数据量很大的情况下,这样做非常耗时,效率很低。这个bug已经修复,经过验证可以正确工作并解决相关问题。