ORA-00054仅在显式或隐式使用NOWAIT(如SELECT...FOR UPDATE NOWAIT)或DDL操作遇锁时触发,普通UPDATE默认等待;查v$locked_object定位锁源,按场景选择COMMIT、KILL或调优加锁逻辑。

ORA-00054 不是“锁没释放”的泛泛问题,而是你代码里明确写了 NOWAIT 或隐式触发了不可等待的锁请求——它只在你主动要求“不等、立刻失败”时才报。普通 UPDATE 语句根本不会抛这个错。
为什么 UPDATE 会触发 ORA-00054?
真正触发它的,几乎全是以下两类操作:
- 你在执行 DDL(比如
ALTER TABLE、DROP INDEX)时,目标表正被另一个事务用SELECT ... FOR UPDATE或未提交的 DML 锁住 - 你的应用代码里显式写了
SELECT ... FOR UPDATE NOWAIT,而目标行已被别人锁住
注意:UPDATE 本身默认会等(无超时),除非你加了 FOR UPDATE NOWAIT 或底层框架/ORM 自动加了它(比如某些 Hibernate 配置、MyBatis 手动拼写错误)。
查锁源:别猜,直接看 v$locked_object
先确认是不是真有长事务占着资源,而不是代码逻辑问题:
- 用 DBA 权限执行:
SELECT l.session_id, o.object_name, o.owner FROM v$locked_object l, dba_objects o WHERE l.object_id = o.object_id; - 再查会话详情:
SELECT sid, serial#, username, program, logon_time FROM v$session WHERE sid = <session_id>; - 重点看
logon_time和program:如果时间很老、program是 PL/SQL Developer 或某个老旧服务进程,基本就是“卡死未提交”
别依赖 v$lock 的 BLOCK 字段——它只标“是否阻塞”,不告诉你谁在等谁;v$locked_object 才直接对应被锁的对象和持有者。
修复路径:分场景选动作
根据锁来源决定怎么处理,不是所有情况都该 kill session:
- 如果是开发测试环境里自己写的存储过程没 commit,直接
COMMIT或ROLLBACK最快 - 如果是生产环境里某个业务会话长时间持有锁(比如导出任务卡住),先查它的 SQL:
SELECT sql_text FROM v$sqltext_with_newlines WHERE hash_value = (SELECT sql_hash_value FROM v$session WHERE sid = <sid>) ORDER BY piece;,确认无害再 kill - 执行 kill:
ALTER SYSTEM KILL SESSION '<sid>,<serial#>';—— 注意必须带serial#,否则可能误杀重用的 SID - 如果 kill 后状态变
killed但锁不释放,说明事务回滚慢,此时不要重启库,等几分钟;真等不及就 OS 层 kill 对应的ora_<pid>_<instance>进程(需 DBA 权限)
DDL 操作报 ORA-00054 时,11g+ 可临时设 ALTER SESSION SET ddl_lock_timeout = 10;,但这是权宜之计,不能替代锁治理。
避免复发:关键在加锁姿势
真正防不住的不是锁,而是“裸奔式更新”。所有并发更新同一行的逻辑,必须收口到可控的锁语义阶段:
- 绝对不用“先
SELECT再UPDATE”:查到的是旧快照,且没加锁,等于让并发直接撞车 - 必须用
SELECT ... FOR UPDATE WAIT 3(单位秒),不是NOWAIT;WAIT才能捕获ORA-30006做重试 - WHERE 条件字段必须有索引,用
EXPLAIN PLAN确认走INDEX RANGE SCAN,否则可能升级成表锁 - MyBatis 中不能靠注解或配置生成
FOR UPDATE,必须在 XML 里手写完整语句,并设fetchSize="1"
最易被忽略的一点:Hibernate 的 setLockMode() 在多数版本中根本不生成 FOR UPDATE,信它等于放弃锁控制权。


















