先查SELECT @@global.read_only, @@global.super_read_only:若任一为1,属实例级只读;若均为0但仍报错,则是文件系统级只读,需立即检查df -h磁盘使用率、mount挂载是否ro、SELinux/AppArmor及目录权限。

确认是 MySQL 实例级只读,还是文件系统级只读
报错 Table is read only 看似指向单张表,但 MySQL 本身没有“表级只读”开关(innodb_read_only 是引擎级、影响全部 InnoDB 表)。真实原因只有两类:MySQL 进程被全局设为只读,或它根本写不了磁盘。先快速区分:
- 执行
SELECT @@global.read_only, @@global.super_read_only;—— 若任一返回1,是实例级只读 - 若两者都是
0,但写操作仍失败,立刻查磁盘:运行df -h看/var/lib/mysql所在分区是否Use%到 100%,再跑mount | grep "$(df /var/lib/mysql | tail -1 | awk '{print $1}')" | grep ro确认是否只读挂载 - 别信
SHOW TABLE STATUS里的Comment字段——它从不显示只读状态,纯属误导
read_only 和 super_read_only 必须按顺序关掉
super_read_only=ON 会锁死 read_only 的修改权限,哪怕你是 SUPER 用户。所以不能跳步:
- 先执行
SET GLOBAL super_read_only = OFF;(需SUPER权限) - 再执行
SET GLOBAL read_only = OFF; - 如果第二步报错
ERROR 1290,说明第一步没成功,回去检查super_read_only是否真变0 - 注意:这些
SET GLOBAL只对新连接生效。已有连接仍可能报错,应用需重连或等连接池刷新
文件系统权限和 SELinux/ AppArmor 是静默杀手
即使 read_only=0,MySQL 进程(通常是 mysql:mysql)若无法写入数据目录,就会自动 fallback 到只读模式,且不报明确错误。
- 检查目录权限:
ls -ld /var/lib/mysql/yourdb/—— 属主必须是mysql,权限至少含drwxr-x--- - 检查文件所有权:
ls -l /var/lib/mysql/yourdb/*.ibd—— 所有.ibd文件也得是mysql:mysql,不能是root:mysql - SELinux 干扰:CentOS/RHEL 上运行
sudo ls -Z /var/lib/mysql/yourdb/,上下文必须是system_u:object_r:mysqld_db_t:s0;否则用sudo semanage fcontext -a -t mysqld_db_t "/var/lib/mysql(/.*)?"+sudo restorecon -Rv /var/lib/mysql - AppArmor 干扰:Ubuntu 上
sudo aa-status | grep mysql若有输出,临时禁用测试:sudo ln -s /etc/apparmor.d/usr.sbin.mysqld /etc/apparmor.d/disable/ && sudo apparmor_parser -R /etc/apparmor.d/usr.sbin.mysqld
配置文件里的 read_only=1 比变量更顽固
如果改了变量马上又变回 ON,八成是配置文件写死了。别只看 /etc/my.cnf,MySQL 启动时会按顺序加载多个路径:
- 检查
/etc/mysql/mysql.conf.d/mysqld.cnf(Debian/Ubuntu) - 检查
/etc/my.cnf.d/server.cnf(RHEL/CentOS) - 检查
mysqld --help --verbose | grep "Default options"输出的全部路径 - 找到所有含
read_only = 1或super_read_only = 1的行,注释掉或删掉 - 改完必须重启 MySQL:
sudo systemctl restart mysql(不是 reload)
磁盘满、文件系统只读、SELinux 拦截这三类问题不会留下明显日志线索,排查时容易漏掉。每次看到 Table is read only,先做这三件事:查 df -h、查 mount 输出、查 sudo ls -Z 或 aa-status,比反复 SET 变量快得多。











