☰
MySQL 8.0报错Unknown system variable ‘query_cache_size‘的排查与配置迁移指南
2026/10/18 0:05:26 网站建设 项目流程

我遇到过很多次这种报错,每次帮别人排查的时候,第一反应基本都是:这哥们儿又升级MySQL版本了,而且没清理老配置文件。

Unknown system variable 'query_cache_size'这个问题,说白了就是MySQL 8.0版本把查询缓存(Query Cache)整个功能给删掉了,但你的配置文件里还留着老版本的参数设置,启动时MySQL根本不认识这些参数,自然直接报错退出。今天我把这个问题的来龙去脉、处理方式和避坑经验一次性说清楚。

1. 先说清楚:这个报错到底是怎么来的

1.1 一段前尘往事:查询缓存的兴衰

MySQL的查询缓存功能从很早的版本就存在了,它的核心思想特别朴素:如果连续执行两条一模一样的SELECT语句,而且涉及的表数据在这期间没有任何变化,第二次就可以直接把第一次缓存好的结果集返回,省去重新解析SQL、扫描索引、读数据页的整个过程。

在MySQL 5.1到5.7这个时代,这个概念听起来很诱人,尤其对于读多写少、数据基本不怎么变动的业务场景,打开query_cache_type=1,配置一个合适的query_cache_size,确实能在某些场景下带来明显的性能提升。

但查询缓存的实现有一个致命短板:失效太频繁。只要表上发生了任何写操作(INSERT、UPDATE、DELETE哪怕只影响了一行),这张表的所有查询缓存全部作废。也就是说,在写入稍微频繁一点的业务里,缓存刚缓存好一批结果,还没来得及命中几次,就被一次写操作给清空了,吃力不讨好,反而因为维护缓存还要额外消耗CPU和锁资源。

所以在MySQL 8.0正式发布时,官方直接砍掉了这个模块,query_cache_type、query_cache_size、query_cache_limit这一整批参数全部移除,连带着SQL_CACHE、SQL_NO_CACHE这两个SQL提示符也没了。

1.2 报错的两个典型触发场景

第一个场景是启动服务直接失败。这种情况最常见于把MySQL从5.7升级到8.0,或者新安装的8.0实例使用了从5.7拷贝过来的my.cnf配置文件。只要文件里有类似下面这些参数,MySQL 8.0在初始化阶段就会报错退出:

query_cache_type=1 query_cache_size=67108864 query_cache_limit=1048576

第二个场景是执行SQL语句时触发。一些老版本的ORM框架,或者早期封装的数据库中间件,内部逻辑为了“优化性能”会主动执行SET SESSION query_cache_type = ON;这类语句,在8.0上同样会得到一模一样的报错。

注意:你在网上搜索这个报错时,能看到很多文章让你“把MySQL升级到5.7.20以上”,这种说法不仅过时而且有害。5.7.20只是修复了查询缓存相关的内存分配Bug,从未等于“支持Oracle的付费功能”。8.0才是彻底移除,必须用完全不同的思路来解决。

2. 最快的解决办法:让配置和版本对齐

2.1 找出并清理配置文件里的残留参数

排查的第一步,是找到MySQL当前实际读取的配置文件。注意,Linux系统中存在多个候选位置,MySQL会按顺序读取,后读取的会覆盖先读取的同名参数。最常见的路径包括:

  • /etc/my.cnf
  • /etc/mysql/my.cnf
  • /etc/mysql/mysql.conf.d/mysqld.cnf(Ubuntu/Debian常见)
  • $MYSQL_HOME/my.cnf
  • 编译安装时指定的basedir目录下的my.cnf

保险起见,用一个命令把实际生效的配置路径打印出来:

mysqld --verbose --help | grep -A 1 "Default options"

输出结果会显示“Default options are read from the following files in the given order”,把每个路径都检查一遍,用grep -n "query_cache"找到带query_cache字样的行,直接注释或删除。

我的习惯是先用grep定位,确认无误后再统一编辑。比如:

grep -rn "query_cache" /etc/my.cnf /etc/mysql/

找到之后,把这些行整行删掉,或者用#注释掉,二选一。有些同学舍不得删,想注释掉留给以后参考,完全可以,注释不影响启动。

2.2 处理其他相关的“老参数”

除了query_cache_size,8.0里一起被移除的查询缓存参数还包括:

参数名作用8.0中的状态
query_cache_type查询缓存开关已移除
query_cache_size缓存总内存大小已移除
query_cache_limit单条结果集缓存上限已移除
query_cache_min_res_unit缓存块最小单位已移除
query_cache_wlock_invalidate写锁期间是否失效缓存已移除

另外还有两个经常和它一起打包出现的老参数也值得检查一遍:key_buffer_size(MyISAM索引缓存,8.0中仅对MyISAM表仍有效,不影响InnoDB)和innodb_buffer_pool_size(这个在8.0中仍然有效且是核心参数,千万别删)。

