We're encountering issues on our oracle 11g database, regarding table lock.
We have a procedure that is executed via sql*plus, which truncates a table, let say table1.
We sometimes get a ORA-00054: resource busy and acquire with NOWAIT error during execution of the procedure at the part when the table is to be truncated. We have a webapp that is in a tomcat server, which when restarted (to kill sessions to the database from tomcat), the procedure can be re executed successfully.
table1 isn't used, not even in select, in the source code for the webapp, but a lot of parent table of table1 is.
So is it possible that an uncommitted update to one of its parent table is causing the lock?. If so, any suggestions on how I can test it?
I've checked with the DBA during times when we encounter the issue, but he can't get the session that is blocking the procedure and the statement that caused the lock.
Yes, an update of a parent table will get a lock on the child table. Below is a test case demonstrating it is possible.
Finding and tracing a specific intermittent locking issue can be painful. Even if you can't trace down the specific conditions it would be a good idea to modify any code to avoid concurrent DML and DDL. Not only can it cause locking issues, it can also break SELECT statements.
If removing the concurrency is out of the question, you may at least want to enable DDL_LOCK_TIMEOUT so that the truncate statement will wait for the lock instead of instantly failing: alter session set ddl_lock_timeout = 100000;
--Create parent/child tables, with or without an indexed foreign key.
create table parent_table(a number primary key);
insert into parent_table values(1);
insert into parent_table values(2);
create table child_table(a number references parent_table(a));
insert into child_table values(1);
commit;
--Session 1: Update parent table.
begin
loop
update parent_table set a = 2 where a = 2;
commit;
end loop;
end;
/
--Session 2: Truncate child table. Eventulaly it will throw this error:
--ORA-00054: resource busy and acquire with NOWAIT specified or timeout expired
begin
loop
execute immediate 'truncate table child_table';
end loop;
end;
/
any suggestions on how I can test it?
You can check the blocking sessions when you get ORA-00054: resource busy and acquire with NOWAIT error.
Blocking sessions occur when one sessions holds an exclusive lock on an object and doesn't release it before another sessions wants to update the same data. This will block the second until the first is either does a COMMIT or ROLLBACK.
SELECT
s.blocking_session,
s.sid,
s.serial#,
s.seconds_in_wait
FROM
v$session s
WHERE
blocking_session IS NOT NULL;
For example, see my similar answer here and here.
Related
I have this 2 jdbc statement (Oracle 10g with isolation level read committed)
delete from emp where emp_id = 1;
insert into emp (emp_id,address,.....) value (1,'newyork',.....);
emp_id,address is the primary key
Unfortunately in a multithreaded environment I get ORA-00001: unique constraint violated while inserting the record.
When I run in a single thread I don't see any issue.
After investigation I found that
first session deletes and then inserts a record but is not yet committed.
second session deletes it
first session now commits it
second session now tries to insert it and throws the error .
Let me know if I am missing anything.
How to solve this problem in oracle.I don't like the solution of single threaded ,I also don't like the solution of retry.
Any clean solution?
Make sure you have enabled AUTOCOMMIT in your program, then check whether the row exists or not before inserting it to prevent ORA-00001. Here's the PL/SQL block.
declare
v_rows_count number;
begin
select count(*) into v_rows_count from emp where emp_id = 1;
if v_rows_count = 0 then
-- Insert the row
else
-- Do not insert the row
end if;
end;
/
Don't forget to commit the change later.
I have a table (T1) with a trigger after update. This trigger looks for some values and then it updates other table (T2)
CREATE OR REPLACE TRIGGER t1_AIR AFTER UPDATE ON t1 FOR EACH ROW
DECLARE
PRAGMA AUTONOMOUS_TRANSACTION;
v_dF1 DATE;
v_dF2 DATE;
v_nDays NUMBER;
v_cReg VARCHAR2(50);
BEGIN
IF :NEW.BN_BE_ESTADO not in ('PEN', 'ERR', 'BAJ') THEN
v_nDays := F_GET_PARAM(NULL, NULL, 'X_DAYS');
PCK_AUX.PR_GET_F1(:NEW.F_A, v_dF1, v_cReg);
PCK_AUX.PR_GET_F2(:NEW.F_A, v_dF2);
UPDATE T2
SET F_F1 = TRUNC(v_dF1),
F_F2 = TRUNC(v_dF2),
F_F_END = PCK_AUX.FU_GET_F_END(v_dF2+1, v_nDays),
F_N_REG = v_cReg
WHERE F_ID = :NEW.F_B;
END IF;
EXCEPTION
WHEN OTHERS THEN
NULL;
END;
But when I update T1 it generates locks in both tables (t1 and t2)... does anyone know why??
Thanks to all
When you update a table, row-level(TX) lock and table lock in row-share(RM) and row exclusive mode(RX) are acquired.
Row-level lock is there so other sessions couldn't modify row(s) being updated. Table locks in different modes are there to protect your table structure from modifications while DML statement (update in your case) in progress.
So yes, in your situation rows that are being updated will be locked in both table 1 and table 2 plus table lock will be held in row share and row exclusive mode.
A bit about your trigger...
Why autonomous transaction pragma is there? Unless you are using that trigger for logging for example, situation when you have to commit whether main transaction succeeds or not, you really do not need this pragma. As #William Robertson absolutely correctly pointed out if use autonomous transaction you have to commit this autonomous transaction otherwise ORA-06519 will be raised.
Reconsider usage of when others then null statement - would be better not to hide but re-raise any exceptions that might be generated.
In oracle 11g it's allowed to set session and system parameter, which's called ddl_lock_timeout. It's very useful when you need to execute some statement and resources are highly used (in order to avoid ORA-00054 exception).
But the case is that there's no such a parameter in 10g.
Of course, I'm able to use such a cosntruction as:
DECLARE START_DATE DATE := SYSDATE;
BEGIN
LOOP
IF SYSDATE>START_DATE+30/60/60/24 THEN
EXIT;
END IF;
BEGIN
<some statement>
EXIT;
EXCEPTION WHEN OTHERS THEN
IF sqlcode != -54 THEN
RAISE;
END IF;
END;
END LOOP;
END;
And by using it, I will try to execute the statement for 30 seconds in a cycle, but the thing here is that the statement is executed many many times and could cause some troubles (i'm not sure but I feel it somehow), but using ddl_lock_timeout the statement is executed only once and then waits for resources which is much fluffier.
Any ideas?
This is an explanation of why LOCK TABLE does not necessarily work.
LOCK TABLE - trick may not be working in all situations.
Session 1: Session 2:
create table table1(a number);
insert into table1 values(1);
commit;
update table1 set a = 2;
lock table table1 in exclusive mode;
<waits...>
commit;
"Table(s) Locked."
update table1 set a = 3; <-- notice session 1 goes in queue now.
DDL fails with "resource busy with NOWAIT".
Reason is DDL first commit the previous transaction
before executing the DDL. And when it commits
session 1 gets the lock as it was already in queue.
Using Oracle, is it possible to indicate which rows are currently locked (and which are not) when performing a select statement (I don't want to lock any rows, just be able to display which are locked)?
For example, a pseudo column that would return the lock/transaction against the row:
SELECT lockname FROM emp;
One thing you could do is this - although it is not terribly efficient and so I wouldn't want to do use it for large data sets. Create a row-level function to try and lock the row. If it fails, then the row is already locked
CREATE OR REPLACE FUNCTION is_row_locked (v_rowid ROWID, table_name VARCHAR2)
RETURN varchar2
IS
x NUMBER;
PRAGMA AUTONOMOUS_TRANSACTION;
BEGIN
EXECUTE IMMEDIATE 'Begin
Select 1 into :x from '
|| table_name
|| ' where rowid =:v_rowid for update nowait;
Exception
When Others Then
:x:=null;
End;'
USING OUT x, v_rowid;
-- now release the lock if we got it.
ROLLBACK;
IF x = 1
THEN
RETURN 'N';
ELSIF x IS NULL
THEN
RETURN 'Y';
END IF;
END;
/
And then you could
Select field1, field2, is_row_locked(rowid, 'MYTABLE') from mytable;
It will work, but it isn't pretty nor efficient.
Indeed, it has exactly one redeeming quality - it will work even if you don't have select privs on the various v$ tables required in the linked document. If you have the privs, though, definitely go the other route.
is it possible to indicate which rows are currently locked (and which are not) when performing a select statement
A SELECT statement will never lock any rows - unless you ask it to by using FOR UPDATE.
If you want to see locks that are held due to a SELECT ... FOR UPDATE (or a real update), you can query the v$lock system view.
See the link that OMG Pony posted for an example on how to use that view.
I think #Michael Broughton's answer is the only way that will always work. This is because V$LOCK is not accurate 100% of the time.
Sessions don't wait for a row, they wait for the end of the transaction that modified that row. Most of the time those two concepts are the same thing, but not when you start using savepoints.
For example:
Session 1 creates a savepoint and modifies a row.
Session 2 tries to modify that same
row, but sees session 1 already has that row,
and waits for session 1 to finish.
Session 1 rolls back to the
savepoint. This removes its entry
from the ITL but does not end the
transaction. Session 2 is still
waiting on session 1. According to
V$LOCK session 2 is still waiting on
that row, but that's not really true
because now session 3 can modify that
row. (And if session 1 executes a
commit or rollback, session 2 will
wait on session 3.)
Sorry if that's confusing. You may want to step through the link provided by OMG Ponies, and then try it again with savepoints.
We have an Oracle database, and the customer account table has about a million rows. Over the years, we've built four different UIs (two in Oracle Forms, two in .Net), all of which remain in use. We have a number of background tasks (both persistent and scheduled) as well.
Something is occasionally holding a long lock (say, more than 30 seconds) on a row in the account table, which causes one of the persistent background tasks to fail. The background task in question restarts itself once the update times out. We find out about it a few minutes after it happens, but by then the lock has been released.
We have reason to believe that it might be a misbehaving UI, but haven't been able to find a "smoking gun".
I've found some queries that list blocks, but that's for when you've got two jobs contending for a row. I want to know which rows have locks when there's not necessarily a second job trying to get a lock.
We're on 11g, but have been experiencing the problem since 8i.
Oracle's locking concept is quite different from that of the other systems.
When a row in Oracle gets locked, the record itself is updated with the new value (if any) and, in addition, a lock (which is essentially a pointer to transaction lock that resides in the rollback segment) is placed right into the record.
This means that locking a record in Oracle means updating the record's metadata and issuing a logical page write. For instance, you cannot do SELECT FOR UPDATE on a read only tablespace.
More than that, the records themselves are not updated after commit: instead, the rollback segment is updated.
This means that each record holds some information about the transaction that last updated it, even if the transaction itself has long since died. To find out if the transaction is alive or not (and, hence, if the record is alive or not), it is required to visit the rollback segment.
Oracle does not have a traditional lock manager, and this means that obtaining a list of all locks requires scanning all records in all objects. This would take too long.
You can obtain some special locks, like locked metadata objects (using v$locked_object), lock waits (using v$session) etc, but not the list of all locks on all objects in the database.
you can find the locked tables in Oracle by querying with following query
select
c.owner,
c.object_name,
c.object_type,
b.sid,
b.serial#,
b.status,
b.osuser,
b.machine
from
v$locked_object a ,
v$session b,
dba_objects c
where
b.sid = a.session_id
and
a.object_id = c.object_id;
Rather than locks, I suggest you look at long-running transactions, using v$transaction. From there you can join to v$session, which should give you an idea about the UI (try the program and machine columns) as well as the user.
Look at the dba_blockers, dba_waiters and dba_locks for locking. The names should be self explanatory.
You could create a job that runs, say, once a minute and logged the values in the dba_blockers and the current active sql_id for that session. (via v$session and v$sqlstats).
You may also want to look in v$sql_monitor. This will be default log all SQL that takes longer than 5 seconds. It is also visible on the "SQL Monitoring" page in Enterprise Manager.
The below PL/SQL block finds all locked rows in a table. The other answers only find the blocking session, finding the actual locked rows requires reading and testing each row.
(However, you probably do not need to run this code. If you're having a locking problem, it's usually easier to find the culprit using GV$SESSION.BLOCKING_SESSION and other related data dictionary views. Please try another approach before you run this abysmally slow code.)
First, let's create a sample table and some data. Run this in session #1.
--Sample schema.
create table test_locking(a number);
insert into test_locking values(1);
insert into test_locking values(2);
commit;
update test_locking set a = a+1 where a = 1;
In session #2, create a table to hold the locked ROWIDs.
--Create table to hold locked ROWIDs.
create table locked_rowids(the_rowid rowid);
--Remove old rows if table is already created:
--delete from locked_rowids;
--commit;
In session #2, run this PL/SQL block to read the entire table, probe each row, and store the locked ROWIDs. Be warned, this may be ridiculously slow. In your real version of this query, change both references to TEST_LOCKING to your own table.
--Save all locked ROWIDs from a table.
--WARNING: This PL/SQL block will be slow and will temporarily lock rows.
--You probably don't need this information - it's usually good enough to know
--what other sessions are locking a statement, which you can find in
--GV$SESSION.BLOCKING_SESSION.
declare
v_resource_busy exception;
pragma exception_init(v_resource_busy, -00054);
v_throwaway number;
type rowid_nt is table of rowid;
v_rowids rowid_nt := rowid_nt();
begin
--Loop through all the rows in the table.
for all_rows in
(
select rowid
from test_locking
) loop
--Try to look each row.
begin
select 1
into v_throwaway
from test_locking
where rowid = all_rows.rowid
for update nowait;
--If it doesn't lock, then record the ROWID.
exception when v_resource_busy then
v_rowids.extend;
v_rowids(v_rowids.count) := all_rows.rowid;
end;
rollback;
end loop;
--Display count:
dbms_output.put_line('Rows locked: '||v_rowids.count);
--Save all the ROWIDs.
--(Row-by-row because ROWID type is weird and doesn't work in types.)
for i in 1 .. v_rowids.count loop
insert into locked_rowids values(v_rowids(i));
end loop;
commit;
end;
/
Finally, we can view the locked rows by joining to the LOCKED_ROWIDS table.
--Display locked rows.
select *
from test_locking
where rowid in (select the_rowid from locked_rowids);
A
-
1
Given some table, you can find which rows are not locked with SELECT FOR UPDATESKIP LOCKED.
For example, this query will lock (and return) every unlocked row:
SELECT * FROM mytable FOR UPDATE SKIP LOCKED
References
Ask TOM "How to get ROWID for locked rows in oracle".