位图索引是oracle数据库里除B树索引之外的另外一种索引的类型,它主要用于数据仓库或者DSS系统。在数据仓库或DSS系统中,针对某些类型的sql,用位图索引比用B树索引要快很多,这主要是位图索引实现了快捷的按位运算的缘故。
位图索引的物理存储结构和普通B树索引的物理存储结构相似,也是按照被索引的键值列有序存储,只不过和索引键值一起存储的不再仅仅是索引键值所对应的rowid,而是变成了三部分的组合。这三部分分别为对应rowid的下限,对应rowid的上限和被压缩存储的位图(Bitmap Segment,位图段最大只能为位图索引叶子块大小的1/2),即oracle数据库中位图索引的物理存储结构:<被索引的键值,对应rowid的下限,对应rowid的上限,位图段>,这里位图段是压缩存储的,解压缩后就是一连串0和1的二进制位图序列,其中1表示被索引键值的一个有效rowid,oracle通过一个转换函数(mapping function)将解压缩段的位图段中的1结合对应rowid的上下限,转换为被索引键值所对应的有效rowid。
上述位图索引的物理存储结构决定了oracle数据库中位图索引的锁粒度是在索引行的位图段上。对于oracle数据库的位图索引而言,它是没有行锁这个概念的,要锁就锁索引行的整个位图,而多个数据行可能对应同一索引的位图段。这种锁的粒度就决定了位图索引不适用于高并发频繁修改的OLTP系统,如果在高并发且频繁修改的OLTP系统中使用了位图索引,很可能会导致严重的并发问题,甚至会产生死锁。
我们来看一个因为位图索引而导致常见的并发insert操作出现死锁的例子。创建一个测试表T1:
SQL> create table t1(id number,sex char(20));
Table created.
在T1表中插入10000条数据:
SQL> begin
2 for i in 1..5000 loop
3 insert into t1 values(i,‘MALE‘);
4 insert into t1 values(i,‘FEMALE‘);
5 end loop;
6 end;
7 /
PL/SQL procedure successfully completed.
在T1的列SEX上创建一个位图索引IDX_BITMAP_T1:
SQL> create bitmap index idx_bitmap_t1 on t1(sex);
Index created.
现在我们来构造死锁的情景,首先session 1中插入一条记录但不commit:
SQL> insert into t1 values(10001,‘MALE‘);
1 row created.
接着到session 2,插入一条记录但不commit:
SQL> insert into t1 values(10002,‘FEMALE‘);
1 row created.
回到session 1,再插入一条记录,这时这个插入操作hang住了:
SQL> insert into t1 values(10003,‘FEMALE‘);--hang住
再回到session 2,又插入一条记录,这时这个插入操作也hang住了:
SQL> insert into t1 values(10004,‘MALE‘);
第2次回到session 1,这里oracle已经检测出了死锁:
SQL> insert into t1 values(10003,‘FEMALE‘);
insert into t1 values(10003,‘FEMALE‘) *
ERROR at line 1:
ORA-00060: deadlock detected while waiting for resource
这里的原理是当插入一条记录时,oracle需要去维护位图索引IDX_BITMAP_T1中所对应键值的位图段,因为记录数较少并且oracle是压缩存储位图段的缘故,所有sex值male的5000条记录对应的都是同一条索引行,sex值为female的5000条记录对应也是同一条索引行。这也意味着当插入一条新的sex值为male的那5000条记录);但插入一条新的sex值为female的记录时,oracle会锁原先所有sex值为female的那5000条记录所对应的位图段(即相当于锁了所有sex值为female的那5000条记录),所以通常情况下不会出现死锁的并发insert操作就这样出现了死锁。
与B树索引相比,位图索引的优势主要体现在如下几个方面:
(1)因为位图索引的位图段的是压缩后存储的,所以如果被索引的distinct值较少,那么位图索引段与同列上的B树索引比起来,会显著节省空间。比如上例中表T的SEX列,其distinct 值仅为2,虽然表T1的数据量为10000,但SEX列上单键值位图索引idx_bitmap_t中只有两个索引行,而如果sex列上创建单键值B树索引,则显然该B树索引中的索引行的数量会是10000.
(2)如果需要再多个列上创建索引,那么位图索引与同等条件下的B树索引比起来,往往会显著节省存储空间。比如针对表CUSTOMER上的三列master_status,region和gender,用户可能会使用上述三列中任意单列或多列去访问表customer,如果此时要建B树索引,那么需要建三个复合B树索引(这里考虑到了复合B树索引可以代替单键值B树索引吗,三列复合B树索引可以代替两列复合B树索引)才能涵盖所有的情况,而如果是建位图索引的话,则只需要建三个针对上述三列的单键值位图索引就够了。
(3)位图索引能够快速处理一些包含了各种AND或OR查询条件的sql,这主要是因为位图索引能够实现快捷的按位运算的缘故。
关于位图索引能够实现快捷的按位运算的原理,我们用一个实例来说明。创建一个测试表customer:
SQL> create table customer(customer# number,marital_status varchar2(10),region varchar2(10),gender varchar2(10),income_level varchar2(10));
Table created.
使用如下sql插入6条记录:
SQL> insert into customer values(101,‘single‘,‘east‘,‘male‘,‘bracket_1‘);
1 row created.
SQL> insert into customer values(102,‘married‘,‘central‘,‘female‘,‘bracket_4‘);
1 row created.
SQL> insert into customer values(103,‘married‘,‘west‘,‘female‘,‘bracket_2‘);
1 row created.
SQL> insert into customer values(104,‘divorced‘,‘west‘,‘male‘,‘bracket_4‘);
1 row created.
SQL> insert into customer values(105,‘single‘,‘central‘,‘female‘,‘bracket_2‘);
1 row created.
SQL> insert into customer values(106,‘married‘,‘central‘,‘female‘,‘bracket_3‘);
1 row created.
SQL> commit;
Commit complete.
在列region上创建一个位图索引idx_b_region
SQL> create bitmap index idx_b_region on customer(region);
Index created.
在列martial_status上创建另外一个位图索引idx_b_martialstatus:
SQL> create bitmap index idx_b_maritalstatus on customer(marital_status);
Index created.
SQL> select * from customer;
CUSTOMER# MARITAL_ST REGION GENDER INCOME_LEV
---------- ---------- ---------- ---------- ----------
101 single east male bracket_1
102 married central female bracket_4
103 married west female bracket_2
104 divorced west male bracket_4
105 single central female bracket_2
106 married central female bracket_3
6 rows selected.