清理完配置文件后,重启MySQL服务:

systemctl restart mysqld

或者:

service mysql restart

启动成功后登录进入MySQL,执行:

SHOW VARIABLES LIKE 'query_cache%';

返回一个空结果集,就说明彻底干净了。

2.3 如果报错来自应用端SQL语句

如果服务能正常启动,但某个应用连接MySQL时报Unknown system variable 'query_cache_size',说明不是配置文件的问题,而是应用在连接初始化阶段执行了设置查询缓存参数的SQL。排查思路是看两个方向:

第一,应用的数据库连接池配置。以Java的HikariCP、Druid为例,有些老项目的connection-init-sql配置里写入了SET SESSION query_cache_type=ON;这种初始化语句。在MySQL 8.0中这属于非法SQL,直接导致连接初始化失败。解决办法是去掉这段初始化SQL。

第二,ORM框架或中间件版本太旧。比如某些老版本的MyBatis、Hibernate方言配置中带了query_cache相关内容,或者TTL Cache中间件中配置了这类语句,升级到适配MySQL 8.0的版本即可。

判断原则很简单:启动都失败,先查my.cnf;启动正常但运行中报错,先查应用和中间件。两条线不要搞反。

3. 从5.7迁移到8.0时的系统性配置清理

3.1 MySQL 8.0的参数兼容性全局变化

如果你正在规划5.7到8.0的升级,query_cache相关参数只是冰山一角。8.0对参数体系做了大范围的调优和精简,涉及字符集、认证插件、排序规则、系统表结构等方方面面。这里列一个常用清单,升级前对照检查:

老参数/配置项5.7状态8.0状态处理建议
query_cache_*可用已移除删除配置,改用业务层缓存或优化SQL
sql_mode默认宽松默认严格(ONLY_FULL_GROUP_BY, STRICT_TRANS_TABLES等)升级前先用5.7测试严格模式兼容性
default_authentication_pluginmysql_native_passwordcaching_sha2_password老客户端需要升级驱动
default_charsetutf8mb3(即utf8)utf8mb4需显式指定或接受默认
information_schema_stats_expiry无86400(默认)统计信息过期导致执行计划漂移,可调整
innodb_file_formatAntelopeBarracuda(默认)已自动切换,无需处理
log_bin默认关闭默认关闭(但binlog格式默认ROW)确认备份恢复策略
explicit_defaults_for_timestamp有默认值与5.7一致,行为更规范需注意TIMESTAMP字段默认值行为差异

这个表不是让你全背下来,而是提示你:升级不是简单地跑一遍mysql_upgrade就完事,至少要把配置逐项检查一遍。

3.2 拿一台测试机做“试运行”

别直接在线上库执行升级,这个坑我踩过一次之后,再也不敢偷懒。

第一步:准备一台与线上配置接近的虚拟机或容器,安装与现网大版本一致的5.7,导出一份线上数据做恢复测试。

第二步:在测试机上停止MySQL 5.7,备份老数据目录(datadir),把二进制文件替换成8.0,启动时先不加载老配置文件,使用mysqld --initialize初始化一个新的数据目录,再把旧数据通过逻辑备份方式导入。导入完成后,打开一个隐藏配置文件的副本,逐个参数测试,看哪些参数导致MySQL拒绝启动。

第三步:全程观察错误日志(/var/log/mysql/error.log),遇到Unknown system variable就删掉对应行,遇到Unknown storage engine就检查引擎兼容性。实测下来,把5.7换成8.0并全量导入数据,大约需要按顺序处理以下几类问题:

  • 查询缓存族参数(本次主题)
  • 字符集相关的character_set_server差异
  • 旧版mysql_native_password认证插件导致的客户端连接失败
  • 触发器、函数、存储过程中的SQL模式差异

处理完一轮之后,再把测试机上最终版本干净且可用的配置文件作为线上配置的基准,而不是直接拿老文件改来改去。

3.3 迁移后性能不仅没变差,反而可能更快

很多人以为移除查询缓存会导致读性能大幅下降,但实际上8.0用更底层的机制换回了更稳定的整体表现。InnoDB缓冲池(innodb_buffer_pool_size)把热数据页放在内存中,查询时直接读内存页,本身就是一个天然的“页级缓存”,比查询缓存的结果集序列化存储更高效。

而且8.0的优化器更强了,基于代价的索引选择,配合直方图、倒序索引等新特性,很多原本依赖查询缓存“糊弄性能”的场景,在主键索引或二级索引合理的情况下,根本不需要额外缓存层。迁移之后,我的实测经验是,去掉查询缓存反而减少了全局锁竞争,在写入频率较高的业务里,整体吞吐反而更稳定了。

4. 如果你真的需要缓存:三个替代方案

