位图索引简介
Bitmap 索引适用于键值大量重复的列的查询。
Bitmap 索引对索引列的每一个键值分别索引。对于一个键值,可能分成一到多个范围进行存储。
每个键值的存储范围大致包括以下几个部分。首先是索引的键值,接着存放当前范围的起始rowid
和终止rowid,最后是这个键值在这个范围内的位置编码。将这个十六进制编码转化为二进制后,
编码值是1 的代表记录符合索引的键值,是0 则表示不符合。由于保存了起始和终止的rowid 以
及在这个范围内的位置变化,因此通过转换,Bitmap 索引也可以对应到rowid。所以,Bitmap 索
引可以提供和B-Tree 索引相同的功能。
位图索引区别于传统B*树索引有两个结构特点:其一是叶子节点上是一个可能的索引列取值对应一个叶子节点。另一个就是叶子节点上通过一个位图向量表示对应行是否取定这个索引值。
位图索引主要用于决策支持系统或静态数据,不支持索引行级锁定,如果是并发操作高的系统,不适合使用位图索引。
位图索引的优势
使用位图向量记录对应行的取值情况不仅可以带来存储空间上的节省,而且可以借助计算机位图运算的快速特性来提高索引结果利用率。下面我们通过模拟情况来进行分析。
Bitmap Index模拟说明
假设存在数据表T,有两个数据列A和B,取值如下。
序号 |
A |
B |
1 |
L |
1 |
2 |
T |
2 |
3 |
L |
2 |
4 |
M |
1 |
对两个数据列A、B分别建立位图索引:idx_t_bita和idx_t_bitb。两个索引对应的存储逻辑结构如下:
Idx_t_bita索引结构,对应的是叶子节点:
索引键值 |
起始rowid |
结束rowid |
位图向量 |
L |
xxx |
ttt |
1010 |
T |
xxx |
ttt |
0100 |
M |
xxx |
ttt |
0001 |
Idx_t_bitb索引结构,对应的是叶子节点:
索引键值 |
起始rowid |
结束rowid |
位图向量 |
1 |
xxx |
ttt |
1001 |
2 |
xxx |
ttt |
0110 |
|
|
|
|
对查询“select * from t where b=1 and (a=’L’ or a=’M’)”
分析:位图索引使用方面,和B*索引有很大的不同。B*索引的使用,通常是从根节点开始,经过不断的分支节点比较到最近的符合条件叶子节点。通过叶子节点上的不断Scan操作,“扫描”出结果集合rowid。
而位图索引的工作方式截然不同。通过不同位图取值直接的位运算(与或),来获取到结果集合向量(计算出的结果)。
针对实例SQL,可以拆分成如下的操作:
1、a=’L’ or a=’M’
a=L:向量:1010
a=M:向量:0001
or操作的结果,就是两个向量的或操作:结果为1011。
2、结合b=1的向量
中间结果向量:1011
B=1:向量:1001
and操作的结果,1001。翻译过来就是第一和第四行是查询结果。
3、获取到结果rowid
目前知道了起始rowid和终止rowid,以及第一行和第四行为操作结果。可以通过试算的方法获取到结果集合rowid。
上面的操作演算过程,说明了两个问题:
位图索引是可以多索引共同合作操作的,不像B*树索引只有一个会加入结果集合;
位图索引的工作是通过位逻辑运算,非扫描操作;
实际试验
下面我们通过一系列的实验,来进一步观察结果。
实验环境构建
SQL> create table t as select owner owner1, object_type type1, owner owner2, object_type type2 from dba_objects;
Table created
SQL> desc t;
Name Type Nullable Default Comments
------ ------------ -------- ------- --------
OWNER1 VARCHAR2(30) Y
TYPE1 VARCHAR2(19) Y
OWNER2 VARCHAR2(30) Y
TYPE2 VARCHAR2(19) Y
SQL> create index idx_t_owner1 on t(owner1);
Index created
SQL> create index idx_t_type1 on t(type1);
Index created
SQL> create bitmap index idx_t_owner2bit on t(owner2);
Index created
SQL> create bitmap index idx_t_type2bit on t(type2);
Index created
SQL> exec dbms_stats.gather_table_stats(user,'T',cascade => true);
PL/SQL procedure successfully completed
常规索引实验
我们构建的环境中,字段和类型完全相同。区别就在于使用的索引类型差异。下面我们先进行常规索引实验。
//为防止影响执行计划,先禁用Bitmap Index
SQL> alter index idx_t_owner2bit unusable;
Index altered
SQL> alter index idx_t_type2bit unusable;
Index altered
SQL> set pagesize 1000;
SQL> set linesize 1000;
SQL> explain plan for select * from t where owner1='SCOTT' and type1='TABLE';
已解释。
SQL> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------
Plan hash value: 2154532428
--------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time|
--------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | 28 | 2 (0)| 00:00:01 |
|* 1 | TABLE ACCESS BY INDEX ROWID| T | 1 | 28 | 2 (0)| 00:00:01 |
|* 2 | INDEX RANGE SCAN | IDX_T_OWNER1 | 28 | | 1 (0)| 00:00:01 |
--------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("TYPE1"='TABLE')
2 - access("OWNER1"='SCOTT')
已选择15行。
注意:owner1和type1上均有B*索引,而且均出现在where条件上。结果采用的Scan方式,而且只使用到了一个索引对象。
Bitmap Index索引实验
此次使用Bitmap Index列对应查询条件。
//索引处理
SQL> alter index idx_t_type2bit rebuild;
Index altered
SQL> alter index idx_t_owner2bit rebuild;
Index altered
SQL> alter index idx_t_owner1 unusable;
Index altered
SQL> alter index idx_t_type1 unusable;
Index altered
SQL> explain plan for select * from t where owner2='SCOTT' and type2='TABLE';
已解释。
SQL> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
-------------------------------------------------------------------------------------------------
Plan hash value: 244872826
------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 61 | 1708 | 13 (0)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID | T | 61 | 1708 | 13 (0)| 00:00:01 |
| 2 | BITMAP CONVERSION TO ROWIDS| | | | | |
| 3 | BITMAP AND | | | | | |
|* 4 | BITMAP INDEX SINGLE VALUE| IDX_T_TYPE2BIT | | | | |
|* 5 | BITMAP INDEX SINGLE VALUE| IDX_T_OWNER2BIT | | | | |
------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
4 - access("TYPE2"='TABLE')
5 - access("OWNER2"='SCOTT')
已选择18行。
在一个SQL中,两个Bitmap索引均使用到。
下面实验一个比较复杂的条件。
SQL> explain plan for select * from t where owner2='SCOTT' and (type2='TABLE' or type2='INDEX');
已解释。
SQL> select * from table(dbms_xplan.display);
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------
Plan hash value: 3499411373
-------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 122 | 3416 | 24 (5)| 00:00:01 |
| 1 | TABLE ACCESS BY INDEX ROWID | T | 122 | 3416 | 24 (5)| 00:00:01 |
| 2 | BITMAP CONVERSION TO ROWIDS | | | | | |
| 3 | BITMAP AND | | | | | |
|* 4 | BITMAP INDEX SINGLE VALUE | IDX_T_OWNER2BIT | | | | |
| 5 | BITMAP OR | | | | | |
|* 6 | BITMAP INDEX SINGLE VALUE| IDX_T_TYPE2BIT | | | | |
|* 7 | BITMAP INDEX SINGLE VALUE| IDX_T_TYPE2BIT | | | | |
-------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
4 - access("OWNER2"='SCOTT')
6 - access("TYPE2"='INDEX')
7 - access("TYPE2"='TABLE')
已选择21行。
请注意Bitmap系列and和or操作,和我们在开篇中做的模拟计算如出一辙。
Bitmap Index是一种适应范围比较窄,但是特效针对性很强的索引类型。在一些场合下合适使用,可以带来出乎意料的效果。
位图索引的缺点
下面通过以下实验,来验证Bitmap位图索引较之普通的B-Tree索引锁的“高昂代价”。位图索引会带来“位图段级锁”,实际使用过程一定要充分了解不同索引带来的锁代价情况。
1.为比较区别,创建两种索引类型的测试表
1)在表t_bitmap上创建位图索引
SEC@ora11g> create table t_bitmap (id number(10), name varchar2(10),sex varchar2(1));
Table created.
SEC@ora11g> create bitmap index t_bitmap_idx on t_bitmap(sex);
Index created.
2)在表t_btree上创建普通B-Tree索引
SEC@ora11g> create table t_btree (id number(10), name varchar2(10), sex varchar2(1));
Table created.
SEC@ora11g> create index t_btree_idx on t_btree(sex);
Index created.
2.每张表中初始化两条数据:“一个男孩”和“一个女孩”
注释:
M - Male - 表示男孩;
F - Femail - 表示女孩。
1)初始化数据t_btree表数据
SEC@ora11g> insert into t_btree values (1, 'Secoooler', 'M');
1 row created.
SEC@ora11g> insert into t_btree values (2, 'Anna','F');
1 row created.
2)初始化数据t_bitmap表数据
SEC@ora11g> insert into t_bitmap values (1, 'Secoooler', 'M');
1 row created.
SEC@ora11g> insert into t_bitmap values (2, 'Anna','F');
1 row created.
SEC@ora11g> commit;
Commit complete.
3)查看初始化之后的结果
(1)t_btree表中包含两条数据
SEC@ora11g> select * from t_btree;
ID NAME S
---------- ---------- -
1 Secoooler M
2 Anna F
(2)t_bitmap表中包含两条数据
SEC@ora11g> select * from t_bitmap;
ID NAME S
---------- ---------- -
1 Secoooler M
2 Anna F
3.在两个不同的session中,对具有普通B-Tree索引表t_btree演示插入、修改和删除“男孩”数据
第一个session中的插入后不要提交
SEC@ora11g> insert into t_btree values (3, 'Andy', 'M');
1 row created.
第二个session中插入同样的状态数据,可以看到,插入、修改和删除均能够成功完成
SEC@ora11g> insert into t_btree values (4, 'Tutu', 'M');
1 row created.
SEC@ora11g> update t_btree set sex='M' where id=2;
1 row updated.
SEC@ora11g> delete from t_btree;
2 rows deleted.
4.在两个不同的session中,对具有Bitmap位图索引表t_bitmap演示插入、修改和删除“男孩”数据
1)第一个session中的插入后不要提交
SEC@ora11g> insert into t_bitmap values (3, 'Andy', 'M');
1 row created.
2)第二个session中对男孩数据进行处理,可以看到,只要操作信息中涉及到位图索引列的插入、修改和删除均无法完成!!
(1)插入测试
当插入数据涉及位图索引列“sex”字段时,是无法完成的。
SEC@ora11g> insert into t_bitmap values (4, 'Tutu', 'M');
问题出现了:出现了“锁等待”停滞不动的现象!
当插入数据未涉及位图索引列“sex”字段时,是可以完成的。
SEC@ora11g> insert into t_bitmap(id,name) values (4, 'Tutu');
1 row created.
SEC@ora11g> commit;
Commit complete.
(2)更新测试
此时第二个会话的测试数据内容如下。
SEC@ora11g> select * from t_bitmap;
ID NAME S
---------- ---------- -
1 Secoooler M
2 Anna F
4 Tutu
当更新位图索引列“sex”字段值为“M”时,是无法完成的。
SEC@ora11g> update t_bitmap set sex='M' where id=1;
1 row updated.
此时成功,是因为第一行数据的sex值本身就是“M”。
SEC@ora11g> update t_bitmap set sex='M' where id=2;
问题出现了:出现了“锁等待”停滞不动的现象!
SEC@ora11g> update t_bitmap set sex='M' where id=4;
问题出现了:出现了“锁等待”停滞不动的现象!
另外,特别注意一下,如果更新的列不是位图索引对应的列,将不会受位图段级索引锁的限制。如下所示。
SEC@ora11g> update t_bitmap set name='Xu' where id=2;
1 row updated.
(3)删除测试
当删除的数据包含位图索引列“sex”字段值为“M”时,是无法完成的。
SEC@ora11g> delete from t_bitmap where id=1;
问题出现了:出现了“锁等待”停滞不动的现象!
当删除表中的所有数据时,同样的道理,也是不能删除的。
SEC@ora11g> delete from t_bitmap;
问题出现了:出现了“锁等待”停滞不动的现象!
5.小结
本文以对数据本身冲击力最小的插入动作为例,演示了B-Tree和Bitmap索引的锁代价。对于B-Tree索引来说,插入动作不影响其他会话的DML操作;但是,对于Bitmap索引来说,由于是索引段级锁,会导致与操作列值相关的内容被锁定(文中提到的“M”信息)。进一步,对于更新动作来说,
产生上面现象的原因:
位图索引被存储为压缩的索引值,其中包含了一个范围内的ROWID,因此ORACLE必须针对一个给定值锁定所有范围内的ROWID,不支持行级别的锁定。
换一种描述方法:使用位图索引时,一个键指向多行(成百上千),如果更新一个位图索引键,会同时将其他行对应位图索引字段进行锁定!
较之B-Tree索引优点:
位图以一种压缩格式存放,因此占用的磁盘空间比B-Tree索引要小得多
较之B-Tree索引缺点:
这种锁定的代价很高,会导致一些DML语句出现“锁等待”,严重影响插入、更新和删除的效率,对于高并发的系统不适用。
位图索引使用原则:
位图索引主要用于决策支持系统或静态数据,不支持索引行级锁定。
位图索引最好用于低cardinality列(即列的唯一值除以行数为一个很小的值,接近零),例如上面的“性别”列,列值有“M”,“F”两种。在这个基本原则的基础上,要认真考虑包含位图索引的表的操作特点,如果是并发操作高的系统,不适合使用位图索引!