查詢訪問同一表的兩個以上索引(二)

yangtingkun發表於2010-05-04

經常看到有人提出這樣的疑問,我在同一張表上建立了多個索引,為什麼Oracle每次都選擇一個,而不能同時利用多個索引呢。一般來說,常見的訪問同一張表的兩個以上索引,存在三種情況,AND-EQUALINDEX HASH JOINBITMAP INDEX AND/OR

此外,還有一個設計上的疑問,如果有ABC三個欄位,都可能作為查詢條件,是建立多個複合索引好,還是建立三個單列的索引。這個問題之所以不好回答是因為和業務或者說和查詢的模式有很大的關係,不過如果理解了Oracle什麼時候會選擇一個以上的索引來訪問表,就會對於理解如何設計合理的索引有很大的幫助。

簡單介紹一下BITMAP索引的AND/OR執行計劃。

查詢訪問同一表的兩個以上索引(一):http://yangtingkun.itpub.net/post/468/499769

 

 

首先建立一個測試表:

SQL> DROP TABLE T_DOUBLE_IND PURGE;

Table dropped.

SQL> CREATE TABLE T_DOUBLE_IND
  2  (ID NUMBER,
  3  NAME VARCHAR2(30),
  4  TYPE VARCHAR2(30),
  5  CONTENTS VARCHAR2(4000));

Table created.

SQL> INSERT INTO T_DOUBLE_IND
  2  SELECT ROWNUM,
  3  OBJECT_NAME,
  4  OBJECT_TYPE,
  5  LPAD('A', 1000, 'A')
  6  FROM DBA_OBJECTS;

75856 rows created.

SQL> CREATE INDEX IND_DOUBLE_NAME
  2  ON T_DOUBLE_IND (NAME);

Index created.

SQL> CREATE INDEX IND_DOUBLE_TYPE
  2  ON T_DOUBLE_IND (TYPE);

Index created.

現在建立了一個測試表和兩個索引,下面看看如何使用BITMAP索引的AND操作:

SQL> EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'T_DOUBLE_IND')

PL/SQL procedure successfully completed.

SQL> SET AUTOT ON EXP
SQL> SELECT ID, NAME, TYPE
  2  FROM T_DOUBLE_IND
  3  WHERE NAME = 'T_DOUBLE_IND'
  4  AND TYPE = 'TABLE';

        ID NAME                           TYPE
---------- ------------------------------ ------------------------------
     75737 T_DOUBLE_IND                   TABLE


Execution Plan
----------------------------------------------------------
Plan hash value: 3887138334

--------------------------------------------------------------------------------------------
| Id| Operation                   | Name            | Rows | Bytes | Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT            |                 |    1 |    37 |     3   (0)| 00:00:01 |
|*1 |  TABLE ACCESS BY INDEX ROWID| T_DOUBLE_IND    |    1 |    37 |     3   (0)| 00:00:01 |
|*2 |   INDEX RANGE SCAN          | IND_DOUBLE_NAME |    2 |       |     1   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("TYPE"='TABLE')
   2 - access("NAME"='T_DOUBLE_IND')

SQL> SELECT /*+ INDEX_COMBINE(A IND_DOUBLE_NAME IND_DOUBLE_TYPE) */ ID, NAME, TYPE
  2  FROM T_DOUBLE_IND A
  3  WHERE NAME = 'T_DOUBLE_IND'
  4  AND TYPE = 'TABLE';

        ID NAME                           TYPE
---------- ------------------------------ ------------------------------
     75737 T_DOUBLE_IND                   TABLE


Execution Plan
----------------------------------------------------------
Plan hash value: 2381509496

--------------------------------------------------------------------------------------------
|Id| Operation                        | Name            |Rows|Bytes| Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------
| 0| SELECT STATEMENT                 |                 |   1|   37|     5   (0)| 00:00:01 |
| 1|  TABLE ACCESS BY INDEX ROWID     | T_DOUBLE_IND    |   1|   37|     5   (0)| 00:00:01 |
| 2|   BITMAP CONVERSION TO ROWIDS    |                 |    |     |            |          |
| 3|    BITMAP AND                    |                 |    |     |            |          |
| 4|     BITMAP CONVERSION FROM ROWIDS|                 |    |     |            |          |
|*5|      INDEX RANGE SCAN            | IND_DOUBLE_NAME |    |     |     1   (0)| 00:00:01 |
| 6|     BITMAP CONVERSION FROM ROWIDS|                 |    |     |            |          |
|*7|      INDEX RANGE SCAN            | IND_DOUBLE_TYPE |    |     |     4   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   5 - access("NAME"='T_DOUBLE_IND')
   7 - access("TYPE"='TABLE')

