转载自:http://blog.itpub.net/12330444/viewspace-619631/
前面我介绍了itl引发的阻塞与死锁,这里有必要再介绍一下位图索引引发的阻塞与死锁,因为这个也是不同于普通死锁的一种死锁方式,在有位图索引存在的表上面,其实很容易就引发阻塞与死锁。这个阻塞不是发生在表上面,而是发生在索引上。因为位图索引锁定的范围远远比普通的b-tree索引锁定的范围大。
假定,一个表,上面有标志字段(flags),分别是(0、1),而我们在这个flag字段上创建了一个位图索引,那么,现在我们执行如下的语句:
Piner@10gR2>update test set flags = 1 where id = 1;
假定id=1的的值原来是0,现在要更新成1,那么,这个语句在位图索引中,将锁住id=1那个记录所在的整个索引块中的flag=0与1的值,如果这个索引块中有很多记录,这个阻塞将是很严重的。另外注意,如果操作的dml不涉及到索引,则是不会被阻塞的。所以,在oltp环境中,如果一个表更新比较频繁,千万不要使用位图索引,如果数据仓库环境中,使用了位图索引,也最好在加载数据的时候将其删除,等数据加载完成以后重新创建。
我们看一个具体的例子
Piner@10gR2>select * from test;
ID FLAG
---------- ----------
1 1
2 1
3 1
4 1
5 1
6 0
7 0
8 0
9 0
10 0
10 rows selected.
我们在上面创建一个位图索引
Piner@10gR2>create bitmap index ind_test on test(flag);
Index created.
然后更新其中的一条记录,仅仅是一条记录
Piner@10gR2>update test set flag = 0 where id=1;
1 row updated.
这个时候,其实整个位图都被锁定了(因为这些记录都在一个索引块中),我们看如下的例子,在另外的会话中,执行如下的语句可以发现:
Piner@10gR2>update test set flag = flag where id=6;
1 row updated.
Piner@10gR2>update test set flag = 1 where id=6; --blocked
可以发现第二个语句被阻塞
其实也就是说,如果不涉及到更新索引(因为第一次中,修改id=6的记录,flag值没有发生变化),那么就不会有阻塞的。当然,如果不是更新flag字段,而是其它字段,也不会有阻塞的,一旦涉及到修改flag的值,阻塞就发生了。
以上是是把0更新成1阻塞,那么把1更新成0同样阻塞
Piner@10gR2>update test set flag = flag where id=2;
1 row updated.
Piner@10gR2>update test set flag = 0 where id=2; --blocked
同样被阻塞
我们看看这个时候的等待原因,可以发现等待在TX锁上面
select EVENT from v$session_wait where sid=153 EVENT ----------------------------- enq: TX - row lock contention
理解了阻塞的原因,那么我们应当就很好理解位图死锁的原因了,数据行死锁差不多,2个进程互相锁住了资源,不同的是,行死锁抢的是同一样的数据,如同一行数据,位图索引死锁抢的是位图值,可以是不同的行。
接上面的表数据,我们模拟一个死锁,在会话1中执行如下命令
会话1
Piner@10gR2>update test set flag = 2 where id=1;
1 row updated.
以上命令一旦发出,位图索引中锁住了所有flag=1(原值)以及flag=2(新值)的记录。
在会话2
Piner@10gR2>update test set flag = 3 where id=6;
1 row updated.
这个命令锁住了所有flag=0(原值)以及flag=3(新值)的记录。
再回会话1,我们更新id=7的记录,注意,以上的操作记录都不相同。
Piner@10gR2>update test set flag = 2 where id=7; --blocked
结果是阻塞了,会话1等待会话2释放位图索引的锁定资源。
那么,我们在会话2中,再更新一条记录
Piner@10gR2>update test set flag = 3 where id=2; --blocked
虽然该记录与以上任何记录都不一样,也被阻塞了,等待会话1释放资源,到这里,死锁就形成了,马上可以看到,报了一个错在会话1上:
Piner@10gR2>update test set flag = 2 where id=7;
update test set flag = 2 where id=7
*
ERROR at line 1:
ORA-00060: deadlock detected while waiting for resource
我们看跟踪文件,发现这里锁定的是索引,因为object_id=13009的对象就是是位图索引
Deadlock graph:
———Blocker(s)——– ———Waiter(s)———
Resource Name process session holds waits process session holds waits
TX-00050027-00001599 16 148 X 19 160 S
TX-00070014-00001579 19 160 X 16 148 S
session 148: DID 0001-0010-00000032 session 160: DID 0001-0013-00000004
session 160: DID 0001-0013-00000004 session 148: DID 0001-0010-00000032
Rows waited on:
Session 160: obj - rowid = 000032D1 - AAADLRAAAAAAAAAAAA
(dictionary objn - 13009,file - 0,block - 0,slot - 0)
Session 148: obj - rowid = 000032D1 - AAADLRAAAAAAAAAAAA
(dictionary objn - 13009,slot - 0)
第2楼 logzgh 于2007-05-23 16:21:55 Says:
“假定id=1的的值原来是0,现在要更新成1,那么,这个语句在位图索引中,锁住所有的flag为0以及flag为1的数据,这个是比较恐怖的。更新一行数据相当于锁定了整个索引”
你这里的说法不太正确。
事实上是锁定了id=1那条记录所在的索引块上所包含的所有记录。对大表来讲,一个索引块并不会包含所有的记录,所以不是整个索引。
第3楼 piner 于2007-05-23 19:33:04 Says:
“假定id=1的的值原来是0,现在要更新成1,那么,这个语句在位图索引中,锁住所有的flag为0以及flag为1的数据,这个是比较恐怖的。更新一行数据相当于锁定了整个索引”
你这里的说法不太正确。事实上是锁定了id=1那条记录所在的索引块上所包含的所有记录。对大表来讲,一个索引块并不会包含所有的记录,所以不是整个索引。
谢谢指正。。。这里我我有时间再实验一下。
第4楼 piner 于2007-05-23 20:16:03 Says:
现在发现,你的说法也不正确,你说锁住id=1的那个索引块,其实,我这里这么少的记录,应当就是一个索引块。
但是,下面的实验可以发现,set flag=2与flag=3是互相不等待的,也就是没有锁等待,按照你的说法,除非只是锁定了这个记录所在的索引块的flag = 新值 与 原值 的所有记录。
现在,我先这么改了,是否正确,我还要验证。
第5楼 piner 于2007-05-23 20:55:44 Says:
具体位图索引的内部,可以参考
http://www.itpub.net/showthread.php?threadid=743939 其实,锁定范围也可能不仅仅是一个索引块,9i与10g的变化也是比较大的,我们现在只要知道,锁定的范围比较大,不适合频繁修改的oltp即可。