Skip to Main Content

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question, kris.

Asked: February 17, 2008 - 4:19 pm UTC

Last updated: August 03, 2011 - 7:37 am UTC

Version: 10.02.01

Viewed 1000+ times

You Asked

Hi Tom,

I am having a question on Locks. I was reading your book Expert Oracle 9i and 10g programming techniques and solutions. In the chapter on LOCKING AND LATCHING, on page 208 under the heading Lock Escalation, it is mentioned - "For example, if you select a row from a table with the FOR Update clause, two locks will be created. One lock is placed on the rows you selected (and this will be exclusive lock; no one else can lock that specific row in exclusive mode). The other lock, a ROW SHARE TABLE lock, is placed on the table itself. This will prevent other sessions from placing an exclusive lock on the table and thus prevent them from altering the structure of the table."

I have done the following after logging into the database as SCOTT .

Create table t1 as select * from dept;

select * from t1 where deptno in (10, 20) for update;

I have logged into Enterprise Manager and went to the following page:

Performance/instance locks/user locks

I found that there are 2 rows created.

1) The User Locks column shown belongs to SCOTT
2) Under the column LOCK TYPE, the first row contains TX and the second row contains TM.
3) Under the column MODE HELD, for the first row, next to TX, I see EXCLUSIVE and the second row, next to TM, I see ROW EXCLUSIVE
4) Under the column, OBJECT NAME, I see T1. There are other columns besides these.

I am all confused about this. I do not see a Shared Lock . Can you please let me know if I am looking at the right thing? Can you please explain these elaborately?


and Tom said...

The row exclusive is it.

You cannot see the individual row locks, they are on the blocks only, not in any v$ table (eg: you have two rows locked, yet you only see ONE lock on the table - that is the lock that prevents the other sessions from gaining an exclusive lock on the table itself, preventing DDL)

The TX lock - that is the "transaction"
The TM lock - that is the select for update against the table itself. It'll permit other rows to be locked, and for the table to be locked in a row share mode, but not an entire table lock

ops$tkyte%ORA10GR2> create table dept as select * from scott.dept;

Table created.

ops$tkyte%ORA10GR2>
ops$tkyte%ORA10GR2> select * from dba_locks where session_id = (select sid from v$mystat where rownum=1);

no rows selected

ops$tkyte%ORA10GR2> select * from dept where deptno in (10,20) for update;

    DEPTNO DNAME          LOC
---------- -------------- -------------
        10 Accounting     NEW YORK
        20 RESEARCH       DALLAS

ops$tkyte%ORA10GR2> select * from dba_locks where session_id = (select sid from v$mystat where rownum=1);

SESSION_ID LOCK_TYPE   MODE_HELD  MODE_REQUE LOCK_ID1   LOCK_ID2   LAST_CONVERT BLOCKING_OTHERS
---------- ----------- ---------- ---------- ---------- ---------- ------------ ----------------------------------------
       147 DML         Row-X (SX) None       156796     0                     0 Not Blocking
       147 Transaction Exclusive  None       524296     10595                 0 Not Blocking

ops$tkyte%ORA10GR2> declare
  2          pragma autonomous_transaction;
  3  begin
  4          lock table dept in row share mode;
  5          commit;
  6  end;
  7  /

PL/SQL procedure successfully completed.

ops$tkyte%ORA10GR2> declare
  2          pragma autonomous_transaction;
  3  begin
  4          lock table dept in exclusive mode;
  5          commit;
  6  end;
  7  /
declare
*
ERROR at line 1:
ORA-00060: deadlock detected while waiting for resource
ORA-06512: at line 4



that the first plsql block there worked shows another transaction can still get at the table, but it cannot lock it exclusively

