序:块头部分有事务槽(itl)一种数据结构,用于记录哪些事务修改了这个数据块的内容,可以看成是一个表格,每一行对应一个事务,包括事务号、事务是否提交等重要信息。
每条记录的头部有一个字段,用于记录itl表项号,指向itl表的指针。相当于记录头的索引。
TX锁代表一个事务,拥有锁的机制,有resource structure,lock structure,enqueue算法。
TM锁是表级锁,也有锁的机制,用于保护对象(表、视图等)的定义不被修改。
行级锁机制:当一个事务开始时,必须先申请一个TX锁,这种锁保护的资源是回滚段、回滚数据块。因此这个申请意味着:用户进程必须先申请到回滚段资源后才能开始一个事务,才能执行DML语句修改数据。申请到回滚段资源后,用户事务就可以修改数据了。修改数据时遵循下面操作顺序。
(1)首先要获得这个表的TM锁,这个锁用于保护事务执行过程中其他用户不能修改表结构。
(2)事务修改某个数据块中的记录时,首先要先在该数据块块头的ITL表中申请一个空的闲表项,也就是事务槽,并在其中记录事务号,UBA等实际就是记录这个事务要使用的回滚段地址。
(3)事务修改该数据块的记录时,会设置该记录头部的ITL索引指向上一步申请的表项。然后在修改记录内容,修改前先在回滚段对记录修改前的状态做一个拷贝,然后才能修改数据记录,这个拷贝用于以后的回滚、恢复、或一致性读。
(4)当其他用户并发修改这条记录时,会根据记录头的ITL索引读取ITL事务槽表项内容,查看这个事务是否已经提交。
(5)如果没有提交,则这个用户的TX锁会等待前一个用户的TX锁的释放。
事务真正需要的只是一个TX锁、每个表一个TM锁,内存开销非常小。而所谓的行级锁其实只是数据块头、数据记录头的一些字段,不会消耗额外资源。行级锁不是真正意义上的锁,虽有锁的功能,但是没有开销。
下面给以实验证明:
A回话(用户hlz)
SQL> select * from t1;
ID NAME
---------- ----------
1 jdh
2 iuhw
SQL> savepoint a;
Savepoint created.
SQL> update t1 set name='shjgd' where id=2;
1 row updated.
B回话(用户sy)
SQL> update hlz.t1 set name='hskjs' where id=2;
光标卡住
A回话
SQL> rollback to a;
Rollback complete.
B回话
SQL> update hlz.t1 set name='hskjs' where id=2;
继续卡着
C回话(用户sys)
(A状态查看)
SQL> select username,event,sid,blocking_session from v$session where username='HLZ';
USERNAME EVENT SID BLOCKING_SESSION
------------------------------ ---------------------------------------------------------------- ---------- ----------------
HLZ SQL*Net message from client 1
(B状态查看)
SQL> select username,event,sid,blocking_session from v$session where username='SY';
USERNAME EVENT SID BLOCKING_SESSION
------------------------------ ---------------------------------------------------------------- ---------- ----------------
SY enq: TX - row lock contention 40 1
A回话回滚到之前的a点,相当于撤销对于记录的修改,但是回话B仍然处于等待状态,这是因为回话B是被回话A的TX锁阻塞的,而不是回话A的行级锁阻塞。
从回话C中可以看到回话B在等回话A的TX锁。