Oracle索引梳理系列(八)- 索引掃描類型及分析(高效索引必備知識)

版權聲明:本文發佈於http://www.cnblogs.com/yumiko/,版權由Yumiko_sunny全部,歡迎轉載。轉載時,請在文章明顯位置註明原文連接。若在未經做者贊成的狀況下,將本文內容用於商業用途,將保留追究其法律責任的權利。若是有問題,請以郵箱方式聯繫做者(793113046@qq.com)。sql


 

理解oracle索引掃描類型的特色以及具體觸發的條件,對於經過合理地使用索引,進行sql優化相當重要(例如組合索引的引導列的選擇問題)oracle

在總結索引掃描類型前,須要再次強調關於索引特色的幾個關鍵點:ide

  • 對於單一列創建的索引,既單一列索引,b-tree中不保存索引列的null值信息
  • 對於多個列創建的索引,既組合列索引,b-tree中會連同其餘非null值列,保留該列null值記錄;對於一條記錄中,組合索引所有列都是null值,組合索引中不會記錄(從以前的實驗看,此時的執行計劃是全表掃描)
  • 建立主鍵約束以及惟一鍵約束,或自動建立惟一索引
  • create index建立的索引屬於普通索引(非惟一索引)
  • create unique index建立的索引屬於惟一索引

其餘的一些特色,可參閱前面的幾篇總結。測試

此外,爲避免概念的混淆,再次說明一下:「索引類型」主要探討的是索引的幾種類別的問題,而「索引掃描類型」主要探討的是索引掃描的幾種具體實現方法的問題。字體

 

 

一、索引掃描類型概述

Oracle提供了五種索引掃描類型,根據具體索引類型、數據分佈、約束條件以及where限制的不一樣進行選擇: 優化

  • 索引惟一掃描(index unique scan)
  • 索引範圍掃描(index range scan)
  • 索引跳躍掃描(index skip scan)
  • 索引全掃描(index full scan)
  • 索引快速掃描(index fast full scan)

 

 

二、索引惟一掃描(index unique scan) 

索引惟一掃描,僅僅針對惟一索引的掃描,且僅適用於等值(=)條件的查詢。從結果集看,至多返回一條記錄。spa

 

具體狀況分析:code

  • 對於單一列創建的索引(單一索引),當索引屬於惟一索引,在檢索條件中,使用該索引進行檢索,且檢索值不是null時,會使用「索引惟一掃描」
  • 對於單一列創建的索引(單一索引),當索引屬於惟一索引,在檢索條件中,使用該索引進行檢索,且檢索值等於null時,會使用「全表掃描」
  • 對於多個列創建的索引(組合索引),當索引屬於惟一索引,且檢索條件中,使用該組合索引進行檢索,且檢索列使用組合索引涉及的全部列時,會使用「索引惟一掃描」

 

須要注意:orm

  • 對於組合索引,若其中涉及的部分列或則全部列,在同一條記錄上存在null值。當使用完整的組合索引列做爲檢索條件,且使用該null值進行該條記錄的檢索時,不會使用「索引惟一掃描」

 

示例:htm

--建立惟一索引
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

 

 

 

三、索引範圍掃描(index range scan)

索引範圍掃描,不只能夠針對惟一索引,也能夠針對非惟一索引。從結果集看,能夠是一條記錄,也能夠是多條記錄。

 

具體狀況分析:

  • 對於單一列創建的索引(單一索引),當索引屬於惟一索引,在檢索條件中,使用該索引進行檢索,且使用範圍的操做符(>,<,>=,<=,between),會使用「索引範圍掃描」。
  • 對於單一列創建的索引(單一索引),當索引屬於非惟一索引,在檢索條件中,使用該索引進行檢索,且檢索值不是null值,會使用「索引範圍掃描」。
  • 對於多個列創建的索引(組合索引),當索引屬於惟一索引,在檢索條件中,使用該索引進行檢索,且檢索列使用組合索引涉及的部分列,但必須存在組合索引的引導列(建立組合索引時指定的第一列)時,會使用「索引範圍掃描」。
  • 對於多個列創建的索引(組合索引),當索引屬於非惟一索引,在檢索條件中,使用該索引進行檢索,檢索列涉及組合索引的部分列或者所有列,但必須存在組合索引的引導列(建立組合索引時指定的第一列)時,會使用「索引範圍掃描」。

 

須要注意:

  • 對於組合索引,當一條記錄的引導列存在null值,當檢索條件中,針對該條記錄,僅使用該引導列進行檢索時,不會使用「索引範圍掃描」。
  • 對於組合索引,當一條記錄的引導列存在null值,當檢索條件中,針對該條記錄,在使用該引導列進行檢索的同時,使用組合索引的其餘列且這些列針對該條記錄的列值不是null值,一併進行檢索,會使用「索引範圍掃描」。

 

示例:

--建立普通索引
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

 

 

四、索引跳躍掃描(index skip scan)

該索引掃描方式主要發生在組合索引上,且組合索引的引導列未被指定在檢索條件中的狀況下發生。
在組合索引中,不管該索引是否爲惟一索引。當引導列未被指定在檢索條件的狀況下,可能會發生「索引跳躍掃描」

 

如下黃色字體內容摘引自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

 

 

五、索引全掃描(index full scan)

對於索引全掃描,就是使用目標索引進行索引掃描時,會掃描全部索引葉塊的全部索引行。

對於索引全掃描,只適用於CBO。

對於索引全掃描,使用單塊讀取的方式,有序讀取索引塊。

對於索引全掃描,從結果集看,結果所有源於索引塊,並且因爲已經按照索引鍵值順序排序,所以不須要單獨排序

對於索引全掃描,會話會產生db file sequential reads事件。

 

具體狀況分析:

  • 對於單一列創建的索引(單一索引),當該索引列有非空約束時,在具體檢索中只檢索該列所有數據,會使用「索引全掃描」。
  • 對於單一列創建的索引(單一索引),當該索引列無非空約束時,在具體檢索中只檢索該列所有數據,且是對該列的統計(count)或者非空條件查詢(is not null),會使用「索引全掃描」。
  • 對於單一列創建的索引(單一索引),當該索引列無非空約束時,在具體檢索中只檢索該列所有數據,且是對該列的常規查詢,不會使用「索引全掃描」。(這是由於對於oracle索引,對於列中存在的null值不記錄在b-tree索引中)
  • 對於多個列創建的索引(組合索引),當該索引列有非空約束時,在具體檢索中只檢索組合索引中涉及的所有列或者部分列的所有數據,會使用「索引全掃描」。
  • 對於多個列創建的索引(組合索引),當該索引列無非空約束時,在具體檢索中只檢索組合索引中涉及的所有列或者部分列的所有數據,且是對這些相關列的統計(count)或者非空條件查詢(is not null),會使用「索引全掃描」。
  • 對於多個列創建的索引(組合索引),當該索引列無非空約束時,在具體檢索中只檢索組合索引中涉及的所有列或者部分列的所有數據,且是對該列的常規查詢,不會使用「索引全掃描」。

 

示例:

--爲測試表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

 

 

六、索引快速掃描(index fast full scan)

對於索引快速掃描,就是使用目標索引進行索引掃描時,會掃描全部索引葉塊的全部索引行。

對於索引快速掃描,只適用於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

 因爲篇幅有限,關於索引掃描類型的總結到此爲止。若存在內容上的遺漏,或者錯誤,歡迎各位指教。

相關文章
相關標籤/搜索