MySQL 只读库踩坑实录:为什么 INSERT/UPDATE 不报错,DELETE 却直接炸了?

在一次线上问题排查中,遇到了一个非常"反直觉"的现象:
新增、修改操作看起来都成功了,但数据库里却没有任何数据变化;
而删除操作却直接抛异常。


一、问题现象

系统运行过程中,出现了如下情况:

  • 新增 / 编辑接口

    • 接口返回成功

    • 程序日志无异常

    • 数据库中无任何数据变更

  • 删除接口

    • 一执行就直接失败

    • Hibernate 抛出异常

异常信息如下:

org.hibernate.exception.GenericJDBCException: JDBC exception executing SQL [delete from system_operation_log where create_time <= ?] [The MySQL server is running with the --read-only option so it cannot execute this statement]

从表面看,这个问题非常矛盾:

如果数据库是只读的,

为什么 INSERT / UPDATE 不报错,

DELETE 却会立刻失败?


二、根本原因:MySQL 开启了只读模式

异常信息中已经明确给出了关键线索:

java 复制代码
The MySQL server is running with the --read-only option

说明当前连接的 MySQL 实例处于只读状态,常见原因包括:

  • 连接的是 MySQL 从库

  • 主库被临时设置为:

    • read_only = ON

    • super_read_only = ON

  • 云数据库发生主从切换,应用未及时切换连接

可以通过如下 SQL 验证:

sql 复制代码
SHOW VARIABLES LIKE 'read_only';

SHOW VARIABLES LIKE 'super_read_only';

只要其中任意一个为 ON,写操作都会受到限制。


三、为什么 INSERT / UPDATE 不报错?

很多人第一次遇到这个问题都会被误导,其核心原因在于:

👉 Hibernate 采用了延迟执行 SQL 的机制


3.1 Hibernate 的延迟执行机制

在使用 JPA / Hibernate 时:

java 复制代码
repository.save(entity);

这一步并不会立刻执行 SQL,而是:

  • 将实体对象放入 Persistence Context(一级缓存)

  • 标记为"待插入 / 待更新"

  • 等待事务提交或 flush 时才真正执行

真正发送 SQL 的时机通常是:

  • 事务提交(commit)

  • 显式调用 flush()

  • 查询前触发自动 flush


3.2 只读库 + 事务 = "假成功"

当数据库处于只读状态时:

  • Hibernate 可以正常生成 INSERT / UPDATE SQL

  • 但在事务提交或 flush 阶段:

    • SQL 被数据库拒绝

    • 事务回滚

  • 异常被框架处理或被吞掉

最终表现为:

Java 代码无异常

接口返回成功

数据库无任何变化

这种情况在以下场景中尤为常见:

  • 使用了 @Transactional

  • 上层代码 catch 了异常但未抛出

  • 定时任务 / 异步任务

  • Spring 的只读事务优化


四、为什么 DELETE 会立刻报错?

DELETE 操作与 INSERT / UPDATE 最大的不同在于:

👉 DELETE 必须立即执行 SQL 并返回影响行数

例如:

sql 复制代码
delete from system_operation_log where create_time <= ?

其执行流程为:

Hibernate

JDBC

MySQL(只读模式)

立即抛 SQLException

因此:

  • DELETE 无法被延迟

  • MySQL 会立刻拒绝写操作

  • Hibernate 直接抛出 GenericJDBCException


五、INSERT / UPDATE / DELETE 行为对比

操作类型 Hibernate 执行方式 只读库下表现
INSERT 延迟执行 不一定立刻报错
UPDATE 延迟执行 不一定立刻报错
DELETE 立即执行 立刻报错

这正是问题"看起来很诡异"的根本原因。


六、如何快速定位和验证问题?

6.1 确认当前连接的数据库角色

sql 复制代码
SELECT @@hostname, @@read_only, @@super_read_only;

6.2 检查是否使用了只读事务

java 复制代码
@Transactional(readOnly = true)

在只读事务中:

  • Hibernate 可能直接跳过 flush

  • 不报错,但永远不会写库


6.3 检查是否使用了读写分离数据源

常见风险点包括:

  • AbstractRoutingDataSource

  • AOP 动态切换数据源

  • MyBatis / JPA 混用

  • 删除走主库,新增却被路由到从库


6.4 强制 flush 验证(非常有效)

java 复制代码
repository.save(entity);

entityManager.flush();

如果这里直接抛出只读异常,

可以 100% 确认:当前连接的是只读库


七、经验总结

新增修改不报错却没数据,删除直接报错,99% 是连错库了。

这并不是 Hibernate 的 Bug,而是:

  • MySQL 只读模式

  • Hibernate 延迟执行机制

  • Spring 事务处理

三者叠加后的典型"假成功"陷阱。


八、实践建议

  • 明确主从库职责,写操作必须走主库

  • 关键写操作可显式调用 flush() 进行早失败

  • 数据库主从切换后第一时间检查 read_only

  • 定时清理类 DELETE 操作务必绑定主库


九、结语

如果你正在使用:

  • Spring Boot + JPA

  • MySQL 主从复制

  • 读写分离架构

那么这个问题几乎是必踩的一坑。

希望这篇文章能帮你少踩一次坑。

欢迎在评论区分享你遇到过的读写分离或事务相关问题 👇

相关推荐
没事偷着乐琅2 小时前
二、Pandas 是啥 是数据库吗?
数据库·pandas
rfidunion2 小时前
busybox1.20.2编译过程
数据库
fengsen52113142 小时前
MySQL--》如何在MySQL中打造高效优化索引
android·mysql·adb
_codemonster3 小时前
JavaWeb开发系列(八)数据库环境配置
数据库
小刘的大模型笔记3 小时前
向量数据库实战指南:从部署到RAG落地
数据库
Hello.Reader3 小时前
从 0 到 1 理解硬盘数据恢复工具原理与工程实现
linux·运维·服务器·网络·数据库
前路不黑暗@3 小时前
Java项目:Java脚手架项目的地图服务(十)
java·数据库·spring boot·笔记·学习·spring cloud·maven
世界尽头与你3 小时前
MySQL 三大日志(binlog、redo log 和 undo log)深度解析
数据库·mysql
a285283 小时前
MS SQL Server 实战 统计与汇总重复记录
数据库·oracle