How Oracle Locking Works
When a Lock is NOT a Lock!
In the last installment of this series " 100 Thing you Probably Didn't Know About Oracle " you learned how Oracle locks the rows of a table. Here is what you learned in a nutshell:
(1) When a transaction modifies a record, the pre-change image is stored in the undo segments, which is required for various things; the most important of which is to provide a read consistent version of the row when another session wants it.
(2) The transaction is assigned a transaction identifier that shows the undo segment number, slot# and record of the undo information.
(3) The transaction locks the rows (since it did not commit) by placing a special type of data in the block header known as Interested Transaction List (ITL) entry. The ITL entry shows the transaction ID and other information.
(4) When a new transaction wants to update the same rows (locked by the previous transaction) it checks the ITL entries in the block first, to check if there is a lock.
(5) Since the lock information of rows is stored in the block itself, and the ITL entries in the block refer to the locks on the rows in that block alone, there is no need to have a central lock manager to dispense and handle the release of the locks. This makes the locking process not only immensely scalable but feasible as well since there is no theoretical limit to the number of locks.
[Updated Jan 22, 2011] [Thank you, Randolph Geist (info@www.sqltools-plusplus.org) for pointing it out. I follow his blog http://oracle-randolf.blogspot.com/, which is a treasure trove of information.
(6) The information that a row is locked is stored along with the row in the form of a lock byte.
[End of Update Jan 22, 2011]
While the article might have answered some of the vexing questions you may have had or needed some clarity on the concepts you were somewhat familiar with, I sincerely hope it has piqued you curiosity to learn even more about these concepts. If I was successful in explanation, now you should not be satisfied, you should have more questions. If you don’t have any, then I completely failed in my explanation.
So, what are the questions? For starters, how do you know what objects being locked in the transaction? It’s actually quite trivial. The view V$LOCK has provided that information for years, albeit in a convoluted form. A new view V$LOCKED_OBJECT is a bit more user-friendly. Let’s examine that with an example. First, update a row:
SQL> update itltest set col2 = 'CHANGED BY SESSION AGAIN' where col1 = 221 2 /1 row updated.
We can check the transaction ID:
SQL> select dbms_transaction.local_transaction_id from dual'LOCAL_TRANSACTION_ID--------------------------------------------------------------------------------2.16.413161 row selected.
As you learned from the previous installment in this series, the transaction ID is a series of numbers denoting undo segment number, slot# and record# (also known as sequence#) respectively, separated by periods.
Now, check the view V$LOCKED_OBJECT:
SQL> select * from v$locked_object 2 / XIDUSN XIDSLOT XIDSQN OBJECT_ID SESSION_ID---------- ---------- ---------- ---------- ----------ORACLE_USERNAME OS_USER_NAME------------------------------ ------------------------------PROCESS LOCKED_MODE------------------------ ----------- 2 16 41316 95263 56ARUP oracle13181 3
The view shows Undo Segment# (XIDUSN), Undo Slot# (XIDSLOT) and Undo Rec# (XIDSQN), which can be used to construct the transaction ID to be joined with the V$TRANSACTION to get the details. The view contains the column OBJECT_ID. Another important column is LOCKED_MODE, which shows the mode the rows are locked. In this case, it’s “3”, which means Row Exclusive. Here is a script that decodes the modes as well as reports the object name.
select owner object_owner, object_name object_name, session_id oracle_sid, oracle_username db_user, decode(LOCKED_MODE, 0, 'None', 1, 'Null', 2, 'Row Share', 3, 'Row Exclusive', 4, 'Share', 5, 'Sub Share Exclusive', 6, 'Exclusive', locked_mode ) locked_mode from v$locked_object lo, dba_objects do where (xidusn||'.'||xidslot||'.'||xidsqn) = ('&transid') and do.object_id = lo.object_id/
Save this script and execute it when you need further details on the transaction. The script will ask for the transaction ID which you can pass in the format reported by dbms_transaction.local_transaction_id .
Next, you may draw my attention to the point #3 above. If there are 10 records in the block and a transaction updated (and therefore locked) all ten of them, how many ITL entries will be used – one or ten?
Good question (I have to say that, since I asked that :) I suppose you can answer that yourself. Ten ITL slots may be feasible; but what if the block has 10,000 records? Is it possible to have that many ITL slots in the block header? Let’s ponder on that for a second. There will be two big issues with that many ITL slots.
First, each ITL slot, by the way, is 24 bytes long. So, 10000 slots will take up 240,000 bytes or almost 22 KB. A typical Oracle block is 8KB (I know, it could be 2K, 4K or 16K; but suppose it is the default 8K). Of course it can’t accommodate 22KB.
Second, even if the total size of the ITL slots is less than the size of the block, where will be the room to hold data? In addition, there should be some space for the data block overhead; where will that space come from?
Obviously, these are genuine problems that make one ITL slot per row impractical. Therefore Oracle does not create an ITL entry for each locked row. Instead, it creates the ITL entry for each transaction, which may have updated a number of rows. Let me repeat that – each ITL slot in the block header actually refers to a transaction; not the individual rows. That is the reason why you will not find the rowid of the rows locked in the ITL slot. Here is the ITL entry from the block header, again:
Itl Xid Uba Flag Lck Scn/Fsc0x01 0x000a.019.00007c05 0x00c00288.1607.0e ---- 1 fsc 0x0000.000000000x02 0x0003.017.00009e24 0x00c00862.190a.0f C--- 0 scn 0x0000.02234e2b
There is a reference to a transaction ID; but not rowid. When a transaction wants to update a row in the block, it checks the ITL entries. If there is none, it means rows in that block are unlocked. However, if there are some ITL entries, does it mean that some rows in the block are locked? Not necessarily. It simply means that the rows the block were locked earlier; but that lock may or may not be active now. To check if a row is locked, the transaction checks for the lock byte stored along with the row.
That brings up an interesting question. If presence of an ITL slot does not mean a record in the block is locked, when does the ITL slot get cleared so that it can be reused, or when does that ITL slot disappear? Shouldn’t that ITL slot disappear when the transaction ends by commit or rollback? That should be the next burning question throbbing in your head right now.
Clearing of ITL Slots
To answer that question, consider this scenario: a transaction updates 10000 records, on 10000 different blocks. Naturally there will be 10000 ITL slots, one on each block, all pointing to the same transaction ID. The transaction commits; and the locks are released. Should Oracle revisit each block and remove the ITL entry corresponding to the transaction as a part of the commit operation?
If that were the processing logic, the commit would have taken a very long time. Acquiring the buffers of the 10000 blocks and updating the ITL entry will not be quick; it will take a very long time, prolonging the commit processing. From part 1 of the series, you learned that the commit processing is actually very quick, with a flush of the log buffer to redo logs and the writing of the commit marker in the redo stream. Even a checkpoint to the datafiles is not done as a part of commit processing – all the effort going towards making the process fast, very fast. Had Oracle added the logic of altering ITL slots, the commit processing would have been potentially long, very long. Therefore Oracle does not remove the ITL entries after that transaction ends (by committing, or rolling back); the slots are just left behind as artifacts.
The proof, as they say, is in the pudding. Let’s see with an example:
SQL> create table itltest (col1 number, col2 varchar2(200));Table created.SQL> begin 2 for i in 1..1000 loop 3 insert into itltest values ( 4 i,'INITIAL VALUE OF COLUMN'); 5 end loop; 6 end; 7 /PL/SQL procedure successfully completed.SQL> commit;Commit complete.
This inserts 1000 records. Let’s find out the file and block these records go to:
1 select 2 dbms_rowid.rowid_relative_fno(rowid) File#, 3 dbms_rowid.rowid_block_number(rowid) Block#, 4 count(1) 5 from itltest 6 group by 7 dbms_rowid.rowid_relative_fno(rowid), 8 dbms_rowid.rowid_block_number(rowid) 9 order by 10* 1,2SQL> / FILE# BLOCK# COUNT(1)---------- ---------- ---------- 7 4027 117 7 4028 223 7 4029 220 7 4030 220 7 4031 2205 rows selected.
Let’s identify the rows in a specific block, block# 4028, for instance.