SELECT ... FOR UPDATE必须在显式事务中使用,ojdbc8默认autocommit=true导致锁立即释放;需调用setAutoCommit(false),配合NOWAIT、SKIP LOCKED或OF子句控制锁范围与行为,并用独立PreparedStatement执行后续UPDATE。

SELECT ... FOR UPDATE 语句必须显式开启事务
Oracle 的行级锁不会在自动提交模式下生效,ojdbc8 默认启用 autoCommit = true,此时执行 SELECT ... FOR UPDATE 后立即提交,锁瞬间释放,根本达不到“控制”效果。必须先调用 connection.setAutoCommit(false)。
- 未关闭 autoCommit:语句执行完即释放锁,其他事务能立刻修改同一行
- 事务未显式 commit/rollback:连接长期持锁,可能引发锁等待甚至死锁
- 建议搭配 try-with-resources + 显式 rollback(异常时)或 commit(成功时)
FOR UPDATE 子句的写法直接影响锁范围
SELECT ... FOR UPDATE 的行为取决于是否带子句——不加任何修饰时,默认对查询结果集所有行加排他锁;加 OF 可限定只锁特定表的列(多表 join 场景),加 NOWAIT 或 SKIP LOCKED 则改变阻塞行为。
-
SELECT id, name FROM orders WHERE status = 'PENDING' FOR UPDATE:锁所有匹配行 -
SELECT o.id, c.name FROM orders o JOIN customers c ON o.cid = c.id FOR UPDATE OF o.id:只锁orders表的行,customers行不被锁 -
FOR UPDATE NOWAIT:冲突时抛java.sql.SQLException,错误信息含ORA-00054: resource busy -
FOR UPDATE SKIP LOCKED:跳过已被锁的行,适合并发消费队列类场景
ojdbc8 中 ResultSet 并不支持对已锁定行做 update/delete
很多人误以为拿到 ResultSet 后调用 rs.updateString() 再 rs.updateRow() 就能更新——这是错的。Oracle 行锁由 SELECT FOR UPDATE 触发,但后续 DML 必须通过独立的 PreparedStatement 执行,ResultSet 的更新方法在 ojdbc8 下对 Oracle 无效(会抛 SQLFeatureNotSupportedException)。
- 正确做法:用同一事务中的新
PreparedStatement执行UPDATE ... WHERE ROWID = ?或主键条件更新 - 可提前查出
ROWID:SELECT ROWID, id, name FROM t FOR UPDATE,后续用WHERE ROWID = ?精准更新 - 避免用
WHERE id = ?更新后被其他事务抢先改了该行内容(丢失更新),ROWID 更可靠
连接池配置不当会导致锁持有时间不可控
很多问题实际出在连接池(如 HikariCP、Druid)上:如果连接归还池时事务未结束,ojdbc8 通常会静默 rollback(取决于池实现和配置),但有些池可能复用未清理的连接,导致锁意外延续或报错。
立即学习“Java免费学习笔记(深入)”;
- HikariCP 默认
rollbackOnReturn = true,但需确认版本(2.7.0+ 才默认启用) - 务必设置
connection-timeout和transaction-isolation,避免低隔离级别干扰锁语义 - 最稳妥方式:在业务代码中严格保证每个
SELECT FOR UPDATE块都有明确的 commit / rollback,不依赖池自动处理



