BITMAP索引的AND操作只有在CBO模式下才會啟用,因此收集表的統計資訊。由於根據當前的列的統計情況分析,使用NAME列上的索引代價最小,因此Oracle選擇了IND_DOUBLE_NAME索引掃描。

可以透過INDEX_COMBINE提示來指定索引進行BITMAP AND/OR操作,這種情況下,Oracle將兩個BTREE索引首先轉化為BITMAP索引,然後執行BITMAP ANDBITMAP OR的操作,根據需要決定是否再將BITMAP索引轉化回BTREE索引,然後根據ROWID訪問表得到最終的結果。

並非這種情況下一定需要提示,Oracle會自動根據統計資訊來判斷,是否應該進行BITMAP索引的轉化:

SQL> SET AUTOT OFF
SQL> UPDATE T_DOUBLE_IND
  2  SET NAME = 'T_DOUBLE_IND'
  3  WHERE TYPE = 'SYNONYM';

22600 rows updated.

SQL> COMMIT;

Commit complete.

SQL> EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'T_DOUBLE_IND')

PL/SQL procedure successfully completed.

SQL> SET AUTOT ON EXP
SQL> SELECT ID, NAME, TYPE
  2  FROM T_DOUBLE_IND
  3  WHERE NAME = 'T_DOUBLE_IND'
  4  AND TYPE = 'TABLE';

        ID NAME                           TYPE
---------- ------------------------------ ------------------------------
     75737 T_DOUBLE_IND                   TABLE


Execution Plan
----------------------------------------------------------
Plan hash value: 2581345372

--------------------------------------------------------------------------------------------
|Id| Operation                        | Name            |Rows|Bytes| Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------
| 0| SELECT STATEMENT                 |                 |2597|85701|  1219   (1)| 00:00:18 |
| 1|  TABLE ACCESS BY INDEX ROWID     | T_DOUBLE_IND    |2597|85701|  1219   (1)| 00:00:18 |
| 2|   BITMAP CONVERSION TO ROWIDS    |                 |    |     |            |          |
| 3|    BITMAP AND                    |                 |    |     |            |          |
| 4|     BITMAP CONVERSION FROM ROWIDS|                 |    |     |            |          |
|*5|      INDEX RANGE SCAN            | IND_DOUBLE_TYPE |8456|     |    12   (0)| 00:00:01 |
| 6|     BITMAP CONVERSION FROM ROWIDS|                 |    |     |            |          |
|*7|      INDEX RANGE SCAN            | IND_DOUBLE_NAME |8456|     |    75   (0)| 00:00:02 |
--------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   5 - access("TYPE"='TABLE')
   7 - access("NAME"='T_DOUBLE_IND')

在這個例子中,由於兩個索引的選擇性都很差,而基表本身由於存在一個長度為1000的列,因此全表掃描也是比較低效的,所以Oracle選擇透過兩個索引進行BITMAP AND操作來獲取記錄。

對於當前的查詢而已,這個選擇是很高效的,但是可以明顯的看到,Oracle的執行計劃中,預計返回行數以及COST值,都是十分不準確的。

而對於OR查詢條件的情況,一般來說Oracle會根據統計資訊來判斷是否選擇使用BITMAP OR執行計劃:

SQL> SELECT ID, NAME, TYPE
  2  FROM T_DOUBLE_IND
  3  WHERE NAME = 'T'
  4  OR TYPE = 'CONTEXT';

        ID NAME                           TYPE
---------- ------------------------------ ------------------------------
      9766 LT_CTX                         CONTEXT
     51595 T                              TABLE
     70782 T                              TABLE
     48576 EM_GLOBAL_CONTEXT              CONTEXT
     48577 EM_USER_CONTEXT                CONTEXT
     75651 T                              TABLE
     75700 T                              TABLE
      7047 REGISTRY$CTX                   CONTEXT
     40742 DR$APPCTX                      CONTEXT
     56564 T                              TABLE

10 rows selected.


Execution Plan
----------------------------------------------------------
Plan hash value: 1253459974

