FLUSH TABLES WITH READ LOCK 可实现真正全局只读,但需配对执行 UNLOCK TABLES,否则阻塞所有写操作并影响主从复制;read_only=ON 仅限制非SUPER用户,SUPER用户仍可写;升级时需结合权限控制、监控拦截及分步执行 mysql_upgrade。

如何用 FLUSH TABLES WITH READ LOCK 设置全局只读
这命令会阻塞所有写操作(包括 DML 和 DDL),让 MySQL 进入“真正只读”状态,适合升级前的短暂冻结。但它不是设置一个开关,而是加锁动作——必须配对执行 UNLOCK TABLES,否则连接卡死、后续操作全堵住。
常见错误现象:SHOW PROCESSLIST 里一堆 Waiting for table flush;应用报错 Lock wait timeout exceeded;甚至主从复制中断(因为从库 SQL 线程也被锁住)。
- 只在单实例维护时用,主从架构下必须先停掉从库的 SQL 线程(
STOP SLAVE SQL_THREAD),否则锁会传播过去 - 它不阻止新连接建立,但新连接一旦执行写语句就会被挂起——所以升级脚本开头要先
KILL掉活跃写事务(查information_schema.INNODB_TRX) - 锁持有期间不能执行
mysqldump --single-transaction(InnoDB 快照不受影响),但--lock-all-tables会冲突
SET GLOBAL read_only = ON 的真实行为边界
这个配置看起来最“干净”,但它只对非 SUPER 权限用户生效,SUPER 用户(比如 root、监控账号、备份工具常用账号)照样能写。很多升级失败就栽在这儿:以为开了 read_only 就万事大吉,结果备份脚本或健康检查探针偷偷插了一条记录。
使用场景:适合主从切换后的从库保护,或长期只读实例;但升级窗口期必须配合权限收紧,不能单独依赖。
- 修改后需确认生效:
SELECT @@global.read_only(注意是@@global,不是@@session) - 某些云厂商 RDS 默认禁用该变量写入(如阿里云 MySQL 5.7+),会报错
Variable 'read_only' is a read only variable - MySQL 8.0.12+ 支持
super_read_only,它连 SUPER 用户也拦住,但开启后无法执行UNLOCK TABLES,和FLUSH TABLES WITH READ LOCK冲突
升级中误触发写操作的典型来源
你以为关了写,其实还有三类“隐身写入”在跑:定时任务、监控采集、应用自身兜底逻辑。它们往往绕过业务权限控制,直连 root 或高权限账号。
- Percona Toolkit 工具(如
pt-online-schema-change)默认用root连接,开read_only后仍可运行,必须显式加--set-vars read_only=0 - Zabbix、Prometheus 的 MySQL exporter 会执行
SHOW GLOBAL STATUS,安全;但有些自定义脚本会顺手INSERT INTO metrics_log记录采集时间戳 - 应用层连接池(如 HikariCP)可能配置了
connection-test-query = SELECT 1,没问题;但如果设成INSERT INTO heartbeat ...,那就直接撞墙
推荐组合策略:短锁 + 权限 + 监控三重拦截
没有银弹。真正可靠的升级只读保障,得靠三层过滤:用 FLUSH TABLES WITH READ LOCK 拦住当前所有写事务,用 read_only = ON + 权限回收挡住新写请求,再用 performance_schema.events_statements_summary_by_digest 实时盯住有没有漏网的 INSERT/UPDATE。
容易被忽略的一点:MySQL 升级包里的 mysql_upgrade 工具在 5.7→8.0 迁移时会自动执行 DDL(比如重建 mysql.user 表),它需要写权限——这意味着你不能在 read_only = ON 状态下直接跑升级,得提前切回可写,或改用 --upgrade-system-tables 分步执行。










