read_only=on后普通用户仍能写入,是因为当前连接用户实际拥有super或system_variables_admin权限,该变量仅在会话建立时检查权限,不拦截已存在的长连接;真正全局只读需同时配置super_read_only=on并回收高危权限。

为什么read_only = ON后普通用户还能写入?
这通常不是配置失败,而是连接用户权限没被正确识别。MySQL 的 read_only 只在「会话建立时」检查用户是否拥有 SUPER 或 SYSTEM_VARIABLES_ADMIN 权限——如果当前连接用户没有这两个权限,read_only = ON 就会生效;但如果用户是 'app'@'10.0.1.%' 且你误授了 GRANT SUPER ON *.*,那它照样能绕过。
验证方式很简单:
-
SELECT CURRENT_USER(), USER();看实际登录身份 -
SHOW GRANTS FOR CURRENT_USER();确认输出里不含SUPER或SYSTEM_VARIABLES_ADMIN
SET GLOBAL read_only = ON 为什么对已有连接无效?
read_only 是全局变量,但它的作用对象是「新建立的连接」。已存在的长连接(比如应用池里复用的连接)仍按旧状态运行,INSERT/UPDATE 不会报错。
实操建议:
- 执行
SET GLOBAL read_only = ON后,必须让所有应用重连,或手动踢掉旧连接:KILL对应的ID(查SHOW PROCESSLIST) - 容器或云环境可滚动重启应用 Pod / 实例,避免手动 KILL 风险
- 别依赖“设完就安全”,重点盯住连接生命周期
怎样让 read_only 持久化且真正锁死普通用户?
仅靠 SET GLOBAL 是临时的,MySQL 重启即失效。持久化必须进配置文件,且顺序和位置不能错:
- 确认 MySQL 实际加载的配置路径:
mysqld --verbose --help | grep "Default options" - 在正确的
[mysqld]段下写入(不是[client]或[mysql]):
read_only = ON super_read_only = ON
注意:两行顺序不能颠倒,否则 MySQL 启动失败;值必须用 ON 或 OFF,不接受 1/true。
MySQL 8.0.22+ 可改用:SET PERSIST super_read_only = ON;,它会写入 mysqld-auto.cnf,优先级高于 my.cnf,适合无法直接改配置的场景。
为什么开了 read_only 还要配 super_read_only?
因为 read_only = ON 默认只拦非超级用户,而很多业务账号(比如监控脚本、备份工具、甚至部分中间件)会悄悄用高权限账号连接——一旦它们存在,read_only 就形同虚设。
super_read_only = ON 才是生产环境该开的开关,它:
- 自动把
read_only设为ON - 禁止所有用户(含
root@localhost)执行写操作,包括DROP TABLE、ANALYZE TABLE、INSTALL PLUGIN - 让
SET GLOBAL read_only = OFF直接报ERROR 1290,除非先关掉super_read_only
真正起效的关键点不在参数本身,而在有没有同时回收高权限账号的 SUPER 权限,以及有没有验证从库 SQL 线程是否仍在正常回放(它不受 super_read_only 影响,这是设计保障)。