4.1 应用层缓存(最推荐)

查询缓存没了,业务对“热点查询”的缓存需求并没有消失。最靠谱的方案是把缓存放到应用层,用Redis或Memcached来承载。

考虑一个商品信息查询的场景,原本靠MySQL查询缓存扛住“同一个商品详情页被反复查询”的热点流量,现在可以在应用服务里加一层Redis:

1. 请求进来,先查Redis,Key设计为 product:detail:{productId} 2. Redis命中,直接返回,不访问MySQL 3. Redis未命中,查询MySQL,把结果序列化写入Redis,设置过期时间(比如10分钟) 4. 数据发生变更时,主动更新Redis里的对应Key,或者直接删除Key让下次查询重建

这样做的好处远不只是补位:查询缓存原来只能缓存完全相同的SQL语句,而应用层缓存可以基于业务语义设计缓存粒度,同样一份商品数据,字段多几个少几个、SQL里换个排序方式,都能命中同一个Redis Key。而且Redis原生支持分布式,多台应用服务器之间可以共享缓存,这比MySQL单机的查询缓存强太多了。

4.2 MySQL 8.0的线程池和Buffer Pool结合改造

如果你的业务对数据库的整体并发能力不满意,与其寄希望于查询缓存,不如把思路放到8.0的新特性上。innodb_buffer_pool_size设置合理(通常为物理内存的60%-70%),热数据基本都在内存中,配合innodb_buffer_pool_instances把缓冲池分成多个实例,减少内部锁竞争;innodb_io_capacity设为2000(SSD场景),让后台刷脏能力跟得上压力。

这些机制加在一起,比一个随时可能被写操作击穿的查询缓存可靠得多。8.0的InnoDB缓冲池本身就能缓存表和索引的数据页,查询走索引定位到页,直接在内存里取结果,这个路径比“查一次SQL再把结果集拷贝一份存起来”轻量得多。

4.3 代理层缓存(ProxySQL、应用网关)

还有一种思路是在数据库前面加一层读写分离代理,比如ProxySQL。它自带查询缓存功能,且缓存粒度可以做到更细,能够识别同一类查询模板(把参数值替换成通配符),不会因为参数值不同就缓存失效。在业务不想大改的情况下,用代理层做缓存是一个折中方案。

不过我不建议为了“替代查询缓存”专门引入一套代理集群,毕竟加一个中间件就意味着增加一层运维负担和故障点。除非你的架构本来就需要读写分离,顺手把查询缓存挂在代理层,那倒是顺理成章。

5. 兼容性排查:如何正确面对“还能不能设置参数”的疑问

5.1 误报排查看这里:不是所有带“cache”的都不能用

移除的是query_cache_*这个命名空间下的参数,但innodb_buffer_pool_size、key_buffer_size、table_open_cache、thread_cache_size在8.0中都是合法参数,很多刚接触8.0的同学看到“缓存”两个字就草木皆兵,把有用的参数也给删了,这反而矫枉过正。

判断一个参数是否适用,最权威的办法是查官方文档,或者直接在MySQL里执行SHOW VARIABLES LIKE '参数名';,能查到的就是当前版本支持的,查不到的就是被移除或者改名的。比如SHOW VARIABLES LIKE 'thread_cache_size';能正常返回,就说明8.0仍然保留它。

5.2 老代码里的SET语句兼容处理

如果你维护的老项目中,有类似这样的一行代码执行:

SET SESSION query_cache_type = ON;

在5.7上可以正常运行,在8.0上直接报错。处理方式分两种:

第一种,代码能改就直接删掉这行。因为查询缓存已经没有了,设置这个session变量根本没有任何意义,删掉后一切都好。

第二种,如果代码是编译好的二进制组件,或者不方便发布新版本,可以做一个兼容性包装:在数据库代理层过滤掉这条SET语句。比如ProxySQL的mysql_query_rules里设置一条规则,把SET SESSION query_cache_type开头的语句直接丢弃(DROP)或者返回OK(OK),就能让老客户端在8.0上正常连接。

还有一种更简单粗暴的做法:把数据库用户隔离,老模块连接专用的一个MySQL 8.0账号,通过init_connect参数强制设置SET SESSION query_cache_type = OFF;。但8.0里query_cache_type已经被移除,这个方法只能用于5.7,在8.0中无效。所以归根结底,还是得靠改代码或者代理层处理。

5.3 5.7和8.0开发的兼容写法

如果你的开发环境是5.7,生产环境已经升级到8.0,测试代码里又涉及到查询缓存的设置,最稳妥的做法是写一个兼容检查逻辑,先探测当前MySQL版本号再决定是否执行SET语句:

