要让从库真正只读,需同时启用read_only=on和super_read_only=on,前者拦截普通用户写入但放行复制线程,后者阻止super权限用户(如root)执行写操作及元数据变更,二者协同方可实现防误写且不影响复制。

MySQL只读模式不是单一开关,而是分层控制:普通用户写入拦截靠 read_only,连 root 都拦住得开 super_read_only,而整个实例跑在只读介质上则必须用 --innodb-read-only=1 启动参数。选错层级,就挡不住写操作。
怎么让从库真正只读(防止误写 + 不影响复制)
从库只读的核心目标是:允许 SQL 线程正常回放主库 binlog,但禁止所有人工或应用发起的写入。
-
read_only = ON是基础——它放行复制线程(因为复制线程不走用户权限校验),但阻断普通用户的INSERT/UPDATE/DELETE/DROP等操作; - 必须搭配
super_read_only = ON(MySQL 5.7.8+),否则root或其他SUPER权限用户仍能执行写操作,包括CREATE FUNCTION、INSTALL PLUGIN、ANALYZE TABLE等元数据变更; - 验证是否生效:
SELECT @@global.read_only, @@global.super_read_only;两个都应返回1; - 注意:
read_only和super_read_only都是动态变量,可在线设置,但super_read_only = ON会自动把read_only设为ON,反之read_only = OFF会强制关掉super_read_only。
为什么开了 read_only 还能写?常见绕过点
不是设了 read_only = ON 就万事大吉——以下情况仍可能触发写入:
-
SUPER权限用户未受控:比如运维用root@localhost登录后执行UPDATE,不会报错; - 临时表不受限:
CREATE TEMPORARY TABLE和后续INSERT依然允许,哪怕read_only = ON; - 存储过程内部写入:如果过程体里有
INSERT,且调用者有执行权限,read_only不拦截; - 系统表操作被忽略:如
PURGE BINARY LOGS、RESET MASTER等管理命令,read_only默认不限制(但super_read_only = ON会拦); - 未检查复制状态:设完
read_only后,务必确认SHOW SLAVE STATUS\G中Seconds_Behind_Master正常更新,避免因权限或锁导致 SQL 线程卡住。
如何让 MySQL 在只读文件系统上启动(CD/DVD/只读挂载)
这不是运行时开关,而是启动前必须准备的硬性约束——read_only 和 super_read_only 在这种场景下完全无效,必须用 --innodb-read-only=1。
- 关键前提:先关闭变更缓冲(
innodb_change_buffering=0),再执行慢关闭(SET GLOBAL innodb_fast_shutdown = 0),确保所有脏页刷盘; - 启动时必须显式指定可写路径:
--pid-file=/tmp/mysql.pid、--innodb-temp-data-file-path=../../../tmp/ibtmp1:12M:autoextend(路径需相对于数据目录); -
--event-scheduler=disabled必须加上,否则事件调度器尝试写日志会失败; - MySQL 8.0 起,
innodb_read_only = 1会禁止所有引擎的建表/删表(因修改数据字典)、CREATE USER、GRANT等操作——这些不是“写数据”,而是改系统表,InnoDB 只读模式一并封死; - 该模式不可动态开启,只能通过配置文件或启动参数设置,且修改后必须重启实例。
别混淆 innodb_read_only 和 read_only
这两个参数名字像,但作用域和生效方式完全不同:
-
innodb_read_only是 InnoDB 存储引擎级只读开关,影响所有 InnoDB 表(包括mysql系统库中的表),MySQL 8.0 起也限制非 InnoDB 表的 DDL;它是只读变量,不能SET GLOBAL修改,只能在配置文件中设并重启; -
read_only是服务器级逻辑开关,对所有非临时表生效,但依赖权限体系——它不碰磁盘文件,只是 SQL 层拦截;可动态启用/禁用,且不影响复制线程; - 日常运维中,99% 的场景只需配
read_only+super_read_only;只有当你把数据库刻进光盘、或挂载到只读 NFS 时,才轮到innodb_read_only出场。
真正难的不是开开关,而是判断“谁需要被挡住”:是防应用误写?还是防 DBA 自己手抖?或是物理介质根本不允许写?没想清楚这点,read_only = ON 就只是个心理安慰。











