read_only=on拦不住root和运维账号,因其仅限制无super或system_variables_admin权限的用户,而mysql 8.0.12+默认授予root@localhost这两项权限,故insert等操作仍成功;super_read_only=on需以read_only=on为前提、持久化配置且配合权限回收才真正生效。

read_only=ON 为什么拦不住 root 和运维账号
因为 read_only 不是权限控制机制,它只对没有 SUPER 或 SYSTEM_VARIABLES_ADMIN 权限的用户生效。MySQL 8.0.12+ 默认给 root@localhost 同时授予这两个权限,所以即使 SELECT @@read_only 返回 1,INSERT 依然成功。
常见错误现象:监控脚本用 root 连从库写心跳表、DBA 临时连上去执行 ALTER TABLE、备份工具带 SUPER 权限直连——这些操作全都不受 read_only 限制。
-
read_only是会话级检查,不校验账号角色,也不区分连接来源 - 只要用户有
SUPER(或 MySQL 8.0+ 的SYSTEM_VARIABLES_ADMIN),就能绕过 -
SHOW GRANTS FOR 'root'@'localhost'里出现SUPER或SYSTEM_VARIABLES_ADMIN就意味着“只读”形同虚设
super_read_only=ON 为什么还是没锁死写入
super_read_only 确实能拦住 SUPER 用户,但它有三个硬性前提:必须 read_only=ON 已启用、MySQL 版本 ≥5.7.20、且不能只靠 SET GLOBAL 动态设置。
容易被忽略的失效点:
-
super_read_only=ON在read_only=OFF时自动失效(MySQL 5.7/8.0 行为一致) - 仅执行
SET GLOBAL super_read_only = ON,服务重启后丢失;MySQL 5.7 必须同步改my.cnf,MySQL 8.0 推荐用SET PERSIST - 云厂商 RDS 常禁用该变量动态修改,得走控制台申请或白名单开通
哪些写操作会被 super_read_only 漏掉
即使 super_read_only=ON 和 read_only=ON 都生效,仍有两类操作可能绕过限制并污染 GTID 或 binlog:
-
SET sql_log_bin = OFF:拥有SUPER权限的用户可关闭 binlog 记录,本地写入但不产生 GTID,后续主从切换时数据丢失或冲突 -
FLUSH TABLES WITH READ LOCK或FLUSH LOGS:这类管理语句不被只读机制拦截,但会生成新 GTID(如腾讯云案例中 flush tables 导致从库 GTID 多出一条) - 复制线程自身写入不受限——这是设计使然,但若
log_slave_updates=OFF,从库手动建表后又提升为主库,下游从库直接断复制
真正封死从库写入的实操组合
单靠一个变量永远不够,必须配置 + 权限 + 验证三步闭环:
- 在
my.cnf的[mysqld]段落同时写死:read_only=ON和super_read_only=ON(MySQL 5.7+) - 回收高权限:
REVOKE SUPER, SYSTEM_VARIABLES_ADMIN ON *.* FROM 'root'@'localhost';(MySQL 8.0.12+ 必须两项都 revoke) - 验证是否生效:
SELECT @@read_only, @@super_read_only;返回两个1,再用目标账号执行INSERT,应报错ERROR 1290 (HY000) - 额外加固:禁用
sql_log_bin修改能力,可通过SET sql_log_bin = OFF是否报错来测试
最常被跳过的环节是权限回收——很多人设了参数就以为万事大吉,却忘了 root 还握着钥匙。











