Troubleshooting ORA-00054: resource busy and acquire with NOWAIT specified or timeout expired

ORA-00054

ORA-00054 is the error code for the failed DDL operation attempt while someone was holding the lock. The main difficulty in dealing with this error is that the error message doesn’t show the blocker. I’m going to outline several methods for identifying the root cause.

We can simulate this problem by performing a DDL in the victim session while keeping an uncommitted change in the blocking session.

Blocker:

@whoami

SPID                            SID    SERIAL#        PID PROCESS                  DBUSER
------------------------ ---------- ---------- ---------- ------------------------ --------------------------------------------------------------------------------------------------------------------------------
1768940                          17      20768         48 1768938                  SYS

create table table123 (n number);

insert into table123 values (1);

Victim:

SQL> @whoami

SPID                            SID    SERIAL#        PID PROCESS                  DBUSER
------------------------ ---------- ---------- ---------- ------------------------ --------------------------------------------------------------------------------------------------------------------------------
1884653                         335      14388         51 1884651                  SYS


truncate table table123 ;
               *
ERROR at line 1:
ORA-00054: resource busy and acquire with NOWAIT specified or timeout expired

Data dictionary

As for the first step, I’m checking if the blocker is still holding any locks:

SELECT session_id
  FROM gv$locked_object v, dba_objects d
  WHERE v.object_id = d.object_id and object_name = 'TABLE123' ;

SESSION_ID
----------
        17

That’s all you need to do if the blocker hasn’t released the lock yet.

Unknown object name

In the example above, I knew the name of the locked table because I wrote the SQL statements. But 3rd party applications often obscure this information. In such cases, you can setup the errorstack trace which will provide more information whenever the ORA-00054 occurs:

ALTER SESSION SET EVENTS '54 trace name errorstack level 3';

In the trace file, we can see the recursive lock statement including the object name for which the error was raised:

truncate table table123
               *
ERROR at line 1:
ORA-00054: resource busy and acquire with NOWAIT specified or timeout expired


ORA-00054: resource busy and acquire with NOWAIT specified or timeout expired
----- Current SQL Statement for this session (sql_id=fmj06n13hfvdh) -----
LOCK TABLE "TABLE123" IN EXCLUSIVE MODE  NOWAIT

While the errorstack trace shows the name of the locked object, it doesn’t tell us anything about the blocker. In contrast, system dump provides the full picture.

System dump

The following command configures generating a system dump on the first occurrence of the ORA-00054 error:

alter system set events '00054 trace name systemstate level 266,  lifetime 1';

The grep based on the object name reveals only the information about the victim:

grep -i table123 /u00/oracle/orabase/diag/rdbms/dkss65c/DKSS65C/trace/DKSS65C_ora_1884653_0001.trc
ObjectName:  Name=LOCK TABLE "TABLE123" IN EXCLUSIVE MODE  NOWAIT
ObjectName:  Name=truncate table table123

But with the search based on the object_id we can position ourselves on the entry related to the blocker:

select object_id from dba_objects where object_name='TABLE123' ;

 OBJECT_ID
----------
     26446

1 row selected.
!grep -i 26446 DB_ora_1884653_0001.trc
ktubl redo: slt: 15 wrp: 1 flg: 0x0c08 prev dba:  0x00000000 rci: 0 opc: 11.1 [objn: 26446 objd: 26446 tsn: 0]
        DML LOCK: tab=26446 flg=11 chi=0

By scrolling up, we can find out the address of the process state object that was holding the lock:

owner: 0x8ff54a00, proc: 0x97e54fc0

Based on the process’ state object address (0x97e54fc0), we can get the Oracle process id:

SO: 0x97e54fc0, type: process (2), map: 0x9759e9b8
  state: LIVE (0x4532), flags: 0x1
  owner: (nil), proc: 0x97e54fc0
  link: 0x97e54fe0[0x97e54fe0, 0x97e54fe0]
  child list count: 9, link: 0x97e55030[0x94f92630, 0x85d87270]
  conid: 1, conuid: 1, SGA version=(1,0), pg: 0
SOC: 0x9759e9b8, type: process (2), map: 0x97e54fc0
   state: LIVE (0x99fc), flags: INIT (0x1)
(process) Oracle pid:48, ser:3611, calls cur/top: (nil)/0x965bd3f0

Finally, we can identify the session id:

48: USER ospid 1821521 sid 17 ser 45047, waiting for 'SQL*Net message from client'

The system dump analysis yields the result, but it’s awkward.

I prefer ASH because the analysis is more user friendly.

Active session history

Luckily, we don’t need to invent the wheel to query ASH. Tanel Poder’s script ash_wait_chains.sql displays the relationship between blockers and waiters.

No information about the blocker, though, was displayed at first.

@ash_wait_chains "session_id||' '||sql_opname" "session_id=335 and session_serial#=14388" &hour

-- Display ASH Wait Chain Signatures script v0.6 BETA by Tanel Poder ( http://blog.tanelpoder.com )

%This     SECONDS        AAS WAIT_CHAIN                                                                                                                                                                                                                                                                                                   FIRST_SEEN          LAST_SEEN
------ ---------- ---------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------- -------------------
 100%           4          0 -> 335 TRUNCATE TABLE                                                                                                                                                                                                                                                                                        2022-06-22 15:04:55 2022-06-22 15:18:57

The reason for the missing blocker information is that the truncate wasn’t waiting on the lock to be released – the error was thrown immediately.

But if we define a ddl_lock_timeout (default is zero), the blocker information will be recorded:

alter session set ddl_lock_timeout = 6 ;

Session altered.

SQL> truncate table table123 ;
truncate table table123
               *
ERROR at line 1:
ORA-00054: resource busy and acquire with NOWAIT specified or timeout expired


@ash_wait_chains "session_id||' '||sql_opname" "session_id=335 and session_serial#=14388" &hour

-- Display ASH Wait Chain Signatures script v0.6 BETA by Tanel Poder ( http://blog.tanelpoder.com )

%This     SECONDS        AAS WAIT_CHAIN                                                                                                                                                                                                                                                                                                   FIRST_SEEN          LAST_SEEN
------ ---------- ---------- ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ ------------------- -------------------
  60%           6          0 -> 335 TRUNCATE TABLE -> [idle blocker 1,17,45047 (sqlplus@lsdba04i.vt.ch (TNS V1-V3))]                                                                                                                                                                                                                      2022-06-22 15:28:21 2022-06-22 15:28:26

Conclusion

In conclusion, getting the blocker information for the command that failed with “ORA-00054: resource busy and acquire with NOWAIT specified or timeout expired” isn’t trivial. There are several methods to achive this.

The straightforward way is to query v$locked_object. That will yield the result only if the blocker is still holding the lock. Otherwise, you have to use either ASH or system dump. The former seems more elegant but requires additional licenses.

The Oracle error message doesn’t show the affected object. If the application hides the failed statement, you can get it from errorstack trace.

Troubleshooting flow chart

Thanks for sharing

Nenad Noveljic

One Comment

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.