read_only=ON不等于只读账号,因其仅限制非SUPER用户写操作,而真正只读账号必须显式授予SELECT和USAGE权限、回收LOCK TABLES等隐式写权限,并在各实例单独初始化。

read_only 参数本身不控制账号权限,只限制用户行为;真正实现“只读账号”必须靠 GRANT SELECT + 显式 REVOKE + USAGE 组合,否则账号要么连不上,要么能绕过只读限制。
为什么只设 read_only = ON 不等于有了只读账号
从库上执行 SET GLOBAL read_only = ON 后,普通用户确实无法执行 INSERT、UPDATE 等语句,但这个开关对拥有 SUPER 权限的账号完全无效。而很多应用或 DBA 习惯用 root 或高权限账号直连从库——一旦误操作,数据一致性就崩了。更关键的是:read_only 是实例级配置,不是账号级策略;它不阻止账号连接,也不影响权限继承逻辑。
CREATE USER 和 GRANT 必须拆开写(MySQL 8.0+ 强制)
MySQL 8.0 起禁止在一条语句里创建用户并授权,否则报错 ERROR 1064 (42000)。常见错误写法:GRANT SELECT ON app_db.* TO 'ro'@'%' IDENTIFIED BY 'pwd'。
- 先创建用户(注意认证插件兼容性):
CREATE USER 'ro_app'@'192.168.10.%' IDENTIFIED WITH mysql_native_password BY 'StrongPass!2026'; - 再授连接权:
GRANT USAGE ON *.* TO 'ro_app'@'192.168.10.%';(缺这句会Access denied) - 再授业务库只读权:
GRANT SELECT ON `app_db`.* TO 'ro_app'@'192.168.10.%'; - 如需查表结构,补
information_schema授权:GRANT SELECT ON `information_schema`.`TABLES` TO 'ro_app'@'192.168.10.%';
必须显式收回隐式写权限,尤其是 LOCK TABLES
很多人只授 SELECT 就以为安全了,但 MySQL 默认给新账号附带 LOCK TABLES 权限——它虽不修改数据,却能阻塞复制线程(比如 FLUSH TABLES WITH READ LOCK),导致 Seconds_Behind_Master 飙升。更隐蔽的是:SHOW VIEW、EXECUTE 在某些场景下也可能触发元数据写入。
- 检查当前权限:
SHOW GRANTS FOR 'ro_app'@'192.168.10.%'; - 显式回收:
REVOKE LOCK TABLES, CREATE, DROP, ALTER, INDEX, INSERT, UPDATE, DELETE, EXECUTE ON *.* FROM 'ro_app'@'192.168.10.%'; - 确认生效:
FLUSH PRIVILEGES;(跳过系统库启动时尤其关键)
云数据库只读实例的账号要单独初始化,不能依赖主库同步
阿里云 RDS、腾讯云 CDB 的只读实例默认 read_only = 1,但 mysql.user 表不会从主库复制过来——这是 MySQL 复制机制决定的(mysql 库默认不复制)。你在主库上 CREATE USER,只读实例上根本没这个账号,应用一连就报 Access denied。
- 导出主库账号定义:
mysqldump -u root -p --no-data --skip-triggers mysql user db > users.sql - 过滤掉非只读权限语句(删掉所有
INSERT INTO mysql.user和含INSERT/UPDATE的GRANT) - 在每个只读实例上执行清理后的 SQL,并确保
IDENTIFIED WITH插件一致(5.7 用mysql_native_password,8.0+ 默认caching_sha2_password)
真正的只读账号不是“没给写权限”,而是“只给了 SELECT + USAGE + information_schema 查阅权”,且每个实例独立部署、权限逐条核验。漏掉 USAGE 连不上,漏掉 information_schema 查不了结构,漏掉 REVOKE LOCK TABLES 可能卡死复制——这些点全在一条线上,断一处,整套读写分离就不可信。


















