标签:
版权声明:本文发布于http://www.cnblogs.com/yumiko/,版权由Yumiko_sunny所有,欢迎转载。转载时,请在文章明显位置注明原文链接。若在未经作者同意的情况下,将本文内容用于商业用途,将保留追究其法律责任的权利。如果有问题,请以邮箱方式联系作者(793113046@qq.com)。
理解oracle索引扫描类型的特点以及具体触发的条件,对于通过合理地使用索引,进行sql优化至关重要(例如组合索引的引导列的选择问题)。
在总结索引扫描类型前,需要再次强调关于索引特点的几个关键点:
其他的一些特点,可参阅前面的几篇总结。
此外,为避免概念的混淆,再次说明一下:“索引类型”主要探讨的是索引的几种类别的问题,而“索引扫描类型”主要探讨的是索引扫描的几种具体实现方法的问题。
Oracle提供了五种索引扫描类型,根据具体索引类型、数据分布、约束条件以及where限制的不同进行选择:
索引唯一扫描,仅仅针对唯一索引的扫描,且仅适用于等值(=)条件的查询。从结果集看,至多返回一条记录。
具体情况分析:
需要注意:
示例:
--创建唯一索引
Yumiko@Sunny >create unique index ind_test_normal on test_normal(empno,ENAME,SAL); Index created.
Yumiko@Sunny >select INDEX_NAME,INDEX_TYPE,TABLE_NAME,UNIQUENESS from user_indexes where TABLE_NAME=‘TEST_NORMAL‘; INDEX_NAME INDEX_TYPE TABLE_NAME UNIQUENES ------------------------- --------------------------- --------------- --------- IND_TEST_NORMAL NORMAL TEST_NORMAL UNIQUE
--查询记录并查看执行计划 Yumiko@Sunny >select * from TEST_NORMAL where empno=7369 and ENAME=‘SMITH‘ and sal=800; EMPNO ENAME JOB SAL ---------- ---------- --------- ---------- 7369 SMITH CLERK 800 Execution Plan ---------------------------------------------------------- Plan hash value: 1399315988 ------------------------------------------------------------------------------------------ | Id | Operation | Name | Rows | Bytes|Cost (%CPU)| Time | ------------------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 1| 39| 1 (0)| 00:00:01| | 1 | TABLE ACCESS BY INDEX ROWID| TEST_NORMAL | 1| 39| 1 (0)| 00:00:01| |* 2 | INDEX UNIQUE SCAN | IND_TEST_NORMAL| 1| | 0 (0)| 00:00:01| ------------------------------------------------------------------------------------------ Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("EMPNO"=7369 AND "ENAME"=‘SMITH‘ AND "SAL"=800) Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 2 consistent gets 16 physical reads 0 redo size 581 bytes sent via SQL*Net to client 458 bytes received via SQL*Net from client 1 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1 rows processed
索引范围扫描,不仅可以针对唯一索引,也可以针对非唯一索引。从结果集看,可以是一条记录,也可以是多条记录。
具体情况分析:
需要注意:
示例:
--创建普通索引
Yumiko@Sunny >create index ind_test_normal on test_normal(empno,ENAME,SAL); Index created.
--验证普通索引创建情况,确认为非唯一索引 Yumiko@Sunny >select INDEX_NAME,INDEX_TYPE,TABLE_NAME,UNIQUENESS from user_indexes where TABLE_NAME=‘TEST_NORMAL‘; INDEX_NAME INDEX_TYPE TABLE_NAME UNIQUENES ------------------------- --------------------------- --------------- --------- IND_TEST_NORMAL NORMAL TEST_NORMAL NONUNIQUE
--查询记录并观察执行计划,此时扫描类型为index range scan Yumiko@Sunny >select * from TEST_NORMAL where empno=7369 and ENAME=‘SMITH‘ and sal=800; EMPNO ENAME JOB SAL ---------- ---------- --------- ---------- 7369 SMITH CLERK 800 Execution Plan ----------------------------------------------- Plan hash value: 67814702 --------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | --------------------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 18 | 2 (0)| 00:00:01| | 1 | TABLE ACCESS BY INDEX ROWID| TEST_NORMAL | 1 | 18 | 2 (0)| 00:00:01| |* 2 | INDEX RANGE SCAN | IND_TEST_NORMAL| 1 | | 1 (0)| 00:00:01| --------------------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("EMPNO"=7369 AND "ENAME"=‘SMITH‘ AND "SAL"=800) Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 3 consistent gets 16 physical reads 0 redo size 717 bytes sent via SQL*Net to client 469 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1 rows processed
该索引扫描方式主要发生在组合索引上,且组合索引的引导列未被指定在检索条件中的情况下发生。
在组合索引中,无论该索引是否为唯一索引。当引导列未被指定在检索条件的情况下,可能会发生“索引跳跃扫描”
以下黄色字体内容摘引自http://book.51cto.com/art/201312/422441.htm
对于组合索引,在无前导列的情况下还能使用索引,是因为Oracle帮我们对该索引的前导列的所有distinct值做了遍历。
所谓的对目标索引的所有distinct值做遍历,其实际含义相当于对原目标SQL做等价改写(即把要用的目标索引的所有前导列的distinct值都加进来)。
例如:create index idx_employee on employee(gender,employee_id),表数据量10000行,其中gender列只有“M”跟“F”值。
当执行select * from employee where employee_id = 100时,相当于执行了等价改写,改写为:
select * from employee where gender = ‘F‘ and employee_id = 100
union all
select * from employee where gender = ‘M‘ and employee_id = 100;
因此,Oracle中的索引跳跃式扫描仅仅适用于那些目标索引前导列的distinct值数量较少、后续非前导列的可选择性又非常好的情形,因为索引跳跃式扫描的执行效率一定会随着目标索引前导列的distinct值数量的递增而递减。否则将执行全表扫描。
示例:
--查询测试表的总数据量
--该表总数据为22928行
Yumiko@Sunny >select count(*) from test; COUNT(*) ---------- 22928
--查询测试表中owner列的列值数据分布情况
--从查询结果看,对于owner列,在22928行的数据中,只分布了3个不同的列值,数据数值的分布情况较为集中 Yumiko@Sunny >select count(distinct owner) from test; COUNT(DISTINCTOWNER) -------------------- 3
--查询测试表中object_id列的数值分布情况
--从查询结果看,对于object_id列,在22928行的数据中,分布了22912个不同的数值,该列整体的数值分布情况较为零散 Yumiko@Sunny >select count(distinct object_id) from test; COUNT(DISTINCTOBJECT_ID) ------------------------ 22912 Yumiko@Sunny >desc test Name Null? Type ---------------------------------------------------------------------- OWNER VARCHAR2(30) OBJECT_NAME VARCHAR2(128) SUBOBJECT_NAME VARCHAR2(30) OBJECT_ID NUMBER DATA_OBJECT_ID NUMBER OBJECT_TYPE VARCHAR2(19) CREATED DATE LAST_DDL_TIME DATE TIMESTAMP VARCHAR2(19) STATUS VARCHAR2(7) TEMPORARY VARCHAR2(1) GENERATED VARCHAR2(1) SECONDARY VARCHAR2(1)
--以数值较为集中的owner作为组合索引的引导列,创建普通索引 Yumiko@Sunny >create index test on test(owner,OBJECT_ID); Index created. Yumiko@Sunny >select INDEX_NAME,INDEX_TYPE,TABLE_NAME,UNIQUENESS from user_indexes where TABLE_NAME=‘TEST‘; INDEX_NAME INDEX_TYPE TABLE_NAME UNIQUENES ------------------------- ------------------- --------------- --------- TEST NORMAL TEST NONUNIQUE --收集测试表最新的统计信息 Yumiko@Sunny >analyze table test compute statistics for table for all columns for all indexes; Table analyzed.
--清空buffer cache缓冲池,保证无测试表的数据块存在在内存中,防止影响到测试结果 Yumiko@Sunny >alter system flush buffer_cache; System altered.
--使用之前创建的组合索引,以组合索引非引导作为条件查询列进行条件查询
--从执行计划看,oracle用到了索引扫描,而且采用index skip scan的方式进行扫描 Yumiko@Sunny >select * from test where object_id=3; Execution Plan ---------------------------------------------------------- Plan hash value: 2389257771 ---------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes| Cost (%CPU)| Time | ---------------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 1 | 86| 5 (0)| 00:00:01| | 1 | TABLE ACCESS BY INDEX ROWID| TEST | 1 | 86| 5 (0)| 00:00:01| |* 2 | INDEX SKIP SCAN | TEST | 1 | | 4 (0)| 00:00:01| ---------------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 2 - access("OBJECT_ID"=3) filter("OBJECT_ID"=3) Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 6 consistent gets 24 physical reads 0 redo size 1402 bytes sent via SQL*Net to client 469 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 1 rows processed
--删除之前的索引 Yumiko@Sunny >drop index test; Index dropped.
--以数值分布较为零散的object_id列作为引导列,再次创建普通索引 Yumiko@Sunny >create index test on test(OBJECT_ID,owner); Index created.
--收集测试表最新的统计信息 Yumiko@Sunny >analyze table test compute statistics for table for all columns for all indexes; Table analyzed.
--清空buffer cache缓冲池,避免影响数据测试 Yumiko@Sunny >alter system flush buffer_cache; System altered.
--使用上面刚刚创建的组合索引的非引导列owner作为条件查询列进行查询操作
--从执行计划看,此次oracle未选择走索引扫描,而是采用了全表扫描的方式
--到此,印证了之前对于index skip scan方式选择的说法 Yumiko@Sunny >select * from test where owner=‘BI‘; 8 rows selected. Execution Plan ---------------------------------------------------------- Plan hash value: 1357081020 ------------------------------------------------------------------------ | Id | Operation | Name | Rows | Bytes| Cost (%CPU)| Time | ------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 8 | 688| 74 (2)| 00:00:01| |* 1 | TABLE ACCESS FULL| TEST | 8 | 688| 74 (2)| 00:00:01| ------------------------------------------------------------------------ Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter("OWNER"=‘BI‘) Statistics ---------------------------------------------------------- 1 recursive calls 0 db block gets 318 consistent gets 315 physical reads 0 redo size 1583 bytes sent via SQL*Net to client 469 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 8 rows processed
对于索引全扫描,就是使用目标索引进行索引扫描时,会扫描所有索引叶块的所有索引行。
对于索引全扫描,只适用于CBO。
对于索引全扫描,使用单块读取的方式,有序读取索引块。
对于索引全扫描,从结果集看,结果全部源于索引块,而且由于已经按照索引键值顺序排序,因此不需要单独排序
对于索引全扫描,会话会产生db file sequential reads事件。
具体情况分析:
示例:
--为测试表TEST_NORMAL的empno列添加非空约束
Yumiko@Sunny >alter table TEST_NORMAL modify(empno not null); Table altered.
--以empno列以及ename列作为组合,创建普通索引 Yumiko@Sunny >create index TEST_NORMAL_ind on TEST_NORMAL(empno,ename); Index created.
--查询上面组合索引涉及的ename列,该列不存在非空约束
--从执行计划看,oracle选择了index full scan的索引扫描方式,且结果集仅仅来源于索引块(没有根据rowid返回数据集的记录,说明无访问数据块) Yumiko@Sunny >select ename from TEST_NORMAL; 14 rows selected. Execution Plan ---------------------------------------------------------- Plan hash value: 2425626010 ------------------------------------------------------------------------------------ | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------------------ | 0 | SELECT STATEMENT | | 14 | 70 | 1 (0)| 00:00:01 | | 1 | INDEX FULL SCAN | TEST_NORMAL_IND | 14 | 70 | 1 (0)| 00:00:01 | ------------------------------------------------------------------------------------ Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 2 consistent gets 8 physical reads 0 redo size 705 bytes sent via SQL*Net to client 469 bytes received via SQL*Net from client 2 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 14 rows processed
对于索引快速扫描,就是使用目标索引进行索引扫描时,会扫描所有索引叶块的所有索引行。
对于索引快速扫描,只适用于CBO。
对于索引快速扫描,使用多块读取的方式,读取索引块。(这种方式相较于索引全扫描,获取数据的效率更高)
对于索引快速扫描,从结果集看,结果全部源于索引块,但数据结果不一定有序。
对于索引快速扫描,会话会产生db file scattered reads事件。
具体情况分析:
参阅上面的索引全扫描,两者所谓的同宗,只是不同的需求(对于结果的响应速度或是数据序列,当然CBO优化器也会进行内部的计算评估选取最优执行路径),产生的不同的执行结果,下面的示例会展示
示例:
--查询测试表的数据量,显示此表有22928行数据
Yumiko@Sunny >select count(*) from test; COUNT(*) ---------- 22928
--确认数据列中不含非空约束 Yumiko@Sunny >desc test Name Null? Type -------------------------------------------------------------------------------- OWNER VARCHAR2(30) OBJECT_NAME VARCHAR2(128) SUBOBJECT_NAME VARCHAR2(30) OBJECT_ID NUMBER DATA_OBJECT_ID NUMBER OBJECT_TYPE VARCHAR2(19) CREATED DATE LAST_DDL_TIME DATE TIMESTAMP VARCHAR2(19) STATUS VARCHAR2(7) TEMPORARY VARCHAR2(1) GENERATED VARCHAR2(1) SECONDARY VARCHAR2(1)
--针对object_id列,创建普通索引 Yumiko@Sunny >create index test on test(object_id); Index created.
--确认上面创建的索引为非唯一索引 Yumiko@Sunny >select INDEX_NAME,INDEX_TYPE,TABLE_NAME,UNIQUENESS from user_indexes where TABLE_NAME=‘TEST‘; INDEX_NAME INDEX_TYPE TABLE_NAME UNIQUENES ------------------------------------------------------------------------- TEST NORMAL TEST NONUNIQUE
--清空buffer_cache缓冲池数据,防止造成测试的影响 Yumiko@Sunny >alter system flush buffer_cache; System altered.
--清空shared pool缓冲池数据,防止对测试造成影响 Yumiko@Sunny >alter system flush shared_pool; System altered.
--收集测试表最新的统计信息 Yumiko@Sunny >analyze table test compute statistics for table for all columns for all indexes; Table analyzed.
--打开会话追踪,仅查看执行计划 Yumiko@Sunny >set autotrace trace --查询索引列object,同时使用is not null作为查询条件。
--由于b-tree索引中不记录null值信息,而且该列不存在非空约束,通过not null条件指定,让oracle不必考虑null值。
--从执行计划看,由于读取的数据量很大且不用排序,oracle选择了更快的多块读方式的index fast full scan,尽快获取数据。
--同样的,此时未出现access by index rowid的情况,说明未查询数据块,仅仅查询了索引块。 Yumiko@Sunny >select object_id from test where object_id is not null; 22928 rows selected. Execution Plan ---------------------------------------------------------- Plan hash value: 1645531115 ----------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ----------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 23290 | 295K| 14 (0)| 00:00:01 | |* 1 | INDEX FAST FULL SCAN| TEST | 23290 | 295K| 14 (0)| 00:00:01 | ----------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter("OBJECT_ID" IS NOT NULL) Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 1582 consistent gets 53 physical reads 0 redo size 502642 bytes sent via SQL*Net to client 17277 bytes received via SQL*Net from client 1530 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 22928 rows processed --修改上面的查询,增加排序操作。
--从执行计划看,由于此次增加了排序操作,oracle选择了偏向有序读取的index full scan的方式进行扫描。
--同样的,此次未查询数据块(不存在access by index rowid)。 Yumiko@Sunny >select object_id from test where object_id is not null order by object_id; 22928 rows selected. Execution Plan ---------------------------------------------------------- Plan hash value: 3883652822 ------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | ------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 23290 | 295K| 60 (2)| 00:00:01 | |* 1 | INDEX FULL SCAN | TEST | 23290 | 295K| 60 (2)| 00:00:01 | ------------------------------------------------------------------------- Predicate Information (identified by operation id): --------------------------------------------------- 1 - filter("OBJECT_ID" IS NOT NULL) Statistics ---------------------------------------------------------- 0 recursive calls 0 db block gets 1578 consistent gets 64 physical reads 0 redo size 502642 bytes sent via SQL*Net to client 17277 bytes received via SQL*Net from client 1530 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 22928 rows processed --再次修改查询,取消not null条件查询以及排序操作。
--此时,对于没有非空约束的object_id列,增加了null值的可能性,而b-tree索引不保存null信息。
--此次,oracle选择了全表扫描的方式。 Yumiko@Sunny >select object_id from test; 22928 rows selected. Execution Plan ---------------------------------------------------------- Plan hash value: 1357081020 -------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time | -------------------------------------------------------------------------- | 0 | SELECT STATEMENT | | 22928 | 91712 | 74 (2)| 00:00:01 | | 1 | TABLE ACCESS FULL| TEST | 22928 | 91712 | 74 (2)| 00:00:01 | -------------------------------------------------------------------------- Statistics ---------------------------------------------------------- 1 recursive calls 0 db block gets 1830 consistent gets 315 physical reads 0 redo size 415626 bytes sent via SQL*Net to client 17277 bytes received via SQL*Net from client 1530 SQL*Net roundtrips to/from client 0 sorts (memory) 0 sorts (disk) 22928 rows processed
由于篇幅有限,关于索引扫描类型的总结到此为止。若存在内容上的遗漏,或者错误,欢迎各位指教。
Oracle索引梳理系列(八)- 索引扫描类型及分析(高效索引必备知识)
标签:
原文地址:http://www.cnblogs.com/yumiko/p/5972246.html