import pymysql conn = pymysql.connect(host='localhost', user='user', password='pass', database='test') cursor = conn.cursor() # 判断大版本,只有5.x才需要处理查询缓存相关设置 cursor.execute("SELECT VERSION()") version = cursor.fetchone()[0] if version.startswith("5."): cursor.execute("SET SESSION query_cache_type = OFF") # 注意:5.7虽然支持query_cache_type,但默认也可能被禁用,这里只是示例 cursor.close() conn.close()

不过这种判断逻辑属于“临时打补丁”,时间长了代码里都是版本分叉,维护成本很高。更推荐的做法是:开发环境也直接统一到8.0,不要为了模拟老版本行为而在新代码里写兼容逻辑。

6. 完整解决过程实录:一步一步复现处理

我把一次完整的处理过程写下来,方便你照着操作。

场景:一台CentOS服务器,MySQL从5.7停机升级到8.0,启动时直接报错。

第一步,看错误日志:

tail -n 50 /var/log/mysql/error.log

日志里有一条很直接的记录:

[ERROR] [MY-000067] [Server] unknown variable 'query_cache_size=67108864'

关键信息就是unknown variable,后面跟着参数名,意思就是当前版本不认识这个参数。

第二步,全盘搜索配置文件:

grep -rn "query_cache" /etc/ /etc/mysql/ /usr/my.cnf 2>/dev/null

搜索结果显示/etc/my.cnf里存在三行:

query_cache_type=1 query_cache_size=67108864 query_cache_limit=1048576

第三步,备份配置文件后再修改:

cp /etc/my.cnf /etc/my.cnf.bak sed -i '/query_cache/d' /etc/my.cnf

先备份再改,万一改错了还能回滚,别冒险直接改。

第四步,检查是否有其他不兼容参数。既然是从5.7升上来的,顺手把整个配置里可能不兼容的项都排查一遍。重点关注sql_mode、default-authentication-plugin、innodb_file_format等。

第五步,重新启动:

systemctl restart mysqld

启动成功后登录验证:

mysql -uroot -p mysql> SHOW VARIABLES LIKE 'query_cache%'; Empty set (0.01 sec)

确认无误后,再简单测试几条常用SQL,确认业务功能正常。如果是新升级的实例,重启后建议加一个快照备份,后续出现异常还能快速回退。

7. 常见问题排查速查表

问题现象可能原因排查步骤解决方案
启动MySQL直接报unknown variable query_cache_size配置文件残留5.7的参数grep -rn "query_cache" /etc/my.cnf删除或注释对应行
服务能启动,但应用连接报错应用或中间件初始化SQL含SET语句检查连接池的connection-init-sql、ORM配置去掉SET语句,或升级中间件版本
5.7升级8.0后,字符集大面积乱码8.0默认字符集改为utf8mb4查看SHOW VARIABLES LIKE 'character_set%';在配置文件中显式指定character_set_server=utf8mb4
老客户端连不上MySQL 8.0认证插件从mysql_native_password改为caching_sha2_password查看SHOW VARIABLES LIKE 'default_authentication_plugin';升级客户端驱动,或为老用户保留mysql_native_password注1
删了query_cache参数后,热点查询变慢缺少了曾经的缓存层用EXPLAIN分析执行计划,检查索引按4.1节加应用层缓存,或优化索引与SQL
修改配置文件后参数不生效改了非默认读取路径的配置文件mysqld --verbose --help查看实际读取顺序修改实际生效的配置文件,然后重启确认

注1:这种方式仅作为过渡,新环境建议直接使用caching_sha2_password,老驱动适配后切换回来。

补充一个容易被忽视的细节:修改配置文件后必须重启MySQL,SET GLOBAL即使能临时改运行时变量,也无法覆盖配置文件里的静态启动参数。更关键的是query_cache_*这类被移除的参数,配置文件里根本没对应运行时变量,不重启等于白改,服务起不来反而成了“改了没效果”的错觉根源。

8. 聊聊我自己的处理习惯:让这次报错以后不再出现

这套流程走下来,核心就是一个思路:版本的升级不只是二进制替换,配置文件的兼容性清理是必做功课。

我个人现在再遇到Unknown system variable系列报错,都会先养成立刻确认版本的习惯。不管是新装8.0还是从老版本升级,第一步永远是mysql --version看清楚当前实例的大版本,然后再去找配置文件的问題。这一步能省掉大量无效排查。

另外,处理完query_cache_size之后,建议顺手把所有来自旧版本的配置项整体过一遍。我在实际操作中发现过很多次:一个环境里残留的旧参数往往不止一个两个,一起处理掉,既能解决当前的启动报错,还能规避后续业务上线时的隐性故障。

最后说一个加分项:在升级或整改结束后,养成把干净可用的my.cnf归档保存的习惯。下次再碰到新环境需要初始化,直接把这份已经验证过的配置拿过去改改路径就能用,比从网上东拼西凑一份配置再慢慢调,省力得多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询