由mysql rewrite插件带来的8.0升级问题及解决方案

发布时间 2023-08-15 17:14:39作者: 香农博士

一、问题发生

在客户现场遇到一个语句,走mysql的执行计划,总是不能达到预期的join顺序,需手动执行straight join。为了让sql能够自动转换,想到了5.7开始支持的rewriter plugin,于是在测试环境测试了一把(结果发现只能做一些简单的查询重写,稍微复杂的多表关联,总是匹配不成功,这个按下不表,后续再进行学习分享)。测试完成后,准备升级到8.0版本,看看8.0有没有新的特性支持。然而升级启动时,却没有启动成功,error log中打印内容如下:

1 [ERROR] [MY-013235] [Server] Error in parsing Routine 'query_rewrite'.'flush_rewrite_rules' during upgrade. You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'QUERY CACHE;   IF NOT message_text IS NULL THEN     SIGNAL SQLSTATE '45000' SET ' at line 6
View Code

根据错误提示,在解析存储过程'query_rewrite'.'flush_rewrite_rules'的时候报错,然后更新数据字典错误,实例无法启动。这很容易想到刚刚安装的rewrite插件,查看安装插件执行过的脚本(/usr/local/mysql5.7/share/install_rewriter.sql),得到这个存储过程定义:

1 CREATE PROCEDURE query_rewrite.flush_rewrite_rules()
2 BEGIN
3   DECLARE message_text VARCHAR(100);
4   COMMIT;
5   SELECT load_rewrite_rules() INTO message_text;
6   RESET QUERY CACHE;
7   IF NOT message_text IS NULL THEN
8     SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = message_text;
9   END IF;
View Code

其中有一个命令,“RESET QUERY CACHE”,这个命令是从query cache中删除所有查询结果。而query cache在8.0版本已经不在支持。我们可以看到8.0版本的rewriter 这个脚本的定义删除了这个命令(/usr/local/mysql8.0/share/install_rewriter.sql):

1 CREATE PROCEDURE query_rewrite.flush_rewrite_rules()
2 BEGIN
3   DECLARE message_text VARCHAR(100);
4   COMMIT;
5   SELECT load_rewrite_rules() INTO message_text;
6   IF NOT message_text IS NULL THEN
7     SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = message_text;
8   END IF;
View Code

到此,似乎找到了问题的症结,即由于8.0中去除了query cache,无法升级rewriter的存储过程。但是怎么让实例继续往下升级,然后完成启动呢?

二、思考过程

起初,我没有仔细看报错,虽然知道是存储过程出了问题,但是实例无法启动,也没法修改或者卸载rewriter插件,于是抱着侥幸,测试了innodb_force_recovery,没有效果。

关闭rewriter_enabled参数也没有效果。

然后,我想之前修改密码时用过--init-file参数,可以通过指定--init-file=/usr/local/mysql5.7/share/uninstall_rewriter.sql来让mysqld启动时先卸载rewriter插件,再升级,这个过程不生效(报错的时候还没走到server启动)。

想来想去,似乎没法搞了,有点迷茫。当天没有再处理。

今天上午,我又想了歪招,既然是存储过程出的问题,而存储过程又存在mysql.proc表,proc表是个MyISAM表,是否可以通过工具来修改,不过搜了一下,貌似只有去分析FRM文件的工具,没有修改MYD的,于是放弃了。

三、解决

就在把焦点聚集到proc表,又没法改动的时候,我想起来,如果把当前的proc数据文件替换回没有安装过rewriter插件的5.7版本的呢。于是赶紧去查了下,果然,果然我的测试环境还有2个没有升级8.0,幸存的5.7.40!

对比了下,当前的proc和标准的proc差别:

1 ----添加插件前
2 -rw-r-----. 1 mysql mysql   9996 Mar 28 17:52 proc.frm
3 -rw-r-----. 1 mysql mysql 301604 Aug 15 14:35 proc.MYD
4 -rw-r-----. 1 mysql mysql   4096 Aug 15 14:35 proc.MYI
5 ---添加插件后
6 -rw-r-----. 1 mysql mysql   9996 Mar 16 17:22 proc.frm
7 -rw-r-----. 1 mysql mysql 301916 Aug 14 15:20 proc.MYD
8 -rw-r-----. 1 mysql mysql   4096 Aug 14 17:02 proc.MYI
View Code

于是把差异的proc.MYD同步到有问题的实例数据目录,再次启动,升级成功了:

 1 2023-08-15T15:04:52.981022+08:00 1 [Note] [MY-011088] [Server] Data dictionary initializing version '80023'.
 2 2023-08-15T15:05:02.565119+08:00 1 [Note] [MY-010337] [Server] Created Data Dictionary for upgrade
 3 2023-08-15T15:05:02.733144+08:00 0 [Warning] [MY-010918] [Repl] 'rpl_semi_sync_master' is deprecated and will be removed in a future release. Please use rpl_semi_sync_source instead.
 4 2023-08-15T15:05:02.733362+08:00 0 [Note] [MY-011130] [Repl] Semi-sync replication initialized for transactions.
 5 2023-08-15T15:05:02.733389+08:00 0 [Note] [MY-011142] [Repl] Semi-sync replication enabled on the master.
 6 2023-08-15T15:05:02.734599+08:00 0 [Note] [MY-011166] [Repl] Starting ack receiver thread.
 7 2023-08-15T15:05:02.734875+08:00 0 [Warning] [MY-010918] [Repl] 'rpl_semi_sync_slave' is deprecated and will be removed in a future release. Please use rpl_semi_sync_replica instead.
 8 2023-08-15T15:05:08.250726+08:00 2 [System] [MY-011003] [Server] Finished populating Data Dictionary tables with data.
 9 2023-08-15T15:05:08.254884+08:00 2 [Note] [MY-011008] [Server] Finished migrating TABLE statistics data.
10 2023-08-15T15:05:08.265917+08:00 2 [Note] [MY-011008] [Server] Finished migrating TABLE statistics data.
11 2023-08-15T15:05:08.900125+08:00 2 [Note] [MY-010006] [Server] Using data dictionary with version '80023'.
View Code

四、总结

搜索问题的时候,发现mysql讨论组也有发过类似的升级问题https://forums.mysql.com/read.php?20,686560,686560#msg-686560,没有提到解决方法。我刚遇到时,也是不知道如何是好,似乎实例要重做了(绝望时,我还提了bug:https://bugs.mysql.com/bug.php?id=112067&thanks=4,哈哈,这应该在官方不认为是一个bug,而是升级前没有认真检查~)。

还好没有放弃,最后的解决方式也是一种临时的对应策略。通过这次处理过程,发现自己存在如下问题:

1. 对8.0的升级不够熟悉,升级前以为in-place方式原地升级,替换下软件包,重启自动升级就完事了,殊不知5.7到8.0的功能变化,需要提前做好检测,避免8.0不再支持的功能导致升级失败,mysql官方地址:https://dev.mysql.com/doc/refman/8.0/en/upgrade-prerequisites.html 也可以通过mysql-shell工具提前检测下是否满足升级8.0的条件。

2.原理还是不够深入,操作没有细节把控,这块要加强学习总结能力。