反向键索引(Reverse Key Index)也是一种B树索引,但是它与一般的B树索引相比又有一个很奇特的地方。反向键索引将索引键值的每一个字节做一个翻转变换,举一个例子:数字123456在反向键索引中的存储形式便是654321。读到这里很多读者就会觉得匪夷所思,因为这会使得本来邻近的整数在索引中变得分散,比如123456、123457被翻转后分别是654321、754321,那么转换后的数字就不会作为邻近的数字存储,索引键的分布变得非常分散。
由于在B树索引中经常要读取以及更新的数据往往被集中在一个特定的范围内,所以经常导致数据块的争用。特别是在Oracle Real Application Clusters环境中,比如一个实例正要往一个表中插入一个值为100的行,与此同时,另一个实例往这个表中插入一个值为101的行,两行数据及2个索引键极有可能是放在同一个数据块及索引叶块中的,两个实例对数据块以及索引叶块的争用便会对应用的并发造成影响。反向键索引在一定程度上可以很好的解决这一问题,反向键索引通过将索引键分散化,可以有效地减少索引叶块的争用。
SQL> create table test_reverse(id int,name char(10));
Table created.
SQL> insert into test_reverse values(123,'lex');
1 row created.
SQL> insert into test_reverse values(124,'tom');
1 row created.
SQL> insert into test_reverse values(125,'john');
1 row created.
SQL> commit;
Commit complete.
创建B树索引,dump索引块,查看索引列存储方式及顺序:
SQL> create index idx_test_reverse on test_reverse(id);
Index created.
SQL> set serveroutput on
SQL> exec seg_unused_space('IDX_TEST_REVERSE','INDEX','LEX');
total_blocks is 8
total_bytes is 65536
unused_blocks is 4
unused_bytes is 32768
last_used_extent_file_id is 4
last_used_extent_block_id is 5440
last_used_block is 4
PL/SQL procedure successfully completed.
SQL> alter system dump datafile 4 block 5443;
System altered.
row#0[8019] flag: ——, lock: 0, len=13
col 0; len 3; (3): c2 02 18
col 1; len 6; (6): 01 00 15 3f 00 00
row#1[8006] flag: ——, lock: 0, len=13
col 0; len 3; (3): c2 02 19
col 1; len 6; (6): 01 00 15 3f 00 01
row#2[7993] flag: ——, lock: 0, len=13
col 0; len 3; (3): c2 02 1a
col 1; len 6; (6): 01 00 15 3f 00 02
SQL> SELECT DUMP(123,16) DUMP FROM DUAL
2 UNION ALL
3 SELECT DUMP(124,16) FROM DUAL
4 UNION ALL
5 SELECT DUMP(125,16) FROM DUAL;
DUMP
--------------------
Typ=2 Len=3: c2,2,18
Typ=2 Len=3: c2,2,19
Typ=2 Len=3: c2,2,1a
在普通B树索引中,索引列的存放顺序为有序的123、124、125。那么在反向键索引中呢?
SQL> drop index idx_test_reverse;
Index dropped.
SQL> commit;
Commit complete.
SQL> create index reverse_test on test_reverse(id) reverse;
Index created.
SQL> alter system checkpoint;
System altered.
SQL> exec seg_unused_space('REVERSE_TEST','INDEX','LEX');
total_blocks is 8
total_bytes is 65536
unused_blocks is 4
unused_bytes is 32768
last_used_extent_file_id is 4
last_used_extent_block_id is 5440
last_used_block is 4
PL/SQL procedure successfully completed.
SQL> ALTER SYSTEM DUMP DATAFILE 4 BLOCK 5443;
System altered.
row#0[8019] flag: ——, lock: 2, len=13
col 0; len 3; (3): 18 02 c2
col 1; len 6; (6): 01 00 15 3f 00 00
row#1[8006] flag: ——, lock: 2, len=13
col 0; len 3; (3): 19 02 c2
col 1; len 6; (6): 01 00 15 3f 00 01
row#2[7993] flag: ——, lock: 2, len=13
col 0; len 3; (3): 1a 02 c2
col 1; len 6; (6): 01 00 15 3f 00 02
索引键值被翻转,顺序也发生了改变。
很明显,反向键索引将索引键分散化也是有代价的,由于索引列的值不是连续的,所以无法进行索引范围扫描(INDEX RANGE SCAN),因为范围扫描中找到查询的第一个值后在进行下一个扫描时无法查找到满足条件的下一个值。
查询示例:
SQL> select id from test_reverse where id=123 or id=124;
ID
----------
123
124
Execution Plan
----------------------------------------------------------
Plan hash value: 1306464222
----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 2 | 8 | 1 (0)| 00:00:01 |
| 1 | INLIST ITERATOR | | | | | |
|* 2 | INDEX RANGE SCAN| REVERSE_TEST | 2 | 8 | 1 (0)| 00:00:01 |
----------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("ID"=123 OR "ID"=124)
Statistics
----------------------------------------------------------
1 recursive calls
0 db block gets
3 consistent gets
0 physical reads
0 redo size
456 bytes sent via SQL*Net to client
420 bytes received via SQL*Net from client
2 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
2 rows processed
本来应该进行的INDEX RANGE SCAN无法完成此查询,需以INLIST ITERATOR迭代方式进行,查询中有1个OR子句,所以需要对索引进行2次检索。
反向键索引并不是一种被广泛使用的索引方式,主要适用于使用等值运算符“=”进行的查询。它的索引键值的分布是分散式的,并不是按照索引列的有序方式存储,因而无法进行范围化的查询。反向键索引的优势:一方面,它可以平衡I/O,有效地减少应用并发时的争用,另一方面,多数情况下数据在堆表中是按照插入的顺序而存储在邻近的位置上,所以数据块在内存中被再次使用的可能性是比较大的。总的来说,大多数情况下反向键索引带来的性能收益往往小于其所花费的代价,应用范围偏窄,在特殊情况下可以灵活使用。