--------------------------------------------------------------------------------------------
|Id| Operation                        | Name            |Rows|Bytes| Cost (%CPU)| Time     |
--------------------------------------------------------------------------------------------
| 0| SELECT STATEMENT                 |                 |  21|  714|    12   (0)| 00:00:01 |
| 1|  TABLE ACCESS BY INDEX ROWID     | T_DOUBLE_IND    |  21|  714|    12   (0)| 00:00:01 |
| 2|   BITMAP CONVERSION TO ROWIDS    |                 |    |     |            |          |
| 3|    BITMAP OR                     |                 |    |     |            |          |
| 4|     BITMAP CONVERSION FROM ROWIDS|                 |    |     |            |          |
|*5|      INDEX RANGE SCAN            | IND_DOUBLE_NAME |    |     |     1   (0)| 00:00:01 |
| 6|     BITMAP CONVERSION FROM ROWIDS|                 |    |     |            |          |
|*7|      INDEX RANGE SCAN            | IND_DOUBLE_TYPE |    |     |     1   (0)| 00:00:01 |
--------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   5 - access("NAME"='T')
   7 - access("TYPE"='CONTEXT')

如果指定返回的結果集比較大,則Oracle不會傾向利用索引:

SQL> SET AUTOT TRACE EXP
SQL> SELECT ID, NAME, TYPE
  2  FROM T_DOUBLE_IND
  3  WHERE NAME = 'T_DOUBLE_IND'
  4  OR TYPE = 'INDEX';

Execution Plan
----------------------------------------------------------
Plan hash value: 4134729579

----------------------------------------------------------------------------------
| Id  | Operation         | Name         | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |              | 29745 |   987K|  2010   (1)| 00:00:29 |
|*  1 |  TABLE ACCESS FULL| T_DOUBLE_IND | 29745 |   987K|  2010   (1)| 00:00:29 |
----------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   1 - filter("NAME"='T_DOUBLE_IND' OR "TYPE"='INDEX')

不過BITMAP索引天生時候回答COUNT(*)的問題,如果只是查詢記錄數,則CBO傾向於利用索引,而和索引的選擇性沒有關係:

SQL> SET AUTOT ON EXP
SQL> SELECT COUNT(*)
  2  FROM T_DOUBLE_IND
  3  WHERE NAME = 'T_DOUBLE_IND'
  4  OR TYPE = 'INDEX';

  COUNT(*)
----------
     33058


Execution Plan
----------------------------------------------------------
Plan hash value: 4098198664

--------------------------------------------------------------------------------------------
|Id| Operation                        | Name            | Rows|Bytes | Cost (%CPU)|Time    |
--------------------------------------------------------------------------------------------
| 0| SELECT STATEMENT                 |                 |    1|   29 |    87   (2)|00:00:02|
| 1|  SORT AGGREGATE                  |                 |    1|   29 |            |        |
| 2|   BITMAP CONVERSION COUNT        |                 |29745|  842K|    87   (2)|00:00:02|
| 3|    BITMAP OR                     |                 |     |      |            |        |
| 4|     BITMAP CONVERSION FROM ROWIDS|                 |     |      |            |        |
|*5|      INDEX RANGE SCAN            | IND_DOUBLE_NAME |     |      |    71   (0)|00:00:01|
| 6|     BITMAP CONVERSION FROM ROWIDS|                 |     |      |            |        |
|*7|      INDEX RANGE SCAN            | IND_DOUBLE_TYPE |     |      |    15   (0)|00:00:01|
--------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   5 - access("NAME"='T_DOUBLE_IND')
   7 - access("TYPE"='INDEX')

如果查詢中經常對多列進行限制條件,且大部分情況只需要COUNT(*)查詢,那麼可能在各個列上建立單列索引會更適合。不過這種情況下,一般在資料倉儲系統或報表系統中更加常見,而在這種型別的資料庫中,一般直接就建立BITMAP索引了。

但是在OLTP系統中,BITMAP索引基本上是不可能的選擇,因為這種型別的索引會極大的影響併發性,顯然BTREE索引才是正確的選擇。不過到底是單列索引還是複合索引,仍然沒有一個確切的答案,還是要看具體的情況進行分析。

 

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4227/viewspace-661902/,如需轉載,請註明出處,否則將追究法律責任。

相關文章