1.分区索引
索引也可以像表分区结构那样来进行分区。有多种方法可以对索引进行分区。在分区表上可以创建局部或者全局索引。并且,有多种分区方案可选,如范围分区,散列分区,列表分区以及混合分区方案。自oracle数据库10g版本以来,也可以在非分区表上建立分区索引。
1.1 局部索引
局部分区索引使用LOCAL关键字来建立,其分区边界与表相同。简单来说,与每个表分区相联结的有一个索引分区。因为维护操作可以在独立分区区级进行,表的可用性更好。对索引分区的维护操作仅需要锁定相应的表分区而不是整张表。
如果局部索引分区键列并且如果SQL语句声明了分区键列上的谓语,执行计划就仅需要访问一个或者很少的索引分区。这个概念通常被称为分区消除。如果执行计划在最小的分区由进行搜索,性能就会得到提升。
create table sales_fact_part
partition by range(year)
(partition p_1997 values less than (1998),
partition p_1998 values less than (1999),
partition p_1999 values less than (2000),
partition p_2000 values less than (2001),
partition p_max values less than (maxvalue)
) as select * from sales_fact;
SQL> set lines 120 pages 100
SQL> set serveroutput off
SELECT *
FROM (SELECT * FROM sales_fact_part WHERE product = 'Xtend Memory')
3 WHERE rownum < 21;
SQL> @x.sql
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID c726kzar01w0h, child number 0
-------------------------------------
SELECT * FROM (SELECT * FROM sales_fact_part WHERE product = 'Xtend
Memory') WHERE rownum < 21
Plan hash value: 2438495830
--------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name| Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
--------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT || | | 16 (100)| || |
|* 1 | COUNT STOPKEY || | | | || |
| 2 | PARTITION RANGE ALL | | 1895 | 218K| 16 (0)| 00:00:01 | 1 | 5 |
| 3 | TABLE ACCESS BY LOCAL INDEX ROWID| SALES_FACT_PART| 1895 | 218K| 16 (0)| 00:00:01 | 1 | 5 |
|* 4 | INDEX RANGE SCAN | SALES_FACT_PART_N1 || | 6 (0)| 00:00:01 | 1 | 5 |
--------------------------------------------------------------------------------------------------------------------------
SELECT *
FROM (SELECT *
FROM sales_fact_part
WHERE product = 'Xtend Memory'
AND YEAR = 1998)
WHERE rownum < 21;
SQL> @x.sql
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID 2r316z9t211gw, child number 0
-------------------------------------
SELECT * FROM (SELECT * FROM sales_fact_part WHERE
product = 'Xtend Memory' AND YEAR = 1998) WHERE rownum < 21
Plan hash value: 3853015538
--------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name| Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
--------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT || | | 15 (100)| || |
|* 1 | COUNT STOPKEY || | | | || |
| 2 | PARTITION RANGE SINGLE | | 453 | 53454 | 15 (0)| 00:00:01 | 2 | 2 |
| 3 | TABLE ACCESS BY LOCAL INDEX ROWID| SALES_FACT_PART| 453 | 53454 | 15 (0)| 00:00:01 | 2 | 2 |
|* 4 | INDEX RANGE SCAN | SALES_FACT_PART_N1 || | 3 (0)| 00:00:01 | 2 | 2 |
--------------------------------------------------------------------------------------------------------------------------
尽管表的可用性很重要,应该仍然要考虑另外一点:如果谓语没有声明分区键列,那么局部索引中必须访问所有的分区以识别候选的数据行。如果分区数非常多,到达几千的量级的话,这可能会导致性能问题的出现。即使这样,也需要衡量创建局部索引而不是全局索引带来的影响。
1.2 全局索引
全局索引通过关键字GLOBAL来创建。在全局索引中,索引的分区边界与表分区边界不一定要匹配,并且表和索引的分区键也可以不一样。
create index sales_index_fact_part_n1 on sales_fact_part(year)
global partition by range(year)
(partition p_1998 values less than(1999),
partition p_2000 values less than(2001),
partition p_max values less than(maxvalue));
--------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name| Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
--------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT || | | 3 (100)| || |
|* 1 | COUNT STOPKEY || | | | || |
| 2 | PARTITION RANGE SINGLE || 180 | 21240 | 3 (0)| 00:00:01 | 2 | 2 |
| 3 | TABLE ACCESS BY LOCAL INDEX ROWID| SALES_FACT_PART| 180 | 21240 | 3 (0)| 00:00:01 | 2 | 2 |
|* 4 | INDEX RANGE SCAN | SALES_FACT_PART_N1 | 180 || 1 (0)| 00:00:01 | 2 | 2 |
--------------------------------------------------------------------------------------------------------------------------
唯一索引可以建立为不包含分区列的全局索引。但如果建立唯一局部索引,则表的分区键必须包含在局部索引中
2 散列分区与范围分区;
在散列分区方案中,分区键列的值使用散列算法进行散列化来确定存储数据行的分区.这种类型的分区方案适合于分区列使用人造键值填充的情况,例如分区列由顺序生成的值填充。如果列值得分别是均匀的,那么每个分区将会存储几乎相等数目的数据行。
散列分区方案还有几个额外的优点。范围分区方案有一些管理开支,因为在将来想放入新的数据行就需要定期加入新的分区。例如,如果分区键是order_date列,那么就必须增加新分区来放入将来的日期的数据。在散列分区中,这个开支就避免了,因为数据行在使用散列算法进行分区的各个分区是均匀分布的。如果列值是均匀分布的则所有分区将保持近似相等数量的数据行,也就没有理由定期增加更多新的分区了。
散列分区表和索引在应对由唯一索引和主键索引引起的与并发相关的性能问题时时非常有效的。典型的主键可能会使用生成的顺序列值来填充。因为索引按照排序后的顺序存储列值,新的数据行的列值将会进入到索引最右侧的叶子块。在该叶子块满了以后,接下来插入的数据行就会进入新的最右侧叶子块中,资源争夺点也就从一个叶子块转移到另一叶子块。随着表的插入并发性增加,会话将会大幅修改索引的最右侧块。基本上,索引的当前最右侧叶子块将会是最主要的资源争夺点。将会看到会话等待事件如缓存区繁忙等待。在RAC中,由于全局缓存通信的成本导致这个问题放大,gc缓冲繁忙将会排在第一为的等待事件。这种类型的最右侧索引的快速增长被称为右侧增长索引。
create sequence sfseq cache 200;
drop table sales_fact_part;
create table sales_fact_part
partition by hash(id)
partitions 32
as select sfseq.nextval id ,f.* from sales_fact f;
create unique index sales_fact_part_n1 on sales_fact_part(id) local;
SQL> select * from sales_fact_part where id=1000;
ID COUNTRY REGION PRODUCT YEAR WEEK SALERECEIPTS
---------- ---------------------------------------- ------------------------------ -------------------------------------------------- ---------- ---------- ---------- ----------
1000 United States of America Northern America Martial Arts Champions 1999 6 288.99 307.633
SQL> @x.sql
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID bfy3qhpjprucg, child number 0
-------------------------------------
select * from sales_fact_part where id=1000
Plan hash value: 1603644595
-------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name| Rows | Bytes | Cost (%CPU)| Time| Pstart| Pstop |
-------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT || | | 2 (100)| || |
| 1 | PARTITION HASH SINGLE || 1 | 131 | 2 (0)| 00:00:01 | 25 | 25 |
| 2 | TABLE ACCESS BY LOCAL INDEX ROWID| SALES_FACT_PART| 1 | 131 | 2 (0)| 00:00:01 | 25 | 25 |
|* 3 | INDEX UNIQUE SCAN | SALES_FACT_PART_N1 | 1 || 1 (0)| 00:00:01 | 25 | 25 |
-------------------------------------------------------------------------------------------------------------------------
如果分区键上的数据分布式均匀的,如有序列生成的情况,那么数据行在所有分区上均匀分布。你可以使用dbms_rowid包来衡量散列分区表上的数据分布。
SQL> 1 SELECT dbms_rowid.rowid_object(ROWID) obj_id, COUNT(*)
2 FROM sales_fact_part
3* GROUP BY dbms_rowid.rowid_object(ROWID)
SELECT dbms_rowid.rowid_object(ROWID) obj_id, COUNT(*)
FROM sales_fact_part
3 GROUP BY dbms_rowid.rowid_object(ROWID);
OBJ_ID COUNT(*)
---------- ----------
89172 3478
89175 3544
89177 3542
89182 3470
89159 3481
89164 3571
89169 3376
89178 3402
89180 3420
89181 3424
89173 3467
89179 3484
89156 3426
散列分区算法:如果提供了分区键值,就可以使用ora_hash函数获得分区ID,ORA_HASH 函数的第2个参数问分区数减1.
SELECT dbms_rowid.rowid_object(ROWID) obj_id,
ora_hash(id, 31.0) part_id,
COUNT(*)
FROM sales_fact_part
GROUP BY dbms_rowid.rowid_object(ROWID), ora_hash(id, 31.0)
6 ORDER BY 1;
OBJ_ID PART_ID COUNT(*)
---------- ---------- ----------
89153 0 3505
89154 1 3492
89155 2 3572
89156 3 3426
89157 4 3480
89158 5 3427
89159 6 3481
89160 7 3457
3. 基于函数的索引:
如果一个谓语在索引列上应用了函数,则优化器不会选用该列上的索引。例如,对于谓语to_char(i)=‘1000’,不会选用id列上的索引,因为索引列上应用了to_char函数,这个限制可以通过表达是to_char(id)上创建基于函数的索引来克服。基于函数的索引预存函数的结果。谓语中所声明的表达是必须与基于函数的索引所声明表示相匹配。
基于函数的索引也可以建立在用户自定义函数上,但这个函数必须定义为确定性函数,也就是说对于这个函数的每一次执行必须返回一致的值。
SQL> select * from sales_fact_part where to_char(id)='1000';
select * from sales_fact_part where to_char(id)='1000'
Plan hash value: 893548352
------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | |478 (100)| | | |
| 1 | PARTITION HASH ALL| | 1 | 131 |478 (1)| 00:00:01 | 1 | 32 |
|* 2 | TABLE ACCESS FULL| SALES_FACT_PART | 1 | 131 | 478 (1)| 00:00:01 | 1 | 32 |
create index sales_fact_part_fbi1 on sales_fact_part(to_char(id));
SQL> @analyze_table_sfp.sql
PL/SQL procedure successfully completed.
SQL> @x.sql
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID 50hdw2f955xpj, child number 0
-------------------------------------
select * from sales_fact_part where to_char(id)='1000'
Plan hash value: 3799936162
---------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Pstart| Pstop |
---------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 2 (100)| | | |
| 1 | TABLE ACCESS BY GLOBAL INDEX ROWID| SALES_FACT_PART | 1 | 77 |2 (0)| 00:00:01 | ROWID | ROWID |
|* 2 | INDEX RANGE SCAN | SALES_FACT_PART_FBI1 |1 | | 1 (0)| 00:00:01 | | |
---------------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("SALES_FACT_PART"."SYS_NC00009$"='1000')
SELECT data_default, hidden_column, virtual_column
FROM dba_tab_cols
WHERE table_name = 'SALES_FACT_PART'
4 AND virtual_column = 'YES';
DATA_DEFAULT HID VIR
-------------------------------------------------------------------------------- --- ---
TO_CHAR("ID") YES YES
SQL> col INDEX_NAME for a20
SQL> col COLUMN_NAME for a20
SELECT index_name, column_name
FROM dba_ind_columns
3 WHERE index_name = 'SALES_FACT_PART_FBI1';
INDEX_NAME COLUMN_NAME
-------------------- --------------------
SALES_FACT_PART_FBI1 SYS_NC00009$
说明:在增加了基于函数的索引后收集表的统计信息很重要,如果不收集,新的虚拟列就没有统计信息。以下脚本用于收集统计信息(要设置cascade=>true):
begin
dbms_stats.gather_table_stats (
ownname =>user,
tabname=>'SALES_FACT_PART',
estimate_percent=>30,
cascade=>true);
end;
4.反键索引:
反键索引中,列值按照逐个字符的反向顺序存储。因为列值是按照反向顺序存储的,连续的列值将会存储在不同的索引叶子块中,从而避免了右侧增长索引所带来的资源争夺问题。但是,在表的数据块中,这些列值还是存储的为顺序的。
反键索引有两个问题。
(1).反转键索引的范围扫描不能使用范围运算符如between,<,>等,因为索引范围扫描的基本假设就是列值按照逻辑升序或者降序存储,反键索引由于列值按照反转顺序存储,没有安装逻辑键的顺序来维护,违反了这个假设,
(2).反键索引可能会人为增加物理读取的次数,因为列值被存储在很多叶子块中,而这些叶子块可能需要读取到缓冲区缓存中来修改块。但是,这个I/O成本的增加需要与右侧增长索引所引起的并发性问题相对照来衡量。
drop index sales_fact_part_n1;
create unique index sales_fact_part_n1 on sales_fact_part(id) global reverse;
SQL> select * from sales_fact_part where id=1000;
SQL> @x.sql
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------
SQL_ID bfy3qhpjprucg, child number 0
-------------------------------------
select * from sales_fact_part where id=1000
Plan hash value: 1855157343
--------------------------------------------------------------------------
| Id | Operation | Name | E-Rows |
--------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | |
| 1 | TABLE ACCESS BY GLOBAL INDEX ROWID| SALES_FACT_PART | 1 |
|* 2 | INDEX UNIQUE SCAN | SALES_FACT_PART_N1 | 31 |
--------------------------------------------------------------------------
select * from sales_fact_part where id between 1000 and 1001
Plan hash value: 893548352
------------------------------------------------------
| Id | Operation | Name | E-Rows |
-------------------------------------------------------
| 0 | SELECT STATEMENT | | |
| 1 | PARTITION HASH ALL| | 3 |
|* 2 | TABLE ACCESS FULL| SALES_FACT_PART | 3 |
-------------------------------------------------------
尤其在RAC中,右侧增长索引可能会引起不能容忍的性能问题,反键索引被引入来解决性能问题,但有的时候可能应该考虑散列分区索引而不是反转键索引。
5.降序索引
索引默认按照升序存储列值,但可以通过使用降序索引来切换为降序存储,如果你的应用按照特定的顺序来获取数据,则在数据行被发送给应用之前需要进行排序。通过降序索引可以避免这个排序。如果应用按照某个特定的顺序上百万次地获取数据,则这类索引是非常有用的。从oracle 11gR2开始,降序索引实现基于函数的索引
SQL> drop index sales_fact_c1;
Index dropped.
SQL> create index sales_fact_c1 on sales_fact(product desc, year desc,week desc);
Index created.
set lines 120 pages 100
begin
dbms_stats.gather_table_stats (
ownname =>user,
tabname=>'SALES_FACT',
estimate_percent=>30,
cascade=>true);
end;
8 /
PL/SQL procedure successfully completed.
SQL> set termout off
SELECT YEAR, week FROM sales_fact s WHERE YEAR IN (1998, 1999, 2000)
AND week < 5 AND product = 'Xtend Memory' ORDER BY product DESC,
YEAR DESC, week DESC
Plan hash value: 2514767912
---------------------------------------------------
| Id | Operation | Name | E-Rows |
---------------------------------------------------
| 0 | SELECT STATEMENT | | |
|* 1 | INDEX RANGE SCAN| SALES_FACT_C1 | 107 |--语句执行过程中没有排序步骤
---------------------------------------------------
5 .不可见索引
在某些场景下,你可能需要增加一个索引来对SQL语句的性能进行调优,但是你不太确定索引带来的负面影响,不可见索引以较小的风险来衡量新索引所带来的影响方面非常有用,一个索引可以加入到数据库中并被标记为不可见,这样优化器就不会选用这个索引,可以确定某个索引没有负面影响或对执行计划没有负面影响后将它标记为可见。
SELECT * FROM (SELECT * FROM sales_fact WHERE
product = 'Xtend Memory' AND YEAR = 1998 AND week
= 1) WHERE rownum < 21
Plan hash value: 3021657266
---------------------------------------------------------------
| Id | Operation | Name | E-Rows |
---------------------------------------------------------------
| 0 | SELECT STATEMENT | | |
|* 1 | COUNT STOPKEY | | |
| 2 | TABLE ACCESS BY INDEX ROWID| SALES_FACT | 7 |
|* 3 | INDEX RANGE SCAN | SALES_FACT_C1 | 9 |
---------------------------------------------------------------
SQL> alter index sales_fact_c1 invisible;
Index altered.
SELECT * FROM (SELECT * FROM sales_fact WHERE
product = 'Xtend Memory' AND YEAR = 1998 AND week
= 1) WHERE rownum < 21
Plan hash value: 2622531735
--------------------------------------------------
| Id | Operation | Name | E-Rows |
--------------------------------------------------
| 0 | SELECT STATEMENT | | |
|* 1 | COUNT STOPKEY | | |
|* 2 | TABLE ACCESS FULL| SALES_FACT | 7 |
--------------------------------------------------
在会话级别设置为TRUE后,优化器又选择了索引:
SQL> alter session set optimizer_use_invisible_indexes=true;
Session altered.
SELECT * FROM (SELECT * FROM sales_fact WHERE
product = 'Xtend Memory' AND YEAR = 1998 AND week
= 1) WHERE rownum < 21
Plan hash value: 3021657266
---------------------------------------------------------------
| Id | Operation | Name | E-Rows |
---------------------------------------------------------------
| 0 | SELECT STATEMENT | | |
|* 1 | COUNT STOPKEY | | |
| 2 | TABLE ACCESS BY INDEX ROWID| SALES_FACT | 7 |
|* 3 | INDEX RANGE SCAN | SALES_FACT_C1 | 9 |
---------------------------------------------------------------
不可见索引还有另一个应用场景,这种索引有助于在删除不使用的索引时用来降低风险。从生产数据库中删除不使用的索引并不是不常见,可能之后却意外地认识到删除索引在一个很重要的报表中用到了,即使经过充分分析后 ,也可能被删除的索引在某个商务过程中必须得,而重建索引可能会导致应用停机。从oracle 11g以来,你可以将索引标记为不可见,等过了几周以后,如果没有任何进程要用到这个索引,则可以较为安全地将其删掉。如果索引被标记为不可见后发现是需要的,则可以很快使用一个SQL御姐将索引还原为可见状态。
6.虚拟索引
虚拟索引对于查看索引的有效性是很有用的,虚拟索引不会分配存储空间,因此可以很快建立。虚拟索引与不可见索引的不同之处在于不可见索引是有与之相关的存储的,只是优化器不能选择它们,而虚拟索引没有与之关联的存储空间,由于这个 原因,虚拟索引也被称为无段索引。
会话可修改一个划线参数_USE_NOSEGMENT_INDEXES控制了优化器是否可以考虑选择虚拟索引。这个参数默认值是False,应用不会选择虚拟索引。使用nosegment子句创建虚拟索引。
SQL> create index sales_virt on sales(cust_id,promo_id) nosegment;
Index created.
SQL> alter session set "_use_nosegment_indexes"=true;
Session altered.
SQL> explain plan for select * from sales where cust_id=:b1 and promo_id=:b2;
Explained.
SQL> select * from table(dbms_xplan.display(null,'','all'));
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------
Plan hash value: 2769445196
----------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | Ps
tart| Pstop |
----------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 33 | 957 | 9 (0)| 00:00:01 |
| |
| 1 | TABLE ACCESS BY GLOBAL INDEX ROWID| SALES | 33 | 957 | 9 (0)| 00:00:01 | RO
WID | ROWID |
|* 2 | INDEX RANGE SCAN | SALES_VIRT | 9188 | | 1 (0)| 00:00:01 |
| |
----------------------------------------------------------------------------------------------------
6.位图联结索引
位图联结索引对于数据仓库应用中物化事实表和纬度表之间的联结是很有用的,在数据仓库表中,一般来说,事实表比纬度表要大得多,并且纬度和事实表使用主键进行联结--它们之间存在外键关系。这种联结的成本由于事实表很大而更高。如果能够预先存储联结结果则这些查询的性能就会得到提高。物化视图是预先存储计算联结结果的可选项之一。位图联结索引是另一个可选项。
典型的DW查询:
SELECT SUM(s.quantity_sold), SUM(s.amount_sold) FROM sales s,
products p, customers c, channels ch WHERE s.prod_id = p.prod_id
AND s.cust_id = c.cust_id AND s.channel_id = ch.channel_id AND
p.prod_name = 'Y box' AND c.cust_first_name = 'Abigail' AND
ch.channel_desc = 'Direct_sales'
Plan hash value: 2309889988
------------------------------------------------------------------------------------------------------
| Id | Operation | Name | E-Rows | OMem | 1Mem | Used-Mem |
------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | | |
| 1 | SORT AGGREGATE | | 1 | | | |
|* 2 | HASH JOIN | | 20 | 2293K| 2293K| 1536K (0)|
|* 3 | TABLE ACCESS FULL | CUSTOMERS | 43 | | | |
| 4 | NESTED LOOPS | | 3235 | | | |
| 5 | NESTED LOOPS | | 3235 | | | |
| 6 | MERGE JOIN CARTESIAN | | 1 | | | |
|* 7 | TABLE ACCESS FULL | CHANNELS | 1 | | | |
| 8 | BUFFER SORT | | 1 | 73728 | 73728 | |
|* 9 | TABLE ACCESS FULL | PRODUCTS | 1 | | | |
| 10 | PARTITION RANGE ALL | | | | | |
| 11 | BITMAP CONVERSION TO ROWIDS | | | | | |
| 12 | BITMAP AND | | | | | |
|* 13 | BITMAP INDEX SINGLE VALUE | SALES_PROD_BIX | | | | |
|* 14 | BITMAP INDEX SINGLE VALUE | SALES_CHANNEL_BIX | | | | |
| 15 | TABLE ACCESS BY LOCAL INDEX ROWID| SALES | 3190 | | | |
------------------------------------------------------------------------------------------------------
利用位图联结索引:
SQL> alter table products modify primary key validate;
Table altered.
SQL> alter table customers modify primary key validate;
Table altered.
SQL> alter table channels modify primary key validate;
Table altered.
create bitmap index sales_bji1 on sales(p.prod_name,c.cust_first_name,
ch.channel_desc)
from sales s,products p,customers c,channels ch
where s.prod_id=p.prod_id
and s.cust_id=c.cust_id
6 and s.channel_id=ch.channel_id local;
Index created.
SELECT SUM(s.quantity_sold), SUM(s.amount_sold) FROM sales s,
products p, customers c, channels ch WHERE s.prod_id = p.prod_id
AND s.cust_id = c.cust_id AND s.channel_id = ch.channel_id AND
p.prod_name = 'Y box' AND c.cust_first_name = 'Abigail' AND
ch.channel_desc = 'Direct_sales'
-------------------------------------------------------------------
| Id | Operation | Name | E-Rows |
-------------------------------------------------------------------
| 0 | SELECT STATEMENT | | |
| 1 | SORT AGGREGATE | | 1 |
| 2 | PARTITION RANGE ALL | | 19 |
| 3 | TABLE ACCESS BY LOCAL INDEX ROWID| SALES | 19 |
| 4 | BITMAP CONVERSION TO ROWIDS | | |
|* 5 | BITMAP INDEX SINGLE VALUE | SALES_BJI1 | |
-------------------------------------------------------------------
位图联结索引通过虚拟列的索引物化了结果集,从而避免了成本较高的联结操作,但是位图联结索引有局限性,索引维度都需要定义有经过验证的主键或者唯一键约束,索引必须是局部的。