位图(bitmap)索引是另外一种索引类型它的组织形式与B树索引相同也是一棵平衡树与B树索引的区别在于叶子节点里存放索引条目的方式不同从前面我们知道B树索引的叶子节点里对于表里的每个数据行如果被索引列的值不为空的则会为该记录行在叶子节点里维护一个对应的索引条目
而位图索引则不是这样其叶子节点里存放的索引条目如下图所示
假设某个表T里所有的记录在列C上只具有三个值和在表T的C列上创建位图索引以后则叶子节点的内容如图所示可以看到位图索引只有三个索引条目也就是每个C列的值对应一个索引条目位图索引条目上还包含表里第一条记录所对应的ROWID以及最后一条记录所对应的ROWID索引条目的最后一部分则是由多个bit位所组成的bitmap每个bit位就对应一条记录
位图索引的结构
当发出where c=这样的SQL语句时oracle会去搜索所在的索引条目然后扫描该索引条目中的bitmap里所有的bit位第一个bit位为则说明第一条记录上的C值为于是返回第一条记录所在的ROWID(根据该索引条目里记录的start ROWID加上行号得到该记录所在的ROWID)第二个bit位为则说明第二条记录上的C值不为依此类推另外如果索引列为空也会在位图索引里记录也就是将对应的bit位设置为即可
如果索引列上不同值的个数比较少的时候比如对于性别列(男或女)等则使用位图索引会比较好因为它对空间的占用非常少(因为都是用bit位来表示表里的数据行)从而在扫描索引的时候扫描的索引块的个数也比较少可以试想一下如果在列的不同值非常多的列上比如主键列上创建位图索引则产生的索引条目就等于表里记录的条数同时每个索引条目里的bitmap里只有一个其它都是这样还不如B树索引的效率高
如果被索引的列经常被更新的话则不适合使用位图索引因为当更新位图所在的列时由于要在不同的索引条目之间修改bit位比如将第一条记录从变为则必须将所在的索引条目的第一个bit位改为再将所在的索引条目的第一个bit位改为因此在更新索引条目的过程中会锁定位图索引里多个索引条目也就是同时只能有一个用户能够更新表T从而降低了并发性
位图索引比较适合用在数据仓库系统里不适合用在OLTP系统里
SQL> create table t_bitmap_test as
select rownum as idtrunc(dbms_randomvalue()) as bitcol
from dba_objects where rownum<=;
SQL> select * from t_bitmap_test;
ID BITCOL
SQL> connect hr/hr
已连接
SQL> alter session set events trace name context forever level ;
会话已更改
SQL> create bitmap index idx_t_bitmap_test on t_bitmap_test(bitcol);
索引已创建
SQL> alter session set events trace name context off;
会话已更改
SQL> select object_id from user_objects where object_name=IDX_T_BITMAP_TEST;
OBJECT_ID
SQL> alter session set events immediate trace name TREEDUMP level ;
会话已更改
事件用来跟蹤创建bitmap索引的过程而TREEDUMP则用来转储索引的树状结构打开转储出来的文件
*** SESSION ID:() ::
qerbiARwo: bitmap size is
qerbiIPI default pctfree=
qerbiIPI length=
qerbiAllocate pfree= space=
qerbiStart first start
qerbiRop: rid=cce new=Y key: (): c
qerbiCmpSz notfound pctfree=
qerbiCmpSz adjblksize= length=
qerbiRop keysize= maxbm=
kdibcoinit(): srid=cce
qerbiRop: rid=cce new=Y key: (): c
kdibcoinit(): srid=cce
qerbiRop: rid=cce new=Y key: (): c
kdibcoinit(c): srid=cce
qerbiRop: rid=cce new=N key: (): c
qerbiRop: rid=cce new=N key: (): c
qerbiRop: rid=cce new=N key: (): c
qerbiRop: rid=cce new=N key: (): c
qerbiRop: rid=cce new=N key: (): c
qerbiRop: rid=cce new=N key: (): c
qerbiRop: rid=cce new=N key: (): c
qerbiRop: rid=ccea new=N key: (): c
qerbiRop: rid=cceb new=N key: (): c
qerbiRop: rid=ccec new=N key: (): c
qerbiRop: rid=cced new=N key: (): c
qerbiRop: rid=ccee new=N key: (): c
qerbiRop: rid=ccef new=N key: (): c
qerbiRop: rid=cce new=N key: (): c
qerbiRop: rid=cce new=N key: (): c
qerbiRop: rid=cce new=N key: (): c
qerbiRop: rid=cce new=N key: (): c
kdibcoend(): erid=ccestatus=
qerbiCon: key: (): c
srid=cce erid=cce bitmap: (): ca
kdibcoend(): erid=ccestatus=
qerbiCon: key: (): c
srid=cce erid=cce bitmap: (): ca c
kdibcoend(c): erid=ccestatus=
qerbiCon: key: (): c
srid=cce erid=cce bitmap: (): ca
这一段是创建bitmap索引的过程我们先把被索引的列的值换算成十六进制
SQL> select dump()dump()dump() from dual;
DUMP() DUMP() DUMP()
Typ= Len=: Typ= Len=: Typ= Len=:
对应的十六进制则是也就是上面转储部分显示的key部分的键值
可
以看到oracle在创建bitmap索引时先从第一条记录开始扫描取出第一条记录的键值(bitcol=)也就是qerbiRop:
rid=cce new=Y key: (): c
new=Y说明这是一个新的键值因此会对应到一个索引条目扫描第二条记录时发现bitcol=该键值也是一个新的键值因此产生一个新
的索引条目对应qerbiRop: rid=cce new=Y key: (): c
扫描到第三条记录时发现bitcol=该键值也是一个新的键值因此产生第三个索引条目对应qerbiRop:
rid=cce new=Y key: (): c
接下来扫描到的记录所对应的bitcol的值都是中的一个因此都不会产生新的索引条目因此它们的new都为N
然后扫描完表里的所有记录以后开始创建bitmap索引条目也就是下面的部分
qerbiCon: key: (): c
srid=cce erid=cce bitmap: (): ca
kdibcoend(): erid=ccestatus=
qerbiCon: key: (): c
srid=cce erid=cce bitmap: (): ca c
kdibcoend(c): erid=ccestatus=
qerbiCon: key: (): c
srid=cce erid=cce bitmap: (): ca
这里的srid表示start rowiderid表示end rowid
可以看到总共产生了个索引条目其key分别为
这
个索引条目的start rowid和end
rowid的格式分两部分中间用点隔开点左边的表示文件号(从左边第一个字节开始的个字节表示)和数据块号(从左边第五个字节开始的个字节表
示)点右边表示数据块里的行号这里的显示可以看到这条记录都位于相同的数据块里这里的c表示文件号
SQL> select syspkg_number_transf_hex_to_dec(c)/ file# FROM dual;
FILE#
SQL> select syspkg_number_transf_hex_to_dec(ce) as blk# FROM dual;
BLK#
因此这条记录在号数据文件的号数据块里我们可以使用dbms_rowid来验证
SQL> select distinct dbms_rowidrowid_relative_fno(rowid) as file#
dbms_rowidrowid_block_number(rowid) as block#
from t_bitmap_test;
FILE# BLOCK#
可以看到完全符合
每个索引条目的bitmap : ()部分表示的也就是前面说到的bit位了由组成
按照前面bitmap的理论这条记录所对应的三个索引条目的bitmap的样子应该为
Key_value start_rowid end_rowid 理论上的bitmap 转储文件的bitmap
cce cce ca
cce cce ca c
cce cce ca
转储文件里的bitmap如何对应到bit位呢 ?首先第一个字节的ca可以不考虑暂时不知道第一个字节是什么意思只考虑后三个字节比如对于key_value=来说对应的二进制为
SQL> col c format a
SQL> col c format a
SQL> col c format a
SQL> select syspkg_number_transf_hex_to_bin() as c
syspkg_number_transf_hex_to_bin() as c
syspkg_number_transf_hex_to_bin() as c from dual;
C C C
其中不足位的前面用补齐因此C=C=C=
在二进制里每个应该倒过来从右到左排列因此为
C C C
然后将它们组织为一个由多个bit位组成的bitmap的话仍然从右到左依次取出每个bit位于是我们有我们可以把这个bit串与理论上的bitmap比较一下
很明显除了最后多出来的个以外其余部分完全一致而最后多出的并不影响这个索引条目的使用
类似的我们可以使用相同的方法来依次验证另外两个键值都是符合理论值的
数据都在一个数据块里的情况比较容易理解如果被索引的数据分布在多个数据块里呢?
经常会有人问到只记录start rowid和end rowid如果被索引的记录分布在多个数据块里那么oracle如何根据start rowed来找到bitmap里的bit=所对应的记录的rowid呢?
创建一个表
SQL> create table t_bitmap_(id numberbitcol char());
insert into t_bitmap_ values(A);
insert into t_bitmap_ values(A);
insert into t_bitmap_ values(A);
insert into t_bitmap_ values(B);
insert into t_bitmap_ values(A);
insert into t_bitmap_ values(A);
insert into t_bitmap_ values(B);
insert into t_bitmap_ values(A);
insert into t_bitmap_ values(A);
insert into t_bitmap_ values(A);
insert into t_bitmap_ values(B);
insert into t_bitmap_ values(B);
insert into t_bitmap_ values(B);
insert into t_bitmap_ values(B);
insert into t_bitmap_ values(B);
insert into t_bitmap_ values(B);
COMMIT;
获得这条记录所在的数据块号
SQL> select distinct dbms_rowidrowid_relative_fno(rowid) as file#
dbms_rowidrowid_block_number(rowid) as block#
from t_bitmap_;
FILE# BLOCK#
可以知道这条记录分布在个数据块里
然后跟蹤对于bitcol列上的bitmap索引的创建过程
alter session set events trace name context forever level ;
create bitmap index idx_t_bitmap_ on t_bitmap_(bitcol);
alter session set events trace name context off;
从转储出来的文件可以看到最终形成了两个索引条目
……
qerbiCon: key: ():
……
srid=cd erid=cd bitmap: (): c c f b f
……
qerbiCon: key: ():
……
srid=cd erid=cd bitmap: (): f f c a c
*** ::
很明显这里的两个索引条目的start rowed和end rowed是不相同的
键值为A的索引条目为
srid=cd erid=cd bitmap: (): c c f b f
其数据块从d到d也就是
SQL> select syspkg_number_transf_hex_to_dec(d) as s_blk#
syspkg_number_transf_hex_to_dec(d) as e_blk#
from dual;
S_BLK# E_BLK#
而键值B的索引条目为
srid=cd erid=cd bitmap: (): f f c a c
其数据块从d到d也就是
SQL> select syspkg_number_transf_hex_to_dec(d) as s_blk#
syspkg_number_transf_hex_to_dec(d) as e_blk#
from dual;
S_BLK# E_BLK#
这个时候
SQL> select substr(bitcol) as bitcoldbms_rowidrowid_block_number(rowid) as block# from t_bitmap_;
BI BLOCK#
B
A
A
A
B
B
B
B
B
A
A
A
B
A
A
B
这时oracle放了很多的bit来对应这条记录但是oracle如何根据这些bit位来找对应的rowid就猜不出了