@Transactional(readOnly = true) 是性能优化提示而非写操作锁,核心是调用 Connection.setReadOnly(true)、Hibernate 设 FlushMode.NEVER,并依数据库支持程度降低锁、日志或缓存开销;实际效果取决于驱动与DB实现,误写会抛 SQLException 或 HibernateException。

Spring 中的 @Transactional(readOnly = true) 不是强制禁止写操作的“锁”,而是一个性能优化提示,核心作用是让底层 JDBC 连接和 ORM 框架(如 Hibernate/JPA)按只读场景做轻量级处理,从而减少资源开销。
它实际做了什么
当设置 readOnly = true 时,Spring 事务管理器(如 DataSourceTransactionManager 或 JpaTransactionManager)会在事务开启阶段执行:
- 调用底层
Connection.setReadOnly(true),向数据库驱动传递只读信号 - 对 Hibernate:自动设为
FlushMode.NEVER,禁用 session 自动 flush,避免无谓的脏检查和 SQL 预生成 - 对 JPA(如 Hibernate 实现):跳过一级缓存写入、不注册实体变更监听、不准备回滚段(Oracle)或减少锁粒度(MySQL)
不同数据库的效果差异
这个提示是否生效,取决于数据库驱动和数据库本身的支持程度:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
- Oracle:真正启用只读事务机制,不分配回滚段、不写 redo log,效果明显
-
MySQL(InnoDB):5.6+ 版本支持
SET SESSION TRANSACTION READ ONLY,可降低 MVCC 快照维护开销;但部分旧驱动可能忽略该设置 -
PostgreSQL:支持
BEGIN READ ONLY,能避免行级锁和 WAL 写入 - H2 / HSQLDB:多数仅作标记,无实质优化
- 若数据库不支持,
setReadOnly(true)可能静默失败,Spring 日志会提示 “Could not set JDBC Connection read-only”
怎么正确使用才有效
只读事务不是加了注解就自动提速,需配合业务逻辑和数据访问方式:
立即学习“Java免费学习笔记(深入)”;
- 确保方法内确实只执行
SELECT,不调用insert/update/delete或任何触发写行为的操作(如 MyBatis 的insert、JPA 的save()、Hibernate 的merge()) - 避免在
readOnly=true方法中调用未标注事务的方法,后者若内部执行写操作,仍可能污染连接(尤其使用同一EntityManager或Session时) - 搭配
propagation = Propagation.SUPPORTS更灵活:若外层已有事务,就复用;若没有,也不强求开启新事务,适合纯查询场景 - 类级别统一配置 + 方法级覆盖:例如
@Service @Transactional(readOnly = true)标在服务类上,再对save()、update()等方法显式标注@Transactional(readOnly = false)
常见误区与异常
一旦违反只读约定,运行时可能报错,典型表现:
-
java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed—— JDBC 层直接拦截 DML -
org.hibernate.HibernateException: FlushMode.NEVER cannot be used with write operations—— Hibernate 在 flush 时发现有 pending change - 没报错但性能无提升?检查是否用了不支持只读的数据库/驱动,或事务未真正生效(如 self-invocation 导致代理失效)

