Rating

  (14 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

to clarify

DF, December 15, 2009 - 2:37 am UTC

Dear Tom!

I'm also a little bit confused in this subject :)
I look in Oracle 8, 9 and 10 docs (last is here http://download.oracle.com/docs/cd/B13789_01/server.101/b10743/consist.htm#BABCJIAJ ).
I look in some of your answers (for example here http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:839412906735#16481032186993 and especially here http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:927629362932#2949641889499 ).
And of course I look in your book "Expert One-on-One Oracle" (it's on russian, so can't make exacat quote, sorry).
So all these sources say that:
- "SELECT FOR UPDATE" makes "X" lock at row level (if rows exists), "RS" at table level,
- "UPDATE" makes "X" lock at row level (if rows exists), "RX" at table level.
But my (and your in this topic) tests say other - "SELECT FOR UPDATE" makes "RX" lock at table level.
And only today at last I've found confirmation in Oracle 11.1 docs (here http://download.oracle.com/docs/cd/B28359_01/server.111/b28318/consist.htm#BABCJIAJ ).
Looks like "SELECT FOR UPDATE" and "UPDATE" both make the same type of lock at table level - "RX".
I belive this is true for different Oracle versions (I've made checks for 9).

But my main question is: is it possible to make share lock at row level?
Does exist something like "SELECT FOR SOMETHING"?
I'll try to explain:
I want to select some data with guarantee that nobody will change it until I will make some work (checks) with it.
Of course I can use "SELECT FOR UPDATE" ("SFU" below) for this, but I don't really want to update it - it's more restrictive than I need.
I only need to be sure that nobody will change it, until I work with it ("SFU" - ok).
And I don't want to prevent others to do the same (lock) as I do now ("SFU" - not ok).
So (resume): I need to lock set of rows in some mode: (1) to deny changing of it by others, (2) but at same time to allow locking in same mode of it by others.
I belive that such mode is shared mode at row level.
But looks like there is no way to do it? Why so? :)

Of course user's type lock can help. But in this case I need to be sure that all other clients will use this technique (standard)..

Sorry for my english.
Thanks.

Tom Kyte
December 16, 2009 - 10:05 am UTC



This changed by the way - we've updated the 11.2 concepts guide (I helped rewrite that one - the 11.2 one). The select for update behavior was modified to take a slightly more restrictive lock.


You want select for update - it doesn't really update it, but it does update it. It leaves behind locks strong enough to prevent anyone from modifying it - but all can READ IT.

You cannot have two people do that however, that won't work - only one of them can lock it so that no one else changes it.


How about instead of you telling us HOW you want to do something...

You instead tell us what the GOAL IS - it sounds like serializable might be what you are looking for. The data will appear 'constant' to you - people can in fact change it - but you get a repeatable read without phantom rows (ANSI phenomena)

about goal of share lock at row level

DF, December 16, 2009 - 3:30 am UTC

Thank you for responce, Tom!

So real-life example, I hope :)
We have:
create table t_parent
(
id number primary key
,begin_date date not null
)
;
create table t_child
(
id number primary key
,parent_id number not null references t_parent
,begin_date date not null
)
;
create procedure check_begin_dates as
a_bad_child_begin_date_count number;
begin
select count(*)
into a_bad_child_begin_date_count
from t_parent p, t_child c
where c.parent_id = p.id
and c.begin_date < p.begin_date
;
if a_bad_child_begin_date_count > 0
then
raise_application_error(-20000, 'there are some children with bad begin_date :(');
end if;
end;
/
We need: never see this error above :)

Suppose, all primary and foreign keys are not updatable.
To realize such constraint we should use simple checks:
1. before update of t_parent.begin_date:
if EXISTS CHILD_DATE_BEGIN < NEW_DATE_BEGIN then ERROR end if
2. before insert or update of t_child.begin_date:
if NEW_DATE_BEGIN < PARENT_DATE_BEGIN then ERROR end if

Now data:
define a_11_2009_date = to_date('11.2009', 'mm.yyyy')
define a_12_2009_date = to_date('12.2009', 'mm.yyyy')
insert into t_parent values (1, &a_11_2009_date);
insert into t_child values (1, 1, &a_12_2009_date);
insert into t_child values (2, 1, &a_12_2009_date);
commit

Session1 wants:
update t_parent set begin_date = &a_12_2009_date where id = 1;
Session2 wants:
update t_child set begin_date = &a_11_2009_date where id = 1;
Session3 wants:
update t_child set begin_date = &a_11_2009_date where id = 2;

If we don't use locks and all these sessions starts "at same time", than only after all will make commit we will have our error after call of check_begin_dates - not before.
So, we should use locks.
Variant 1: before insert or update of t_child.begin_date we lock parent.
Variant 1.1: "SELECT FOR UPDATE" lock.
Suppose Session2 starts first. Session1 and Session3 are waiting.
With Session1 all is ok. But what Session3 is waiting for?
Variant 1.2: "SELECT FOR SOMETHING" lock. It's my missed shared lock at row level :)
Suppose Session2 starts first. Session1 is waiting. Session 3 is running - good.
All is ok, but we don't have "SELECT FOR SOMETHING" :(
Variant 1.3: User's locks. We lock named object "t_parent#1" (parent row surrogate) in share mode. Before update of parent we should also try to lock "t_parent#1" in exclusive mode.
Suppose Session2 starts first. Session1 is waiting. Session 3 is running - good.
All is ok, but we need more coding.
Variant 2: before update of t_parent.begin_date we lock all children (art of migrating key, may be).
Variant 2.1: "SELECT FOR UPDATE" lock.
Suppose Session2 starts first. Session1 is waiting. Session3 is running - good.
All is ok, but we need many locks.

Resume:
1.1:
Lock is too strong. In case of many concurrent sessions working with t_child table we can only hope, that transactions will be not very long.
1.2:
Nice concept, I think. But doesn't implemented.
1.3:
Workaround for 1.2 implementation.
2.1:
Good choise. But often in real-life we have much more children for one parent (more child rows) and much more parent-child relations (more child tables) and these relation may be not so "straight" as in this example.

So, this was my vision why Oracle need shared lock at row level.
Amazing is that everything is ready for such type of locks, but it's not implemented.

What do you think about it, Tom?

With respect, DF.

Tom Kyte
December 16, 2009 - 10:13 am UTC

variant II does NOT work, You need to either

a) lock tables
b) enforce all updates to parent or child to lock the PARENT RECORD first, to serialize access to the parent level data

to do this. You cannot lock that which you cannot SEE. Consider:

time     session1                                session2
t0:   insert into parent ( 01-jan-2009 ); 
t1:   insert into child ( 03-jan-2009 );
t2:   commit;

everything is dandy right now.

t3    insert into child ( 02-jan-2009 );
t4                                             update parent set dt=02-jan-2009;
t5    commit;
t6                                             commit;


bummer, you have bad data.


1.1:
Lock is too strong. In case of many concurrent sessions working with t_child
table we can only hope, that transactions will be not very long.


No, it isn't. The lock is at the lowest level possible to get the correct answer. This by definition means the lock is NOT too strong, rather it is just RIGHT.

You cannot lock that which you cannot SEE. Therein lies the problem.






one again about variant 1

DF, December 17, 2009 - 2:30 am UTC

