前两天看到现场alert日志中有一些00060(Deadlock)的告警。查了一下日志文件,发现一些奇怪的现象,比如有些锁在Insert时产生的,有些死锁是对同一个对象产生的。于是在解决这些问题的同时,仔细研究了一下TX锁,总结了产生TX锁的各种情况。
数据记录被锁
我们知道,Oracle中事务产生的索都是行级锁。也就说,事务在对表做更新操作(Update、Delete)时,只在针对数据块中需要更新的数据记录加锁。这种类型的锁就是我们最常见的锁。看下面的例子:
SQL> create table t_lock(a number, b varchar2(20), c char(10)) initrans 1 maxtrans 3; Table created SQL> insert into t_lock values(1,1,1); 1 row inserted SQL> commit; Commit complete 会话1:
SQL> update t_lock set b='2' where a=1; 1 row updated 会话2:
SQL> delete t_lock where a=1; 会话1锁住了a=1的记录,会话试图删除该记录时,被hung住。
SQL> select * from dba_waiters;
WAITING_SESSION HOLDING_SESSION LOCK_TYPE MODE_HELD MODE_REQUESTED LOCK_ID1 LOCK_ID2--------------- --------------- --------- --------- -------------- -------- 28 20 Transaction Exclusive Exclusive 1048671 40361 并且,如果将t_lock的数据块dump出来的话,也可以看到在记录行上的锁标志
tl: 19 fb: --H-FL-- lb: 0x1 cc: 3 其中,0x1对应的是产生锁的事务在数据块itl列表中的条目号。
数据块中itl条目达到最大限制时产生的锁
ITL(Interested Transaction List)直白点说就是对该数据块“有兴趣”的事务列。也就是会对该数据块进行修改的事务。一个表的数据块上同时最多可以有多少个事务对其进行更新,是由创建表时的参数maxtrans指定的。但是,如果表建立在基于9i新特性ASSM(自动段空间管理)的表空间上的话,那么指定的maxtrans就无效,一律都是255。比如上面的例子中,我们指定了maxtrans为3(表空间不是ASSM),所以同时最多只能有3个事务对一个数据块进行更新。看下面的例子。
先给上面的表多插入一些数据:
SQL> insert into t_lock values(2,2,2); 1 row inserted SQL> insert into t_lock values(3,3,3); 1 row inserted SQL> insert into t_lock values(4,4,4); 1 row inserted SQL> insert into t_lock values(5,5,5); 1 row inserted SQL> commit; Commit complete 会话1:
SQL> update t_lock set b='2' where a=1; 1 row updated 会话2:
SQL> update t_lock set b='2' where a=2; 1 row updated 会话3:
SQL> update t_lock set b='2' where a=3; 1 row updated 会话4:
SQL> update t_lock set b='2' where a=4; 1 row updated 前面已经占用了3个itl slot,第4个事务再申请itl时被hung住了。
SQL> select * from dba_waiters;
WAITING_SESSION HOLDING_SESSION LOCK_TYPE MODE_HELD MODE_REQUESTED LOCK_ID1 LOCK_ID2 ------ --------- -------------- -------- --------19 27 Transaction Exclusive Exclusive 1048671 40361 Dump出数据块,可以看到itl已经达到最大限制条目数:
seg/obj: 0x87c0 csc: 0x00.a337dee9 itc: 3 flg: - typ: 1 - DATA fsl: 0 fnx: 0x0 ver: 0x01 Itl Xid Uba Flag Lck Scn/Fsc0x01 0x0008.053.00009ef3 0x00802e6a.71b4.2b ---- 1 fsc 0x0000.000000000x02 0x000d.037.0000a98a 0x0080655a.ac1f.2b ---- 1 fsc 0x0000.000000000x03 0x000c.004.0000acb9 0x008006a6.06bc.28 ---- 1 fsc 0x0000.00000000 这里我们需要提出另外一个问题,如果事务操作不是update、delete,而是进行Insert操作,会不会也会达到maxtrans的限制呢?做个试验看下吧:
会话1:
SQL> update t_lock set b='2' where a=1; 1 row updated 会话2:
SQL> update t_lock set b='2' where a=2; 1 row updated 会话3:
SQL> update t_lock set b='2' where a=3; 1 row updated 会话4:
SQL> insert into t_lock values(6,6,6); 1 row inserted 这时,尽管已经分配itl条目给三个事务,但是第四个事务在做insert插入操作时并没有被hung住。
看下数据块dump内容:
Object id on Block? Y seg/obj: 0x87c0 csc: 0x00.a337df1b itc: 3 flg: O typ: 1 - DATA fsl: 0 fnx: 0xa03000c ver: 0x01 Itl Xid Uba Flag Lck Scn/Fsc0x01 0x0003.00b.0000a3ab 0x0080397c.0b0f.2a ---- 1 fsc 0x0000.000000000x02 0x0000.000.00000000 0x00806a5a.as1e.2b ---- 1 fsc 0x0000.000000000x03 0x000e.037.0000a458 0x14401ff2.17d8.2b ---- 1 fsc 0x0000.00000000 我们发现,确实只有三个transaction在这个数据块上。那么还有一个事务呢?别急,把下一个数据块dump出来看看:
Object id on Block? Y seg/obj: 0x87c0 csc: 0x00.a337df22 itc: 1 flg: O typ: 1 - DATA fsl: 0 fnx: 0x0 ver: 0x01 Itl Xid Uba Flag Lck Scn/Fsc0x01 0x0004.038.0000a6dc 0x00806fbf.bb4e.0e ---- 1 fsc 0x0000.00000000 原来如此!在做insert操作时,如果发现数据块已经达到maxtrans的最大限制,就从freelist中取出下一空闲数据块进行插入操作,而不管是否达到pctused的限制了。
Table上有Index时达到maxtrans限制
以上的测试是针对table上没有index时做的测试。当在table上建了index,情况就变得复杂多了。
· 场景一:
SQL> drop table t_lock; Table dropped SQL> create table t_lock(a number, b varchar2(20), c char(10)) initrans 1 maxtrans 5; Table created SQL> insert into t_lock values(1,1,1); 1 row inserted SQL> insert into t_lock values(2,2,2); 1 row inserted SQL> insert into t_lock values(3,3,2); 1 row inserted SQL> insert into t_lock values(4,4,2); 1 row inserted SQL> insert into t_lock values(5,5,1); 1 row inserted SQL> commit; Commit complete SQL> create index t_lock_idx on t_lock(a) maxtrans 3; Index created 会话1:
SQL> delete from t_lock where a=1; 1 row deleted 会话2:
SQL> delete from t_lock where a=2; 1 row deleted (注意:以上操作都会索引列)
会话3:
SQL> update t_lock2 set b=2 where b=2; 1 row updated(注意:这个会话没有涉及索引列)
会话4:
SQL> delete from t_lock where a=3;
第4个会话被hung住了:
SQL> select * from dba_waiters;
WAITING_SESSION HOLDING_SESSION LOCK_TYPE MODE_HELD MODE_REQUESTED LOCK_ID1 LOCK_ID2 --------------- --------------- ----- -------- --------19 20 Transaction Exclusive Share 393284 42733
我们的table上的maxtrans是5,这里并没有达到,而index上的maxtrans是3,第4个Transaction被hung。这里似乎可以下一个结论:table上有index的话,如果达到index上的maxtrans限制,后面的事务就会被hung住。但是,请注意第3个会话,他的操作并没有涉及到索引列,那么去掉第3个会话会怎样?
· 场景二:
会话1:
SQL> delete from t_lock where a=1; 1 row deleted
会话2:
SQL> delete from t_lock where a=2; 1 row deleted (注意:以上操作都会索引列)
会话3:
SQL> delete from t_lock where a=3;
第3个会话被hung住了:
SQL> select * from dba_waiters;
WAITING_SESSION HOLDING_SESSION LOCK_TYPE MODE_HELD MODE_REQUESTED LOCK_ID1 LOCK_ID2 ---- --------------- --------- -------- -------- --------27 20 Transaction Exclusive Share 1048605 40351
这里,第3个会话就被hung住了。那就是说对有index得情况来说,itl数的限制是minx(maxtrans_of_index1 - 1, maxtrans_of_index2 – 1..., maxtrans_of_table) 。
我们前面提到,如果在insert时发现数据块的itl数已经达到maxtrans的限制,那么会在一个新的数据块进行插入,以避免会话阻塞。那么有了index以后,是否还是这样呢?看下面这种情况。
· 场景三:
会话1:
SQL> delete from t_lock where a=1; 1 row deleted
会话2:
SQL> delete from t_lock where a=2; 1 row deleted (注意:以上操作都会索引列)
会话3:
SQL> insert into t_lock values(6,6,6);
第3个会话被hung住了:
SQL> select * from dba_waiters;
WAITING_SESSION HOLDING_SESSION LOCK_TYPE MODE_HELD MODE_REQUESTED LOCK_ID1 LOCK_ID2 - -------------- --------------- -------- --------19 20 Transaction Exclusive Share 131099 40859
我们看到,尽管达到maxtrans限制的会话是一个insert操作,但它还是被阻塞了。并不是想没有index时那样重新分配一块block。我想,oracle这样做的目的是为了保持index块的紧凑把。否则,试想,如果index块像data 块一样分布,index scan的意义就不大了。
有index情况比较复杂,很难知道Oracle内部是怎么处理的。但是,尽管从逻辑上分析,被lock住的对象应该是index,但从v$locked_object视图中查到的却是table。莫非oracle在做加锁时,是把index看作是table的一部分,而不是独立的资源(事实上,将index dump出来的话,它的行锁机制应该和数据块上差不多,不同的是,在做update操作时,index块上是先delete,然后insert,而数据块上是直接update)?
Bitmap Index条目被锁
如果Index是bitmap index,那么还有一种情况能产生tx锁。
我们知道,bitmap index一般建立在distinct值数目比较少的字段上。Oracle会建立一个类似于二进制位图(所以叫bitmap index)的结构,位图上每个条目对应一个值。而每个值会对应多个rowid。类似以下:
0:rowid_1, rowid_5, rowid_11, rowid_20 ...1: rowid_2, rowid_3, rowid_7, rowid_13 ...2: rowid_4, rowid_6, rowid_8, rowid_12 ......
而如果多个会话需要同时更新一个条目对应的不同记录中位图索引列的话,就会产生争用,导致之中的会话被阻塞。看以下例子:
SQL> create bitmap Index t_lock_bmidx on t_lock(c); Index created 会话1:
SQL> update t_lock set c=0 where a=1; 1 row updated
会话2:
SQL> update t_lock set c=0 where a=5;
会话2被hung住。
SQL> select * from dba_waiters;
WAITING_SESSION HOLDING_SESSION LOCK_TYPE MODE_HELD MODE_REQUESTED LOCK_ID1 LOCK_ID2 --------------- ----------------------- --------27 20 Transaction Exclusive Share 196635 41890
请注意以上两个transaction更新的记录是a=1和a=5,它们对应的c值都是1,因此第2个会话被hung住。
唯一性约束条件导致的tx锁
唯一性约束很简单,就是要保证被约束的字段不会有多于1条的重复记录。所以,事务会加上相应的tx锁,以保证约束不被违反。而如果两个事务提交后的结果会导致约束冲突的话,那么后来的会话就会被阻塞。
SQL> alter table t_lock 2 add constraint t_lock_pk primary key (a); Table altered
会话1:
SQL> insert into t_lock values(6,6,6); 1 row inserted
会话2:
SQL> update t_lock set a=6 where a=1;
可以看到,第2个会话被hung住了。
SQL> select * from dba_waiters;
WAITING_SESSION HOLDING_SESSION LOCK_TYPE MODE_HELD MODE_REQUESTED LOCK_ID1 LOCK_ID2 - -------------- --------------- -------- --------27 28 Transaction Exclusive Share 196683 41866
其它
事实上,产生tx锁还有一些其它情况。以上我所分析的都是常见的情况以及我所遇到的情况。
Tx锁是为了控制并发进程以及数据完整性而产生的。系统中产生tx锁应该是很常见的事(如果你去生产系统中查询v$lock视图,就能看到不少tx锁)。关键的问题是我们如何保证小事务不被大事务的tx锁阻塞,以及怎样避免死锁。希望以上的分析能够帮助你定位和解决这些问题。