直接改my.cnf权限不能防set global,因该文件仅控制启动默认值,运行时set global/persist在sql层动态修改内存或持久化配置,与文件权限无关;必须同时撤销system_variables_admin、session_variables_admin、connection_admin和super权限,并执行flush privileges验证生效。

为什么直接改 my.cnf 权限不能防住 SET GLOBAL
很多人以为把 /etc/my.cnf 设成 600 就能防用户改参数,其实完全无关——my.cnf 控制的是 MySQL 启动时的默认值,而运行中用户执行 SET GLOBAL 或 SET PERSIST 是动态修改内存/持久化配置,不读也不写配置文件。权限控制全在 SQL 层,和文件系统权限毫无关系。
必须同时撤销这四个权限才能禁用 SET GLOBAL
MySQL 8.0+ 中,仅 REVOKE SYSTEM_VARIABLES_ADMIN 不够,用户仍可能通过以下任意一种方式绕过:
-
CONNECTION_ADMIN:隐式允许修改全局变量,优先级高于SYSTEM_VARIABLES_ADMIN撤销结果 -
SESSION_VARIABLES_ADMIN:支持SET PERSIST,写入mysqld-auto.cnf,重启后自动生效 -
SUPER:虽已标记为 deprecated,但未显式回收就依然有效 - 角色继承:比如用户被赋予了
mysql.session角色,而该角色自带CONNECTION_ADMIN
正确操作是四者一次性撤回:REVOKE SYSTEM_VARIABLES_ADMIN, SESSION_VARIABLES_ADMIN, CONNECTION_ADMIN, SUPER ON *.* FROM 'user'@'host';
然后必须执行 FLUSH PRIVILEGES;,否则缓存不更新。
如何验证权限是否真正失效
别信 SHOW GRANTS 输出,它不显示角色继承的权限。要确认真实状态,得查系统表:
- 查角色授予的变量权限:
SELECT * FROM information_schema.role_table_grants WHERE grantee = "'user'@'host'" AND privilege_type IN ('SYSTEM_VARIABLES_ADMIN', 'SESSION_VARIABLES_ADMIN', 'CONNECTION_ADMIN'); - 查直接授权:
SHOW GRANTS FOR 'user'@'host'; - 验证是否拦截成功:
SELECT @@global.max_connections;应返回Access denied; you need (at least one of) the SYSTEM_VARIABLES_ADMIN privilege(s)
特别注意 host 匹配——如果应用连的是 'app'@'10.20.30.40',而你只撤销了 'app'@'%',那照样能执行。
关键配置字段还得靠触发器硬拦截
SQL 层权限只能管“谁可以执行 SET”,但拦不住运维手动 UPDATE system_config SET pay_timeout = 9999 这类直连修改。对业务关键字段(如 pay_timeout、max_retry_count),必须加 BEFORE UPDATE 触发器:
DELIMITER $$
CREATE TRIGGER protect_config BEFORE UPDATE ON system_config
FOR EACH ROW
BEGIN
IF OLD.pay_timeout != NEW.pay_timeout THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'pay_timeout is read-only';
END IF;
END$$
DELIMITER ;
触发器里别查表、别调函数、别写日志表——锁住主表的同时再写日志,高并发下极易死锁。真要留痕,只记最简字段:table_name、pk_id、old_value、new_value、CURRENT_USER()。