one again about variant 1
Thanks for responce, Tom!

You are right about variant 2. It's my mistake, I forgot insert in t_child.

Now about variant 1.
What do you mean, saying "SELECT FOR UPDATE" lock is just right? What about Session3? Why should it be waiting?
What do you think about 1.2 and 1.3? Do you think that share mode lock at row level is useless?
Consider:
Time Session1               Session2                 Session3                
----|----------------------|------------------------|-----------------------|
T00:|ins parent (1,01-);   |                        |                       |
T01:|ins child (1,1,31-);  |                        |                       |
T02:|ins child (2,1,31-);  |                        |                       |
T03:|commit;--setup done.  |                        |                       |
----|----------------------|------------------------|-----------------------|

Variant 1.1
Time Session1               Session2                 Session3                
----|----------------------|------------------------|-----------------------|
----|----------------------|------------------------|-----------------------|
T04:|                      |SEL parent where id=1   |                       |
    |                      |FOR UPDATE;             |                       |
    |                      |--lock acquired.        |                       |
----|----------------------|------------------------|-----------------------|
T05:|                      |ins child (3,1,11-);    |SEL parent where id=1  |
    |                      |                        |FOR UPDATE;            |
    |                      |                        |--locked! waiting...   |
----|----------------------|------------------------|-----------------------|
T06:|upd parent set bd=21- |--user sleep...         |--WHAT ARE WE WAITING? |
    |where id=1;           |                        |--WHY ARE WE WAITING?  |
    |--locked! waiting...  |                        |                       |
----|----------------------|------------------------|-----------------------|
T07:|--waiting...          |commit;                 |--but waiting...       |
----|----------------------|------------------------|-----------------------|
T08:|--waiting...          |                        |--ohh, yes!!!          |
    |                      |                        |ins child (4,1,11-);   |
----|----------------------|------------------------|-----------------------|
T09:|--waiting...          |                        |commit;                |
----|----------------------|------------------------|-----------------------|
T10:|--all children done.  |                        |                       |
    |[check_error raised.] |                        |                       |
-----------------------------------------------------------------------------

Variant 1.2
Time Session1               Session2                 Session3                
----|----------------------|------------------------|-----------------------|
----|----------------------|------------------------|-----------------------|
T04:|                      |SEL parent where id=1   |                       |
    |                      |FOR SOMETHING;          |                       |
    |                      |--shared lock acquired. |                       |
----|----------------------|------------------------|-----------------------|
T05:|                      |ins child (3,1,11-);    |SEL parent where id=1  |
    |                      |                        |FOR SOMETHING;         |
    |                      |                        |--shared lock acquired.|
----|----------------------|------------------------|-----------------------|
T06:|upd parent set bd=21- |--user sleep...         |ins child (4,1,11-);   |
    |where id=1;           |                        |                       |
    |--locked! waiting...  |                        |                       |
----|----------------------|------------------------|-----------------------|
T07:|--waiting...          |commit;                 |commit;                |
----|----------------------|------------------------|-----------------------|
T08:|--all children done.  |                        |                       |
    |[check_error raised.] |                        |                       |
-----------------------------------------------------------------------------

Variant 1.3
Time Session1               Session2                 Session3                
----|----------------------|------------------------|-----------------------|
----|----------------------|------------------------|-----------------------|
T04:|                      |USERLOCK "parent#1"     |                       |
    |                      |IN SHARE MODE;          |                       |
    |                      |--shared lock acquired. |                       |
----|----------------------|------------------------|-----------------------|
T05:|                      |ins child (3,1,11-);    |USERLOCK "parent#1"    |
    |                      |                        |IN SHARE MODE;         |
    |                      |                        |--shared lock acquired.|
----|----------------------|------------------------|-----------------------|
T06:|USERLOCK "parent#1"   |--user sleep...         |ins child (4,1,11-);   |
    |IN EXCLUSIVE MODE;    |                        |                       |
    |--locked! waiting...  |                        |                       |
----|----------------------|------------------------|-----------------------|
T07:|--waiting...          |commit;                 |commit;                |
----|----------------------|------------------------|-----------------------|
T08:|--all children done.  |                        |                       |
    |upd parent set bd=21- |                        |                       |
    |where id=1;           |                        |                       |
----|----------------------|------------------------|-----------------------|
T09:|[check_error raised.] |                        |                       |
-----------------------------------------------------------------------------

With respect, DF.

Tom Kyte
December 17, 2009 - 10:09 am UTC

In order for this to work, you have to serialize at the parent level.

You have to either

a) lock the parent table
b) select for update the specific parent row(s).


You pick - but in order to safely have a constraint that crosses rows in tables, you have to serialize (or crosses tables).



So, what I mean is, you wrote:

"1.1:
Lock is too strong. In case of many concurrent sessions working with t_child
table we can only hope, that transactions will be not very long. "

I wrote:

No, it is NOT too strong, it is in fact the least level of locking possible to enforce your data integrity constraint. By definition it is not too strong, it is JUST RIGHT.


The answer to "why are you waiting, why are you waiting" is "data integrity is important, data integrity is important"


Someone has to wait. The way locks work in Oracle, this is the "just right level".


You could do what you wanted with dbms_lock and explicit locking - you'd need to make sure everyone did it consistently - just like with my approach.


And for rules like "parent cannot have more than N children, N is specified in parent record" or "the sum of salaries in a department cannot exceed M, where M is specified at the DEPT level", your approach would not work (meaning, in general, your approach does not work)

finally

DF, December 17, 2009 - 3:47 pm UTC

