SELECT FOR UPDATE 在 Oracle 中只锁查询结果集的行,条件无索引会升级为表级锁;需确保 WHERE 字段有索引、避免 JOIN 后过滤、优先用主键、禁用先查后更新;JDBC 需手动事务、PreparedStatement 和原生 SQL;超时应设 WAIT n 而非 NOWAIT;Hibernate 的 setLockMode 多数场景无效,应验生成 SQL 或绕过 ORM。
SELECT FOR UPDATE 在 Oracle 中到底锁什么
它只锁查询结果集里的行,不是锁整个表,也不是锁语句执行期间所有相关行。如果你 select ... for update 没带 where 条件,或者条件没走索引,就会升级成表级锁(或大量行锁),拖垮并发。
实操建议:
- 确保
WHERE条件字段有有效索引,用EXPLAIN PLAN确认走了索引扫描,而不是全表扫描 - 避免在
FOR UPDATE语句里 JOIN 多张表后才过滤——Oracle 可能锁住中间结果的多行,甚至无关行 - 如果业务允许,用主键或唯一约束字段做条件,锁粒度最精准
- 不要在事务里先
SELECT再UPDATE—— 这样查不到最新数据,也根本没加锁,纯属“幻读温床”
Java 里怎么正确触发 SELECT FOR UPDATE
JDBC 默认不支持自动加锁,必须显式控制事务隔离级别和 SQL 语法。Spring 的 @Transactional 不会帮你加 FOR UPDATE,那是 SQL 层的事。
实操建议:
- 用
Connection.setAutoCommit(false)手动开启事务,别依赖框架默认传播行为 - 执行带
FOR UPDATE的 SQL 时,必须用PreparedStatement,且不能用 Hibernate 的find()或 JPQL —— 它们生成的 SQL 不含FOR UPDATE - Oracle JDBC 驱动要求:URL 中加上
oracle.jdbc.readTimeout=0(否则网络抖动可能中断锁等待) - 若用 MyBatis,写 XML 时直接拼
FOR UPDATE,并确保fetchSize设为 1(防止驱动预取多行导致锁范围扩大)
ORA-00054: resource busy 错误怎么设超时
这是最常见的并发失败信号,本质是另一事务还没释放锁,当前会话等不及了。Oracle 默认不超时,卡死直到对方提交/回滚,生产环境绝不能接受。
立即学习“Java免费学习笔记(深入)”;
实操建议:
- 在
SELECT ... FOR UPDATE后加WAIT n,比如FOR UPDATE WAIT 3表示最多等 3 秒,超时抛ORA-30006(不是 00054) - 别用
NOWAIT直接报错——它把重试逻辑甩给上层 Java,容易引发雪崩式重试,应改用WAIT+ 明确秒数 - Java 层捕获
SQLException时,检查getSQLState()是否为61000(ORA-30006)或ORA-00054,再决定退避重试还是降级 - 注意:
WAIT时间受数据库参数enqueue_resources影响,若并发连接数高但该值低,可能提前报错
为什么 setLockMode(LockMode.PESSIMISTIC_WRITE) 不起作用
Hibernate 的这个方法只对 HQL/JPQL 生效,而且仅在 Oracle 12c+、配合特定方言(如 Oracle12cDialect)时才会生成 FOR UPDATE。多数老项目用的是 Oracle10gDialect,它压根不识别这个锁模式。
实操建议:
- 别信 Hibernate 文档里“自动翻译”的说法,用
show_sql=true实际看生成的 SQL,没FOR UPDATE就是无效 - 如果必须用 JPA,改用原生 SQL 查询:
em.createNativeQuery("SELECT * FROM t WHERE id = ? FOR UPDATE WAIT 2") - 更稳妥的做法:绕过 ORM,用
JdbcTemplate或DataSourceUtils.getConnection()拿原生 Connection 执行带锁 SQL - 特别注意:Hibernate 的二级缓存和查询缓存会干扰锁行为——同一行被缓存后,后续请求可能跳过 DB 查询,自然也不触发锁
锁的边界永远在 SQL 和事务里,不在 Java 对象生命周期里。任何想靠“对象加锁”“服务层同步”解决行级并发的方案,都会在高并发下漏锁或死锁。










