上周在调一批线上实例的连接数,有个同事又跑来问我:他明明在命令行里把max_connections改成了 2000,怎么过几天实例重启之后,SHOW VARIABLES一看又回到了 151。他吐槽说网上都说 MySQL 8.0 支持参数持久化,改了不应该重启失效,怎么到自己手里就不灵了。问题其实不在 MySQL 8.0,而在用的命令上。MySQL 从 8.0 开始确实提供了专门解决“修改参数后重启失效”的官方能力,核心就是SET PERSIST这条 SQL 语法。它会自动把运行时修改同步写入数据目录下的mysqld-auto.cnf文件,让参数在实例重启后继续保留。这篇文章就把这套机制完整拆开:怎么用、底层怎么运转、重启后真的失效了该从哪里排查,以及 Docker 和主从环境里容易踩的坑,一次讲清楚。适合还在手动改 my.cnf 的 DBA、运维,以及平时自己折腾 MySQL 的全栈同学。
1. 为什么8.0之前改参数重启就失效:动态变量与配置文件加载机制
在聊SET PERSIST之前,得先把问题根源说透。MySQL 里的 GLOBAL 级变量,按运行时的可修改性分成两类:
- 动态变量:运行中可以通过
SET GLOBAL修改,但只修改 mysqld 进程内存里的当前值,不写任何磁盘文件。 - 静态变量:只能通过启动参数或配置文件设置,mysqld 进程起来之后无法在线修改。
而配置文件(my.cnf 或 mysqld.cnf)只在实例启动时被解析一次。mysqld 启动时把配置文件里的值读入内存,作为全局变量的当前值;运行中你用SET GLOBAL改的只是内存值;重启后重新按配置文件加载,内存里的修改自然消失。这就是“改完参数重启失效”最底层的原理。
1.1 动态变量与静态变量的根本差异
打个比方就清楚了。配置文件的修改相当于改“开机启动项”,SET GLOBAL相当于改“当前系统运行参数”。你改当前运行参数但不改启动项,重启当然恢复原样。
举一个最典型的例子:
SET GLOBAL max_connections = 2000;这条命令执行后,当前实例的全局max_connections立刻变成 2000,新的连接可以到 2000。但如果 [mysqld] 段里没有写max_connections=2000,实例一重启,max_connections就回到默认的 151。
MySQL 8.0 之前的官方推荐做法,是在配置文件里手动加上对应的参数,然后重启实例。这种做法的缺点很明显:
- 多实例环境里每台机器都要改,容易出现遗漏,漏掉一台之后各实例配置漂移。
- my.cnf 可能通过
!includedir包含多个配置文件,参数到底写在哪个文件、哪个位置生效,需要仔细核对加载顺序。 - 修改过程中如果写错了参数名或值,重启后实例可能起不来,需要现场排查。
1.2 8.0之前的“标准做法”和它的问题
还有不少人误以为FLUSH PRIVILEGES或者mysqladmin reload能重新加载参数,实际上不能。reload只是重新加载授权表和一部分日志相关配置,绝大多数 GLOBAL 参数仍然只有重启才能重新从文件读取。这也是为什么早期排查“参数没生效”时,经常发现用户改完参数后没有重启,或者重启后改的文件不是实际加载的那个。
MySQL 8.0 引入SET PERSIST之后,这套手动流程已经被官方机制替代。你不用再纠结参数写到 my.cnf 的哪个段、要不要加引号、要不要重启,一条 SQL 就能同时搞定“当前生效”和“重启保留”。但想要用好它,还是得知道它背后是怎么工作的。
2. SET PERSIST 与 PERSIST_ONLY:两条路线,一个文件,说清楚区别
MySQL 8.0 之后,持久化参数的核心命令有三条:SET PERSIST、SET PERSIST_ONLY、RESET PERSIST。它们都围绕同一个文件mysqld-auto.cnf工作。
2.1 三种持久化SQL的使用场景
SET PERSIST 变量 = 值;:修改当前全局变量的值,同时把这条配置写入mysqld-auto.cnf。适合既要立刻生效、又要重启保留的动态参数。SET PERSIST_ONLY 变量 = 值;:只把配置写入mysqld-auto.cnf,不修改当前实例的运行值,等下次重启才生效。适合无法在线修改的静态参数。RESET PERSIST [IF EXISTS] 变量;:从mysqld-auto.cnf中删除该变量的持久化记录。当前运行值不受影响,但如果当前值来自持久化文件,那下次重启就会按 my.cnf 或默认值重新加载。
四条命令的差异整理成表格,一眼就能看清:
| 语法 | 修改当前全局值 | 写入持久化文件 | 重启后 |
|---|---|---|---|
SET GLOBAL | 是 | 否 | 恢复为配置文件或默认值 |
SET PERSIST | 是 | 是 | 保持持久化值 |
SET PERSIST_ONLY | 否 | 是 | 启动时按持久化值生效 |
RESET PERSIST | 不影响当前值 | 删除该变量记录 | 恢复为配置文件或默认值 |
2.2 一次完整实操:永久修改max_connections
以修改max_connections为例,最标准的操作是这样:
-- 1. 查看当前全局值 SHOW GLOBAL VARIABLES LIKE 'max_connections'; -- 2. 修改并持久化 SET PERSIST max_connections = 2000; -- 3. 确认已写入持久化文件 SELECT * FROM performance_schema.persisted_variables WHERE variable_name = 'max_connections'; -- 4. 再次确认当前全局值已生效 SHOW GLOBAL VARIABLES LIKE 'max_connections';执行完第 2 条之后,当前全局值立刻变成 2000,同时performance_schema.persisted_variables表里会多出一行记录。这条记录正是mysqld-auto.cnf文件内容的 SQL 视图。之后再重启,max_connections也能保持 2000。
这里有个容易忽略的点:查询时尽量用SHOW GLOBAL VARIABLES,而不是SHOW VARIABLES。SHOW VARIABLES查的是会话级别,某些参数(比如sort_buffer_size)如果被当前连接的SET SESSION覆盖过,你看到的值是会话值,不是全局值,很容易造成“没生效”的误判。
2.3 权限要求和常见的失败报错
SET PERSIST和SET PERSIST_ONLY需要较高的权限。8.0 里对执行用户的最低要求是SYSTEM_VARIABLES_ADMIN权限,兼容旧版的SUPER权限。普通开发账号执行时,通常会收到这样的报错:
ERROR 1227 (42000): Access denied; you need (at least one of) the SYSTEM_VARIABLES_ADMIN privilege(s) for this operation如果遇到这类报错,不要直接去改权限,更合理的做法是让 DBA 统一执行变更,或者在内部账号体系里给指定账号单独授权。毕竟能持久化参数的账号已经属于高权限账号,给得太宽后续审计会有压力。
对于像port、datadir、bind_address这类只读变量,直接执行SET PERSIST会报Variable 'xxx' is a read only variable。这种场景应该改用SET PERSIST_ONLY:
SET PERSIST_ONLY bind_address = '0.0.0.0';这条命令不会立刻修改当前实例的监听地址,但会写入持久化文件,下次重启后按新地址监听。实际工作中不少人在这一步翻车,拿只读变量去试SET PERSIST,报错了还以为 MySQL 8.0 的持久化功能有问题。
3. mysqld-auto.cnf 的加载优先级:常规配置文件的“最后说话人”
SET PERSIST产生的配置会写进数据目录下的mysqld-auto.cnf,很多人没搞清这个文件和 my.cnf 的关系,所以遇到“配置到底听谁的”时就糊涂了。
3.1 文件位置、格式与内容解读
文件位置很简单,就在datadir目录下,和数据文件放在一起。内容是 JSON 格式,大致长这样(不同小版本格式可能略有差异):
{ "Version": 1, "mysql_server": { "max_connections": { "Value": "2000" }, "innodb_buffer_pool_size": { "Value": "4294967296" } } }这个文件不是给人类手写的,是由 MySQL 自己维护的。每次执行SET PERSIST或SET PERSIST_ONLY,它都会自动更新。
3.2 启动时的加载顺序与--persisted-globals-load
启动时 mysqld 会先读取所有常规配置文件(比如/etc/my.cnf、/etc/mysql/my.cnf、/etc/mysql/conf.d/下的文件),按顺序加载完后,最后才读取mysqld-auto.cnf。也就是说,如果 my.cnf 里写max_connections=100,而mysqld-auto.cnf里写的是 2000,最终生效的是 2000。
这个“最后读取”的设计很关键。它的意义在于:持久化参数拥有最终决定权,只要你在运行时通过SET PERSIST改过,就不怕常规配置文件里还有旧值。
不过有一个前提,就是系统变量persisted_globals_load必须保持默认的 ON。如果有人在启动命令行或配置里显式把它设成了 OFF,那么实例启动时就不会加载mysqld-auto.cnf,所有持久化参数全部失效。这个开关本身不是为普通调优准备的,主要是给一些特殊部署场景做控制用,但确实有被误开的案例。
3.3 运维视角:这份文件应该怎么对待
从运维角度来看,mysqld-auto.cnf有几个需要特别注意的地方。
第一,不要用 vim 手改这个文件。JSON 多一个逗号、少一个引号,轻则配置被忽略,重则实例启动失败。想清理或回滚某个参数,就老老实实用RESET PERSIST。
第二,备份数据目录时别忘了它。有些备份工具只备份 InnoDB 数据文件,不备份 JSON 配置文件。一旦数据目录被恢复到另一台机器,mysqld-auto.cnf缺失,所有持久化参数都会丢,实例会按默认值或 my.cnf 启动。如果参数是max_connections这类还好,要是innodb_buffer_pool_size这种性能参数丢了,线上可能会出现一段时间的性能波动。
第三,查看持久化情况用表,不要直接 cat 文件。SQL 层面有performance_schema.persisted_variables表,输出更清晰,也方便做变更审计。
SELECT * FROM performance_schema.persisted_variables;4. 重启后参数还是没保住:四个排查链路逐个击破
即使知道了SET PERSIST的存在,实战中依然会碰到“重启后参数还是没生效”的情况。我一般按下面四条链路依次排查,基本都能定位到根因。
4.1 排查链路一:确认你执行的是不是SET PERSIST
这是最普遍的低级错误。很多人以为自己在用SET PERSIST,翻历史 SQL 一看,执行的是SET GLOBAL。两条命令在客户端里看起来差不多,但重启后的结果完全不同。
排查方法很简单,先查持久化表里有没有记录:
SELECT * FROM performance_schema.persisted_variables WHERE variable_name = 'max_connections';如果返回空,说明从来没有成功写入过持久化文件。这时候再回头看变更记录,大概率是SET GLOBAL而不是SET PERSIST。
4.2 排查链路二与三:文件存在性和加载开关
第二步,直接看文件在不在、权限对不对:
ls -l /var/lib/mysql/mysqld-auto.cnf正常情况下文件属主是mysql:mysql,权限至少要对 mysqld 进程可读。如果文件不存在,就说明持久化根本没写进去,回到链路一检查执行语句。
第三步,确认persisted_globals_load没有被关掉。可以在启动命令行和配置文件里搜一下:
grep -R "persisted-globals-load" /etc/mysql/如果查到--persisted-globals-load=OFF,文件即使存在也会被跳过加载。这种情况下,所有持久化参数在重启后都不会生效,但当前运行期可能看起来一切正常,极具迷惑性。
4.3 排查链路四:错误日志、参数合法性和重启后的验证方法
SET PERSIST在写入时会做参数名校验和简单的值合法性校验,但并不能覆盖所有运行时调整的风险。比如把innodb_buffer_pool_size调得超过物理内存太多,当次操作可能成功,重启时却可能因为内存申请失败导致启动异常。所以大内存类参数调完后,最好找一次低峰期做一次重启验证。
排查时同步看错误日志:
grep -i "persisted" /var/log/mysql/error.log启动阶段如果加载持久化配置时有异常,日志里一般会有相关记录。最后,重启后验证要快准狠:
SHOW GLOBAL VARIABLES LIKE 'max_connections';这里再强调一次,用 GLOBAL 而不是基本信息量的SHOW VARIABLES。有些参数存在 SESSION 级副本,当前会话里被改过之后,SHOW VARIABLES显示的是会话值,会造成误判。
5. Docker挂载、主从同步与清理策略:持久化的进阶避坑
基础机制搞清楚之后,再说几个真实环境里容易出问题的场景。
5.1 Docker容器里最容易丢参数的地方
Docker 里跑 MySQL 8.0 的同学一定要理解:mysqld-auto.cnf是写在数据目录里的。如果你没有把/var/lib/mysql挂载到宿主机,容器一旦被删除重建,整个数据目录都会消失,更不用说持久化参数了。
规范的启动方式是把数据目录挂载出来:
docker run -d --name mysql8 \ -p 3306:3306 \ -v /opt/mysql-data:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORD=yourpassword \ mysql:8.0这样挂载之后,容器内执行SET PERSIST生成的文件会留在宿主机的/opt/mysql-data/mysqld-auto.cnf里,容器重建后配置依然存在。
另外,通过环境变量(MYSQL_*)注入的参数和SET PERSIST不是同一个体系。环境变量本质上是在容器初始化阶段写进启动配置的,它的生效时机和优先级需要单独看镜像的实现,不能想当然地认为“两边都设了就一定会怎样”。我在实际中见过有人用环境变量设了max_connections,又在容器里用SET PERSIST改了另一个值,重启后总是“随机”出现不同结果,就是因为没理清两个体系的生效顺序。
5.2 主从和多实例环境下的一致性
SET PERSIST只影响当前实例,不参与主从复制,不会从主库同步到从库。这点一定要记住。在主库上执行SET PERSIST之后,从库不会跟着变。
对于连接数、buffer pool 这类关键参数,建议所有节点保持一致。做法有两种:
- 在主库和每个从库上分别执行
SET PERSIST,适合实例数量少的情况。 - 通过配置管理工具把参数统一写入各节点的
mysqld-auto.cnf,或者直接统一维护 my.cnf,适合实例多的情况。
如果只是主库改了、从库没改,主从切换之后整个集群的性能配置可能直接变了样。比如主库max_connections=2000,从库还是默认 151,切换后业务高峰一压上来,连接直接打满。
5.3 参数回滚、清理与大内存参数的额外提醒
回滚单个参数,用RESET PERSIST:
RESET PERSIST max_connections;希望删除“存在才删、不存在不报错”的版本,可以加IF EXISTS:
RESET PERSIST IF EXISTS max_connections;如果想一次性清掉所有持久化配置,官方并没有提供一条“清空所有”的 SQL,只能一条条执行RESET PERSIST。不要图省事直接删掉mysqld-auto.cnf文件,尤其是在生产环境。真要删,也必须在停库状态下操作,并且提前备份文件内容。
最后再提醒一个大坑:SET PERSIST写入的参数,当前运行正常不代表重启后一定正常。在线修改innodb_buffer_pool_size这类大内存参数时,机器上还有别的进程占着内存,当次调整可能没问题;但重启时如果系统内存状态不佳,mysqld 可能在加载阶段就失败。所以大内存相关的参数调整之后,建议在下一个低峰期主动重启验证一次,别等到故障重启才发现实例起不来。
我现在的习惯是:凡是线上需要跨重启保留的参数,统一用SET PERSIST来做;只有明确只在当前运行期生效的实验性调优才用SET GLOBAL。每次变更都会在记录里写清楚执行命令和回滚方法,下次审计或者排查时直接翻记录,不用再猜。这套流程跑了大半年,“重启后参数失效”的问题再没出现过。