Tom, I like Oracle, it's good enough, however I want it will be even better in each new version.
And I hope that asktom.oracle.com is right place for me (simple developer) not only for asking help on using existing Oracle possibilities, but also for making my (may be trivial) proposals.

So, you wrote: "data integrity is important". I agree with you absolutely.
But look, I don't ask "why waiting" for Session1 - because Session1 MUST WAIT, Session3 - DON'T.
I'm sure that X-lock for entire parent table is working approach for any of such integrity checks.
But you (Oracle team) has implemented "SELECT FOR UPDATE" - X-lock at row level.
Why? May be also because X-lock of entire table for some cases is TOO STRONG = SURPLUS.
It's like use of excavator for carrot planting - possible, but unnecessary.

You wrote: "in general, your approach does not work".
Let say without deep thinking:
- for 100% of such checks we can use X-lock of parent table;
- for 90% - enough use of X-lock of parent row;
- for 1-2% - enough use of S-lock of parent row (I venture to suppose).

So, if we don't need update parent row and don't need any children aggregates (can't find more "don't need") then my approach can be useful.

May be you are right, I can ignore _possiblity_ of discomfort for Session3(,4,5,..N).
But... it's not perfect, sorry :) Please, understand it right - it's not rebuke, just my opinion.

So, I let me hope you will implement S-lock at row level in next Oracle version :)

Thank you, DF.

Tom Kyte
December 17, 2009 - 5:16 pm UTC

But your case of "less strong" leads to .....

starvation. The one session - the update - never gets to do it's job, the lesser sessions keep on a coming.

We use a queuing mechanism with the select for update.

I don't see your way as "better", hyperbole like "It's like use of excavator for carrot planting - possible, but unnecessary." isn't warranted - your way isn't any better, nor worse, just different.

We use a queue, you would use a mob versus the few.


You can do what you want already - via dbms_lock. You still need to make sure everyone (regardless of whether you could do this via a select or dbms_lock) uses the correct code path to access the data - in all cases.



I wish I could get people to understand locking in general, way before any extensions are added making it even "a larger topic". I'd guesstimate that at least 9 out of 10 (and I'm being very generous there) of developers really don't understand the fundamentals of locking and concurrency control as it exists right now.


For the 1-2% case, use dbms_lock, it exists, it works, it is an insignificant amount of extra code...

one more

DF, December 17, 2009 - 6:20 pm UTC

Only now I've finally understood your phrase "JUST RIGHT LOCK" :) Your queue logic is also clear - you talk about equal parent-child resource access priority.
But consider: Session1 don't want update - it disconnected (or do nothing) from TimePoint 4.

I'm convinced of following concept: we should request resurce lock in mode we need for task - not weaker and not stronger (restrictive).

Consider one more sample:
We have some kind of global prameters set (art of ini-file, registry). We need to use some of them for checks or calulations when data change in many tables occures. So we need to read it only! We will have many reads, much more when writes. Is "SELECT FOR UPDATE" still good solution here?

So, I really think, that developer should have choise between X- or S- mode lock on row - dbms_lock is not bad, but "SELECT FOR SOMETHING" is better :)

With respect, DF.

Tom Kyte
December 18, 2009 - 12:21 pm UTC

... We need
to use some of them for checks or calulations when data change in many tables
occures. So we need to read it only! We will have many reads, much more when
writes. Is "SELECT FOR UPDATE" still good solution here?
...

sounds like SERIALIZABLE - highly concurrent. Provides for removal of all four of the ANSI 'phenomena' (you get repeatable reads, no phantom reads)


continue

DF, December 20, 2009 - 3:57 pm UTC

Tom, I can't miss such possibility to discover Oracle concept (philosophy) from first hands, so let me to continue, if you don't mind.

