只读事务是向数据库声明“仅查询、不修改”,从而跳过写锁、日志记录等开销以提升性能;适用于用户展示、分页列表、报表统计等纯查询场景,需确保方法内无任何写操作且处于有效事务上下文中。

只读事务不是“加功能”,而是明确告诉数据库“这次只查,不改”,从而触发底层优化。关键在于让数据库跳过写锁、日志记录、事务状态维护等开销,提升查询性能和并发吞吐。
什么时候该用 readOnly=true
适用于纯查询场景,比如:用户资料展示、商品列表分页、报表统计、缓存预热等。只要方法里没有 insert/update/delete 操作,且不调用其他可能修改数据的内部方法,就可以安全标记为只读。
- 方法体中只调用 select 查询(MyBatis 的 @Select、JPA 的 @Query(value = "...", nativeQuery = true) 等)
- 未调用任何含 @Transactional(readOnly = false) 或无声明的更新类服务方法
- 不涉及 save()、update()、delete()、executeUpdate() 等写操作
怎么正确配置只读事务
@Transactional(readOnly = true) 必须在事务真正启动时生效。这意味着:它只对被 Spring 代理的方法起作用,且该方法必须处于事务上下文中(例如由 propagation=REQUIRED 触发)。
Java项目代码review工具。分析Git变更+完整调用链路上下文,推断业务需求,进行多维度评分和分类汇总,生成完整PRD文档。包含细粒度Java代码审查清单(Null安全、异常处理、Streams、并发、equals/hashCode、资源管理、API设计、性能、MyBatis/ORM、事务边界、SQL/DD...
- 如果方法本身不开启事务(如 propagation=NOT_SUPPORTED),readOnly 设置会被忽略
- 类上标注 @Transactional(readOnly = true),所有 public 方法默认只读;方法上覆盖时可设为 readOnly = false 来局部放开
- 搭配 isolation 和 timeout 使用更稳妥,例如:
@Transactional(readOnly = true, timeout = 10, isolation = Isolation.READ_COMMITTED)
只读事务的实际效果与注意事项
不同数据库表现略有差异,但主流行为一致:MySQL InnoDB 在 READ COMMITTED 或 REPEATABLE READ 下,只读事务通常不分配 undo log、不持有行锁(除非显式加锁),也不会触发 binlog 写入(取决于 binlog_format 和 transaction_write_set_extraction 配置)。
- 误在只读事务里执行 update,会直接抛出
SQLException: Connection is read-only - Spring 不会自动检测 SQL 类型,所以 readOnly 是“契约式声明”,靠开发者保障语义正确
- 对简单单表查询,优化效果可能不明显;但在高并发、复杂联查或大结果集场景下,能显著降低锁竞争和日志压力
配合其他手段进一步优化查询
只读事务是基础一环,建议组合使用:
- 开启二级缓存(如 MyBatis 的 @CacheNamespace)避免重复查库
- 对固定维度报表,考虑物化视图或离线计算+缓存
- 使用数据库连接池的 read-only 连接属性(如 HikariCP 的
connectionInitSql=SET SESSION TRANSACTION READ ONLY) - JPA 场景下,配合
@QueryHints(@QueryHint(name = "org.hibernate.readOnly", value = "true"))告知 Hibernate 实体只读
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