I was thinking about starvation of writer sessions...
It looks like two-stage locking technique can solve this problem.
Consider:
->
We have:
Writers (Session1) - they change (write) master (parent) data.
Readers (Session2, Session3) - they use (read) master (parent) data.
Writers procedure:
1. lock "parent#1 PASS" in X-mode;
2. lock "parent#1" in X-mode;
3. do job and end transaction.
Readers procedure:
1. lock "parent#1 PASS" in any(S)-mode (and don't hold it - for this we can use very short autonomous transaction);
2. lock "parent#1" in S-mode;
3. do job and end transaction.
<-
So we can prevent new readers appearance and avoid writers starvation.
May be it looks complicated, but don't forget that with such approach we have high degree of readers concurrency.
Same technique we can use on table level.
And even, may be, it can be nice alternative for serializable transaction..?
I think that may be approximately same concept is used to quiesce the database. Am I right?
Please, give your comment on this.

And one more question, please.
Looks like "ORA-08177: can't serialize access for this transaction" is not truly error (low level), but restriction (high level, conceptual).
Am I right? If yes, then what is the reason for this?
Standard? Or may be side effect of optimistic approach - if some other has made our modifications, it's looks like bad serialization?

Thank you, DF.

Tom Kyte
December 21, 2009 - 1:24 pm UTC

... May be it looks complicated,...

for a theorized 1-2% problem, yes, it sure does. I didn't really understand it either. I don't know what "pass" means.



... And even, may be, it can be nice alternative for serializable transaction..?
...

why? since serializable requires no special coding on part of the developer and this requires the developer actually understands the database better than 99.9999% of developers do today?


.... So we can prevent new readers appearance ...

which would get you back to being almost just like select for update - we queue the reads again.


As for ora-8177, here is what I wrote in Expert Oracle Database Architecture, ora-8177 is sort of like an ORA-4091 Mutating table constraint. It is an an implementation restriction - but is an "error" none the less. It is also better than the alternative (deadlock) that occurs in other systems (at a much higher rate, since READS would deadlock with WRITES in the other systems - here only the write of the same data conflicts)

<quote>

SERIALIZABLE

This is generally considered the most restrictive level of transaction isolation, but it provides the highest degree of isolation. A SERIALIZABLE transaction operates in an environment that makes it appear as if there are no other users modifying data in the database. Any row we read is assured to be the same upon a reread, and any query we execute is guaranteed to return the same results for the life of a transaction. For example, if we execute

Select * from T;
Begin dbms_lock.sleep( 60*60*24 ); end;
Select * from T;


the answers returned from T would be the same, even though we just slept for 24 hours (or we might get an ORA-1555, snapshot too old error, which is discussed in Chapter 8). The isolation level assures us these two queries will always return the same results. Side effects (changes) made by other transactions are not visible to the query regardless of how long it has been running.

In Oracle, a SERIALIZABLE transaction is implemented so that the read consistency we normally get at the statement level is extended to the transaction.

Note As noted earlier, there is also an isolation level in Oracle denoted READ ONLY. It has all of the qualities of the SERIALIZABLE isolation level, but it prohibits modifications. It should be noted that the SYS user (or users connected as SYSDBA) cannot have a READ ONLY or SERIALIZABLE transaction. SYS is special in this regard.


Instead of results being consistent with respect to the start of a statement, they are preordained at the time you begin the transaction. In other words, Oracle uses the rollback segments to reconstruct the data as it existed when our transaction began, instead of just when our statement began.

That’s a pretty deep thought there—the database already knows the answer to any question you might ask it, before you ask it.

This degree of isolation comes with a price, and that price is the following possible error:

ERROR at line 1:
ORA-08177: can't serialize access for this transaction


You will get this message whenever you attempt to update a row that has changed since your transaction began.

Oracle takes an optimistic approach to serialization—it gambles on the fact that the data your transaction wants to update won’t be updated by any other transaction. This is typically the way it happens, and usually the gamble pays off, especially in quick-transaction, OLTP-type systems. If no one else updates your data during your transaction, this isolation level, which will generally decrease concurrency in other systems, will provide the same degree of concurrency as it would without SERIALIZABLE transactions. The downside to this is that you may get the ORA-08177 error if the gamble doesn’t pay off. If you think about it, however, it’s worth the risk. If you’re using SERIALIZABLE transaction, you shouldn’t expect to update the same information as other transactions. If you do, you should use the SELECT ... FOR UPDATE as described previously in Chapter 1, and this will serialize the access. So, using an isolation level of SERIALIZABLE will be achievable and effective if you

* Have a high probability of no one else modifying the same data
* Need transaction-level read consistency
* Will be doing short transactions (to help make the first bullet point a reality)

Oracle finds this method scalable enough to run all of their TPC-Cs (an industry standard OLTP benchmark; see http://www.tpc.org for details). In many other implementations, you will find this being achieved with shared read locks and their corresponding deadlocks, and blocking. Here in Oracle, we do not get any blocking, but we will get the ORA-08177 error if other sessions change the data we want to change as well. However, we will not get the error as frequently as we will get deadlocks and blocks in the other systems.

But—there is always a “but”—you must take care to understand these different isolation levels and their implications. Remember, with isolation set to SERIALIZABLE, you will not see any changes made in the database after the start of your transaction, until you commit. Applications that attempt to enforce their own data integrity constraints, such as the resource scheduler described in Chapter 1, must take extra care in this regard. If you recall, the problem in Chapter 1 was that we could not enforce our integrity constraint in a multiuser system since we could not see changes made by other uncommitted sessions. Using SERIALIZABLE, we would still not see the uncommitted changes, but we would also not see the committed changes made after our transaction began!
</quote>

continue

DF, December 22, 2009 - 9:56 am UTC

Thank you for answers, Tom!

...I didn't really understand it either. I don't know what "pass" means...
Better I place both procedures here.

Writers procedure:
declare
a_pass_lh varchar2(128);
a_lh varchar2(128);
a_r integer;
begin
dbms_lock.allocate_unique('parent#1 PASS', a_pass_lh);
dbms_lock.allocate_unique('parent#1', a_lh);

a_r := dbms_lock.request(a_pass_lh, dbms_lock.x_mode, dbms_lock.maxwait, true);
a_r := dbms_lock.request(a_lh, dbms_lock.x_mode, dbms_lock.maxwait, true);

--doing job and ending transaction.
dbms_lock.sleep(60);
commit;
end;

Readers procedure:
declare
a_pass_lh varchar2(128);
a_lh varchar2(128);
a_r integer;
begin
dbms_lock.allocate_unique('parent#1 PASS', a_pass_lh);
dbms_lock.allocate_unique('parent#1', a_lh);

a_r := dbms_lock.request(a_pass_lh, dbms_lock.s_mode, dbms_lock.maxwait, false);
--a_r := dbms_lock.request(a_pass_lh, dbms_lock.s_mode, 0, false);
--if a_r = 1 then raise_application_error(-20000, 'other user is changing our master''s data, try again later.'); end if;
a_r := dbms_lock.release(a_pass_lh);
a_r := dbms_lock.request(a_lh, dbms_lock.s_mode, dbms_lock.maxwait, true);

--doing job and ending transaction.
dbms_lock.sleep(60);
commit;
end;

So you can see - with such approach reader doesn't block other readers, but with "select for update" does.
And at same time we have possibility for writers to regulate readers flow (but old one should finish their job) - no starvation.
And if writing is seldom, we have high degree of readers concurrency.

Now about serializable transaction.
Of course we can use already implemented optimistic approach.
But excepting 8177-constraint we have possibility that someone else will change our master's data during our work.
So it is possible we will commit out-of-date data (based on not actual master's data).
You wrote about it in last paragraph.
It's ok for some cases (reports, any more?), but it's not ok for many other (checks, calculations may be)...
Consider follow concept:
if we want to check before commit (or calculate and commit) some data (our data) and our data is depend (based) on some other data (master data), nobody should change master data till we commit.
What do you think about it, is it viable?
I think for such purposes use of shared locks is good choice. Where am I wrong?
Or are you disposed only to optimistic approach?

I was also thinking about optimistic approach.
Commited data should include actuality timestamp (begin of serializable transaction).
And if master's data last change is before if, all is ok, if not - our commited data is not actual.
After commit check and retry - is it good idea?

Thank you, DF.

Tom Kyte
December 31, 2009 - 8:28 am UTC

... So you can see - with such approach reader doesn't block other readers, but
with "select for update" does.
...

And I'll say it once more - it does not in general work - for most transactions it would not apply. If I could get people to understand transaction processing in the first place - maybe something like this could make sense. But the truth is - in a multi-versioning, read consistent model - you cannot lock data you cannot see and that is the cause of pretty much all logical integrity issues in client applications (that insist on doing their own integrity constraints). Serialization is a necessary evil. Because there is one case where it could be avoided (and you just demonstrated *HOW* to do that and it didn't require that much more code - in fact, you should remove the release for less code, the commit will do that) doesn't mean it should be done.

...But excepting 8177-constraint we have possibility that someone else will change
our master's data during our work.
.....

You'd have to explain why that matters. You wanted a read consistent view, you got it. It doesn't really matter if the data is modified while you are processing, you have a read consistent view of the database - as if you were the only one in the database.


... It's ok for some cases (reports, any more?), but it's not ok for many other
(checks, calculations may be)...
...


give me a for example - and then we are back to "thou must physically serialize" again.


... if we want to check before commit (or calculate and commit) some data (our
data) and our data is depend (based) on some other data (master data), nobody
should change master data till we commit.
....

Ah, here is the problem. If you are doing some sort of financial thing (parent child - sum up child records for example), not only do you need to prevent modifications to the data that exists but you also need to PREVENT NEW DATA from becoming present (we are back to serializable again! highly concurrent serializable - but serializable mode isolation!!!!!!). You have everything you want in serializable, your requested lock mode isn't necessary, you have repeatable reads with no phantoms.

... Or are you disposed only to optimistic approach? ...

I'm disposed towards a few simple approaches, unforunately - until we go with just one - SERIALIZE EVERYTHING - developers will continue to totally mess up the data, since they don't get concurrency in the first place :(


...
I was also thinking about optimistic approach.
Commited data should include actuality timestamp (begin of serializable
transaction).
And if master's data last change is before if, all is ok, if not - our commited
data is not actual.
After commit check and retry - is it good idea?
....

I did not understand that thought - not sure what you mean.

If you mean "if the data we READ was modified during our transaction - we should roll back", I will disagree with you wholeheartedly.

A reader, January 24, 2010 - 5:11 pm UTC

Hello;

When I issue an update, itl entry is opened for this row.
next transaction see that itl is open for this row and goes to rollback segments to see if it is committed
or not, if it is not committed, it will hang.


Regarding the insert statement to primay key column.
If a user insert a value to primary column and not commit, whenever another user tries to
insert the same value to that column it will hang.

How is the process in this scenerio??

Tom Kyte
January 26, 2010 - 1:52 am UTC

... it will hang.
...

no, it will block. A hang sounds like "a bug"



for the primary key, it would block on the INDEX - not on the table. You cannot create two index entries with the same key.

A reader, January 30, 2010 - 12:33 am UTC

Regarding the pk inserts;

Does oracle add an index after it inserts to table? or simultaneously?
Tom Kyte
February 01, 2010 - 9:37 am UTC

the index must exist as we create the constraint itself. The index will be in existence at the time of constraint creation.

Drawback of 'Row Level Locking'

Bix, April 09, 2010 - 9:25 am UTC

Hi Tom,
I am not able to understand the drawback with the 'row level locking' feature of ORACLE mentioned in the below article - is that true ? if that is true - Can you please explain bit more in detail ? Many thanks for your help

http://www.tlingua.com/new/articles/Chapter1.html

Many thanks for your time .
Tom Kyte
April 13, 2010 - 9:02 am UTC

they are not really talking about row locking there - they are talking about the fact that when you get locked on a row, you start the wait on the TRANSACTION - even if the transaction gives up that row lock before it commits/rollsback - a situation that *can* occur but happens *infrequently*.



Drawback with ' row level locking'

Bix, April 13, 2010 - 4:13 am UTC

Hi Tom,
I wanted to give you the actual part (drawback with rowl level locking) from the article mentioned above in my qiestion - so that you dont need to spend time in opening the URL and finding the actual part - Please see below the examples and conclusion from the author ??
Thanks for your time 
=================================================

Unneeded Contention and Possible False Deadlocks
Even though there are many excellent design reasons for implementing row locks on disk and using transaction level locks as wait points, it can cause artificial contention. The second transaction is truly waiting for the release of the last row, but since Oracle does not create a lock for each row, the only thing the second transaction can do is wait for the completion of the entire first transaction. 

Is that not the same thing? How can the first transaction release its lock on the last row without doing a commit (or rollback)? Answer: savepoints. By using savepoints, the first transaction can release its row lock without committing. Although the row has been unlocked, the second transaction will be left waiting for the completion of a transaction which is technically no longer blocking it. In fact, a third transaction can update the very same row even while the second transaction is still waiting for it. Consider an example from our EMP table: 

SVRMGR> create table emp (empno number, name varchar2(2000));
Statement processed.
SVRMGR> insert into emp values(1, rpad('Scott'   , 1000));
1 row processed.
SVRMGR> insert into emp values(2, rpad('Anthony' , 1000));
1 row processed.
SVRMGR> insert into emp values(3, rpad('Tiger'   , 1000));
1 row processed.
SVRMGR> commit;
Statement processed.
Update the second row, set a savepoint, and update the third row but do not commit yet. Then confirm the last two rows are locked by a query from TABLE_ROWS. 

SVRMGR> update emp set empno = empno * 10 where empno = 2;
1 row processed.
SVRMGR> savepoint x;
Statement processed.
SVRMGR> update emp set empno = empno * 10 where empno = 3;
1 row processed.
SVRMGR> select trunc(id1 / 65536)                                    rbs
     2>      , mod  (id1 , 65536)                                    slot
     3>      , id2                                                   seq
     4>      , rpad(to_char(trunc(id1 / 65536), 'FM0xxx') || '.' ||
     5>             to_char(  mod(id1 , 65536), 'FM0xxx') || '.' ||
     6>             to_char(      id2,          'FM0xxxxxxx'), 18)   txid
     7>
     8>   from v$lock, v$session
     9>  where v$lock.type        = 'TX'
    10>    and v$lock.sid         = v$session.sid
    11>    and v$session.username = USER
    12> ;
RBS        SLOT       SEQ        TXID
---------- ---------- ---------- ------------------
         2         25        525 0002.0019.0000020d
1 row selected.
SQL> select myrid          myrid
  2       , rowflagdecode  rowflag
  3       , txid           txid
  4    from TABLE_ROWS
  5   where owner = 'SYS'
  6     and name  = 'EMP'
  7  ;

MYRID                 ROWFLAG  TXID
--------------------- -------- --------------------
0000073a.0000.0007    --H-FL-- 0000.000.00000000
0000073a.0001.0007    --H-FL-- 0002.019.0000020d
0000073a.0002.0007    --H-FL-- 0002.019.0000020d
In a second session, attempt to update the last row and confirm the expected behavior: blocking. 

SVRMGR> update emp set empno = empno * 100 where empno = 3;
The following query reveals that Oracle implements the wait of the second transaction for the first by using exclusive lock requests. 

SVRMGR> select v$lock.sid                                            sid
     2>      , rpad(to_char(trunc(id1 / 65536), 'FM0xxx') || '.' ||
     3>             to_char(  mod(id1 , 65536), 'FM0xxx') || '.' ||
     4>             to_char(      id2,          'FM0xxxxxxx'), 18)   txid
     5>      , lmode                                                 lmode
     6>      , request                                               request
     7>   from v$lock, v$session
     8>  where v$lock.type        = 'TX'
     9>    and v$lock.sid         = v$session.sid
    10>    and v$session.username = USER
    11> ;
SID        TXID               LMODE      REQUEST
---------- ------------------ ---------- ----------
         8 0002.0019.0000020d          6          0
        11 0002.0019.0000020d          0          6
2 rows selected.
In the first session execute rollback to savepoint to reproduce the non-ideal behavior. 

SVRMGR> rollback to savepoint x;
Statement processed.
Even though the last row has been released by the rollback to savepoint, the second session is still blocking needlessly waiting for the first transaction to commit. 

SVRMGR> select v$lock.sid                                            sid
     2>      , rpad(to_char(trunc(id1 / 65536), 'FM0xxx') || '.' ||
     3>             to_char(  mod(id1 , 65536), 'FM0xxx') || '.' ||
     4>             to_char(      id2,          'FM0xxxxxxx'), 18)   txid
     5>      , lmode                                                 lmode
     6>      , request                                               request
     7>   from v$lock, v$session
     8>  where v$lock.type        = 'TX'
     9>    and v$lock.sid         = v$session.sid
    10>    and v$session.username = USER
    11> ;
SID        TXID               LMODE      REQUEST
---------- ------------------ ---------- ----------
         8 0002.0019.0000020d          6          0
        11 0002.0019.0000020d          0          6
2 rows selected.
We can confirm the release of the row lock on disk by using the TABLE_ROWS view. 

SQL> select myrid          myrid
  2       , rowflagdecode  rowflag
  3       , txid           txid
  4    from TABLE_ROWS
  5   where owner = 'SYS'
  6     and name  = 'EMP'
  7  ;
MYRID                 ROWFLAG  TXID
--------------------- -------- --------------------
0000073a.0000.0007    --H-FL-- 0000.000.00000000
0000073a.0001.0007    --H-FL-- 0002.019.0000020d
0000073a.0002.0007    --H-FL-- 0000.000.00000000
The last row is no longer locked by any transaction, yet we wait. We could even use a third session to update the very row the second session is waiting to update. 

SVRMGR> update emp set empno = empno * 10 where empno = 3;
1 row processed.
SVRMGR>
SVRMGR> select trunc(id1 / 65536)                                    rbs
     2>      , mod  (id1 , 65536)                                    slot
     3>      , id2                                                   seq
     4>      , rpad(to_char(trunc(id1 / 65536), 'FM0xxx') || '.' ||
     5>             to_char(  mod(id1 , 65536), 'FM0xxx') || '.' ||
     6>             to_char(      id2,          'FM0xxxxxxx'), 18)   txid
     7>
     8>   from v$lock, v$session
     9>  where v$lock.type        = 'TX'
    10>    and v$lock.sid         = v$session.sid
    11>    and v$session.username = USER
    12> ;
RBS        SLOT       SEQ        TXID
---------- ---------- ---------- ------------------
         2         25        525 0002.0019.0000020d
         2         25        525 0002.0019.0000020d
         5         34        312 0005.0022.00000138
3 rows selected.
SVRMGR>
Query the meta data once again via the TABLE_ROWS view to reveal that the last row is now locked by the new transaction '0005.022.00000138'. 

SQL> select myrid          myrid
  2       , rowflagdecode  rowflag
  3       , txid           txid
  4    from TABLE_ROWS
  5   where owner = 'SYS'
  6     and name  = 'EMP'
  7  ;
MYRID                 ROWFLAG  TXID
--------------------- -------- --------------------
0000073a.0000.0007    --H-FL-- 0000.000.00000000
0000073a.0001.0007    --H-FL-- 0002.019.0000020d
0000073a.0002.0007    --H-FL-- 0005.022.00000138
Commit the first session to release the second session and see how the session behaves when it discovers the row it was waiting for is now locked by a different transaction. 

SVRMGR> commit;
Statement processed.
Our second session is still blocked, but it is now waiting on the new transaction. 

SVRMGR> select v$lock.sid                                            sid
     2>      , rpad(to_char(trunc(id1 / 65536), 'FM0xxx') || '.' ||
     3>             to_char(  mod(id1 , 65536), 'FM0xxx') || '.' ||
     4>             to_char(      id2,          'FM0xxxxxxx'), 18)   txid
     5>      , lmode                                                 lmode
     6>      , request                                               request
     7>   from v$lock, v$session
     8>  where v$lock.type        = 'TX'
     9>    and v$lock.sid         = v$session.sid
    10>    and v$session.username = USER
    11> ;
SID        TXID               LMODE      REQUEST
---------- ------------------ ---------- ----------
        11 0005.0022.00000138          0          6
        12 0005.0022.00000138          6          0
2 rows selected.
SVRMGR>
If this keeps up, our second session may never get to update the row even though no other transaction ever commits a modification to the row! 


Every solution has its drawbacks. A drawback of Oracle's row level locking is that a transaction has no true place to wait on a row. It can only wait on another transaction which may eventually release the row without committing. Without a mechanism to inform the waiting transaction, the second transaction waits unnecessarily. In certain applications this could dramatically reduce concurrency and may, in fact, generate deadlocks where none exist.

Tom Kyte
April 13, 2010 - 9:19 am UTC

see above, it is nothing to do with row level locking really, but rather the queuing mechanism we use (we wait on the transaction). It is an edge case that frankly doesn't happen very often.

Milind, April 19, 2010 - 4:48 am UTC

Can you please explain about how the locking works in case of SELECT .... FOR UPDATE, when it is selecting large number of rows? Does it read all rows in memory for locking purpose? If SELECT .... FOR UPDATE query is taking 5 minutes then can second session (started after this query) is able to lock rows which are not yet accesses by SELECT .... FOR UPDATE in first session?


Tom Kyte
April 19, 2010 - 8:59 am UTC

it visits each row - they may or may not be in memory (you can lock more rows than you have memory to hold).

But yes, it has to touch - physically touch and modify - each and every row to put a lock on it before it returns.


and yes, another session that wanted to lock a row could lock that row if the first session hasn't gotten to it yet.

A reader, April 19, 2010 - 9:55 am UTC

>> and yes, another session that wanted to lock a row could lock that row if the first session hasn't gotten to it yet.

Scenario 1: say another session get the lock on required row(s), update it and commit

will first session restart again or just rollback the block(s) where update happen?


Scenario 2: say another session get the lock on required row(s), update it and not commit

will first session block and wait for the second session to commit?


Thanks

Lots Of Blocking sessions

aliyar, August 03, 2011 - 2:35 am UTC

Dear Tom ,

we are using four node 10.2.0.4 RAC .
o/s : Hp/Ux

two nodes as Pure OLTP AND two nodes as Hybrid

we are facing Heavy Blocking session issues. some time we need to re-boot instance to fix the problem .

all sessions acquire 'enque row level lock'. so i don't think this is due to database configuration problem.

is there any other do we need to cross check to find out why lots of blocking session ?

Can you Please Provide Some Idea On this Issue

ppreciate your Help for DBA World.

Thanks
Aliyar
Tom Kyte
August 03, 2011 - 7:37 am UTC

all sessions acquire 'enque row level lock'. so i don't think this is due to
database configuration problem.


correct it is not due to a database configuration issue.

it is due to your application - someone has locked a row and others would like that row. It is traditional, old fashioned, standard "blocker and blockees"

http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:839412906735

you'll need to use the GV$ tables instead of V$ for those queries. or google search:

who is blocking who in rac


for some other scripts

More to Explore

PL/SQL demos

Check out more PL/SQL tutorials on our LiveSQL tool.

PL/SQL docs

PL/SQL reference manual from the Oracle documentation library