Oracle - 怎樣使用B樹索引和位圖索引

http://blog.csdn.net/tswisdom/article/details/7396826

 

注:low-cardinality是指該列或者列的組合具有的不同值的個數較少,即該列有很多重複值。high-cardinality是指該列或者列的組合具有不同的值的個數較多,即該列有很少的重複值。

理解每種索引的適用場合將對性能產生重大影響。

傳統觀念認爲位圖索引最適用於擁有很少不同值的列 ---- 例如GENDER, MARITAL_STATUS,和RELATION。但是,這種假設是不準確的。實際上,對於大多非頻繁更新的併發系統,位圖索引也是適用的。事實上,下面將會展示,對以一個具有100%唯一值的列(主鍵的候選列)來說,位圖索引和B樹索引一樣有效。

本文將提供一些例子以及優化建議,它們對於low-cardinality和high-cardinality上的兩種索引都是通用的。這些例子將幫助DBA理解位圖索引不是依賴於cardinality而是依賴於程序自身。

索引比較

在唯一列上使用位圖索引有一些缺點 --- 其中一個是需要足夠的空間(Oracle不推薦使用)。但是,位圖索引的大小不但與位圖索引列的cardinality有關,還與數據的分佈有關。因此,GENDER列上的位圖索引比B樹索引要小,相反,EMPNO上的位圖索引比B樹索引大的多。但是,相對於OLTP系統來說,決策支持系統只有很少的用戶訪問,因而對於這些系統,資源不是問題。

爲了闡明這個觀點,我創建了兩個表,test_normal和test_random。用PL/SQL塊在test_normal中插入100萬條記錄,然後在test_random表中隨機插入相同的記錄。

  1. Create table test_normal (empno number(10), ename varchar2(30), sal number(10));
  2. Begin
  3. For i in 1..1000000
  4. Loop
  5. Insert into test_normal
  6. values(i, dbms_random.string('U',30), dbms_random.value(1000,7000));
  7. If mod(i, 10000) = 0 then
  8. Commit;
  9. End if;
  10. End loop;
  11. End;
  12. /
  13. Create table test_random
  14. as
  15. select /*+ append */ * from test_normal order by dbms_random.random;
  16. SQL> select count(*) "Total Rows" from test_normal;
  17. Total Rows
  18. ----------
  19. 1000000
  20. Elapsed: 00:00:01.09
  21. SQL> select count(distinct empno) "Distinct Values" from test_normal;
  22. Distinct Values
  23. ---------------
  24. 1000000
  25. Elapsed: 00:00:06.09
  26. SQL> select count(*) "Total Rows" from test_random;
  27. Total Rows
  28. ----------
  29. 1000000
  30. Elapsed: 00:00:03.05
  31. SQL> select count(distinct empno) "Distinct Values" from test_random;
  32. Distinct Values
  33. ---------------
  34. 1000000
  35. Elapsed: 00:00:12.07
  1. Create table test_normal (empno number(10), ename varchar2(30), sal number(10));  
  2.   
  3. Begin  
  4. For i in 1..1000000  
  5. Loop  
  6.    Insert into test_normal   
  7.    values(i, dbms_random.string('U',30), dbms_random.value(1000,7000));  
  8.    If mod(i, 10000) = 0 then  
  9.    Commit;  
  10.   End if;  
  11. End loop;  
  12. End;  
  13. /  
  14.     
  15. Create table test_random   
  16. as   
  17. select /*+ append */ * from test_normal order by dbms_random.random;  
  18.   
  19. SQL> select count(*) "Total Rows" from test_normal;  
  20.   
  21. Total Rows  
  22. ----------  
  23.    1000000  
  24.   
  25. Elapsed: 00:00:01.09  
  26.   
  27. SQL> select count(distinct empno) "Distinct Values" from test_normal;  
  28.   
  29. Distinct Values  
  30. ---------------  
  31.         1000000  
  32.   
  33. Elapsed: 00:00:06.09  
  34. SQL> select count(*) "Total Rows" from test_random;  
  35.   
  36. Total Rows  
  37. ----------  
  38.    1000000  
  39.   
  40. Elapsed: 00:00:03.05  
  41. SQL> select count(distinct empno) "Distinct Values" from test_random;  
  42.   
  43. Distinct Values  
  44. ---------------  
  45.         1000000  
  46.   
  47. Elapsed: 00:00:12.07  

注意,test_normal表是組織良好的,test_random表是隨機創建的,因此,其中的數據是無組織的。在上面的表中,EMPNO列上的值完全不同,因此可以作爲候選主鍵。如果你把該列定義爲主鍵,oracle將會建立一個B樹索引,因爲Oracle不支持主鍵位圖索引。

爲了分析這些索引的行爲,我們執行下面的步驟:

  1. 在表test_normal上:
    1. 在EMPNO列上建立一個位圖索引,並執行一些相等性查詢。
    2. 在EMPNO列上建立一個B樹索引,執行一些相等性查詢,並且比較獲得不同結果集所執行的查詢需要的物理I/O和邏輯I/O的次數。
  2. 在表test_random表上:
    1. 和1.1相同的步驟
    2. 和1.2相同的步驟
  3. 在表test_normal上:
    1. 和1.1相同的步驟,但是執行範圍查詢。
    2. 和1.2相同的步驟,但是執行範圍查詢。比較統計結果。
  4. 在表test_random表上:
    1. 和3.1相同的步驟。
    2. 和3.2相同的步驟
  5. 在表test_normal上:
    1. 在SAL列上建立一個位圖索引,並且執行一些相等性查詢和範圍查詢。
    2. 在SAL列上建立一個B樹索引,並且執行一些相等性查詢和範圍查詢(和5.1相同的結果集),比較獲取結果執行的I/O次數。
  6. 在兩個表中添加GENDER列,並且把該列更新爲3個可能的值:M(女性), F(男性), null(未知)。根據一些條件更新該列的值。
  7. 在該列上建立一個位圖索引並且執行一些相等性查詢。
  8. 在GENDER列上建立一個B樹索引並且執行一些相等性查詢,和步驟7的結果比較。

步驟1到4涉及一個high-cardinality列(完全不同),步驟5是一個normal-cardinality列,步驟7和8是一個low-cardinality列。

步驟1.1(在表test_normal上)

在該步中,我們在表test_normal上建立一個位圖索引,然後檢查索引的大小、聚簇因子(clustering factor)和表的大小。然後執行一些相等性查詢並且查看使用位圖索引時查詢需要的I/O次數。

  1. SQL> create bitmap index normal_empno_bmx on test_normal(empno);
  2. Index created.
  3. Elapsed: 00:00:29.06
  4. SQL> analyze table test_normal compute statistics for table for all indexes for all indexed columns;
  5. Table analyzed.
  6. Elapsed: 00:00:19.01
  7. SQL> select substr(segment_name,1,30) segment_name, bytes/1024/1024 "Size in MB"
  8. 2 from user_segments
  9. 3* where segment_name in ('TEST_NORMAL','NORMAL_EMPNO_BMX');
  10. SEGMENT_NAME Size in MB
  11. ------------------------------------ ---------------
  12. TEST_NORMAL 50
  13. NORMAL_EMPNO_BMX 28
  14. Elapsed: 00:00:02.00
  15. SQL> select index_name, clustering_factor from user_indexes;
  16. INDEX_NAME CLUSTERING_FACTOR
  17. ------------------------------ ---------------------------------
  18. NORMAL_EMPNO_BMX 1000000
  19. Elapsed: 00:00:00.00
  1. SQL> create bitmap index normal_empno_bmx on test_normal(empno);  
  2.   
  3. Index created.  
  4.   
  5. Elapsed: 00:00:29.06  
  6. SQL> analyze table test_normal compute statistics for table for all indexes for all indexed columns;  
  7.   
  8.   
  9. Table analyzed.  
  10.   
  11. Elapsed: 00:00:19.01  
  12. SQL> select substr(segment_name,1,30) segment_name, bytes/1024/1024 "Size in MB"  
  13.   2  from user_segments  
  14.   3* where segment_name in ('TEST_NORMAL','NORMAL_EMPNO_BMX');  
  15.    
  16. SEGMENT_NAME                               Size in MB  
  17. ------------------------------------       ---------------  
  18. TEST_NORMAL                                50  
  19. NORMAL_EMPNO_BMX                           28  
  20.   
  21. Elapsed: 00:00:02.00  
  22. SQL> select index_name, clustering_factor from user_indexes;  
  23.   
  24. INDEX_NAME                             CLUSTERING_FACTOR  
  25. ------------------------------         ---------------------------------  
  26. NORMAL_EMPNO_BMX                       1000000  
  27.   
  28. Elapsed: 00:00:00.00  

可以看到,表上索引的大小是28M並且聚簇因子的大小等於表中的行數。現在我們爲不同的結果集執行一些相等性查詢:

  1. SQL> set autotrace only
  2. SQL> select * from test_normal where empno=&empno;
  3. Enter value for empno: 1000
  4. old 1: select * from test_normal where empno=&empno
  5. new 1: select * from test_normal where empno=1000
  6. Elapsed: 00:00:00.01
  7. Execution Plan
  8. ----------------------------------------------------------
  9. 0 SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=1 Bytes=34)
  10. 1 0 TABLE ACCESS (BY INDEX ROWID) OF 'TEST_NORMAL' (Cost=4 Car
  11. d=1 Bytes=34)
  12. 2 1 BITMAP CONVERSION (TO ROWIDS)
  13. 3 2 BITMAP INDEX (SINGLE VALUE) OF 'NORMAL_EMPNO_BMX'
  14. Statistics
  15. ----------------------------------------------------------
  16. 0 recursive calls
  17. 0 db block gets
  18. 5 consistent gets
  19. 0 physical reads
  20. 0 redo size
  21. 515 bytes sent via SQL*Net to client
  22. 499 bytes received via SQL*Net from client
  23. 2 SQL*Net roundtrips to/from client
  24. 0 sorts (memory)
  25. 0 sorts (disk)
  26. 1 rows processed
  1. SQL> set autotrace only  
  2. SQL> select * from test_normal where empno=&empno;  
  3. Enter value for empno: 1000  
  4. old   1: select * from test_normal where empno=&empno  
  5. new   1: select * from test_normal where empno=1000  
  6.   
  7. Elapsed: 00:00:00.01  
  8.   
  9. Execution Plan  
  10. ----------------------------------------------------------  
  11.    0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=1 Bytes=34)  
  12.    1    0   TABLE ACCESS (BY INDEX ROWID) OF 'TEST_NORMAL' (Cost=4 Car  
  13.           d=1 Bytes=34)  
  14.    2    1     BITMAP CONVERSION (TO ROWIDS)  
  15.    3    2       BITMAP INDEX (SINGLE VALUE) OF 'NORMAL_EMPNO_BMX'  
  16.   
  17. Statistics  
  18. ----------------------------------------------------------  
  19.           0  recursive calls  
  20.           0  db block gets  
  21.           5  consistent gets  
  22.           0  physical reads  
  23.           0  redo size  
  24.         515  bytes sent via SQL*Net to client  
  25.         499  bytes received via SQL*Net from client  
  26.           2  SQL*Net roundtrips to/from client  
  27.           0  sorts (memory)  
  28.           0  sorts (disk)  
  29.           1  rows processed  

步驟1.2(在表test_normal上)

現在刪除表中EMPNO列上的位圖索引並創建一個B樹索引。像前面一樣我們查看索引的大小、聚簇因子的大小並且執行相同的查詢,比較I/O的次數。

  1. SQL> drop index NORMAL_EMPNO_BMX;
  2. Index dropped.
  3. SQL> create index normal_empno_idx on test_normal(empno);
  4. Index created.
  5. SQL> analyze table test_normal compute statistics for table for all indexes for all indexed columns;
  6. Table analyzed.
  7. SQL> select substr(segment_name,1,30) segment_name, bytes/1024/1024 "Size in MB"
  8. 2 from user_segments
  9. 3 where segment_name in ('TEST_NORMAL','NORMAL_EMPNO_IDX');
  10. SEGMENT_NAME Size in MB
  11. ---------------------------------- ---------------
  12. TEST_NORMAL 50
  13. NORMAL_EMPNO_IDX 18
  14. SQL> select index_name, clustering_factor from user_indexes;
  15. INDEX_NAME CLUSTERING_FACTOR
  16. ---------------------------------- ----------------------------------
  17. NORMAL_EMPNO_IDX 6210
  1. SQL> drop index NORMAL_EMPNO_BMX;  
  2.   
  3. Index dropped.  
  4.   
  5. SQL> create index normal_empno_idx on test_normal(empno);  
  6.   
  7. Index created.  
  8.   
  9. SQL> analyze table test_normal compute statistics for table for all indexes for all indexed columns;  
  10.   
  11. Table analyzed.  
  12.   
  13. SQL> select substr(segment_name,1,30) segment_name, bytes/1024/1024 "Size in MB"  
  14.   2  from user_segments  
  15.   3  where segment_name in ('TEST_NORMAL','NORMAL_EMPNO_IDX');  
  16.   
  17. SEGMENT_NAME                               Size in MB  
  18. ----------------------------------         ---------------  
  19. TEST_NORMAL                                50  
  20. NORMAL_EMPNO_IDX                           18  
  21.   
  22. SQL> select index_name, clustering_factor from user_indexes;  
  23.   
  24. INDEX_NAME                            CLUSTERING_FACTOR  
  25. ----------------------------------    ----------------------------------  
  26. NORMAL_EMPNO_IDX                      6210  

很明顯,在該表的EMPNO列上,B樹索引比位圖索引要小。B樹索引上的聚簇因子接近於表中的數據塊數;因此B樹索引對於範圍查詢更有效。

現在,我們使用B樹索引執行相同的查詢。

  1. SQL> set autot trace
  2. SQL> select * from test_normal where empno=&empno;
  3. Enter value for empno: 1000
  4. old 1: select * from test_normal where empno=&empno
  5. new 1: select * from test_normal where empno=1000
  6. Elapsed: 00:00:00.01
  7. Execution Plan
  8. ----------------------------------------------------------
  9. 0 SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=1 Bytes=34)
  10. 1 0 TABLE ACCESS (BY INDEX ROWID) OF 'TEST_NORMAL' (Cost=4 Car
  11. d=1 Bytes=34)
  12. 2 1 INDEX (RANGE SCAN) OF 'NORMAL_EMPNO_IDX' (NON-UNIQUE) (C
  13. ost=3 Card=1)
  14. Statistics
  15. ----------------------------------------------------------
  16. 29 recursive calls
  17. 0 db block gets
  18. 5 consistent gets
  19. 0 physical reads
  20. 0 redo size
  21. 515 bytes sent via SQL*Net to client
  22. 499 bytes received via SQL*Net from client
  23. 2 SQL*Net roundtrips to/from client
  24. 0 sorts (memory)
  25. 0 sorts (disk)
  26. 1 rows processed
  1. SQL> set autot trace  
  2. SQL> select * from test_normal where empno=&empno;  
  3. Enter value for empno: 1000  
  4. old   1: select * from test_normal where empno=&empno  
  5. new   1: select * from test_normal where empno=1000  
  6.   
  7. Elapsed: 00:00:00.01  
  8.   
  9. Execution Plan  
  10. ----------------------------------------------------------  
  11.    0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=1 Bytes=34)  
  12.    1    0   TABLE ACCESS (BY INDEX ROWID) OF 'TEST_NORMAL' (Cost=4 Car  
  13.           d=1 Bytes=34)  
  14.    2    1     INDEX (RANGE SCAN) OF 'NORMAL_EMPNO_IDX' (NON-UNIQUE) (C  
  15.           ost=3 Card=1)  
  16.   
  17. Statistics  
  18. ----------------------------------------------------------  
  19.          29  recursive calls  
  20.           0  db block gets  
  21.           5  consistent gets  
  22.           0  physical reads  
  23.           0  redo size  
  24.         515  bytes sent via SQL*Net to client  
  25.         499  bytes received via SQL*Net from client  
  26.           2  SQL*Net roundtrips to/from client  
  27.           0  sorts (memory)  
  28.           0  sorts (disk)  
  29.           1  rows processed  

可以看到,對於相同的結果集,在唯一列上的位圖索引和B樹索引需要相同的物理和邏輯讀取次數。

BITMAP(位圖) EMPNO B-TREE(B樹)
Consistent Reads Physical Reads Consistent Reads Physical Reads
5 0 1000 5 0
5 2 2398 5 2
5 2 8545 5 2
5 2 98008 5 2
5 2 85342 5 2
5 2 128444 5 2
5 2 858 5 2

步驟2.1(在表test_random上)

現在,在test_random表上執行相同的操作:

  1. SQL> create bitmap index random_empno_bmx on test_random(empno);
  2. Index created.
  3. SQL> analyze table test_random compute statistics for table for all indexes for all indexed columns;
  4. Table analyzed.
  5. SQL> select substr(segment_name,1,30) segment_name, bytes/1024/1024 "Size in MB"
  6. 2 from user_segments
  7. 3* where segment_name in ('TEST_RANDOM','RANDOM_EMPNO_BMX');
  8. SEGMENT_NAME Size in MB
  9. ------------------------------------ ---------------
  10. TEST_RANDOM 50
  11. RANDOM_EMPNO_BMX 28
  12. SQL> select index_name, clustering_factor from user_indexes;
  13. INDEX_NAME CLUSTERING_FACTOR
  14. ------------------------------ ---------------------------------
  15. RANDOM_EMPNO_BMX 1000000
  1. SQL> create bitmap index random_empno_bmx on test_random(empno);  
  2.   
  3. Index created.  
  4.   
  5. SQL> analyze table test_random compute statistics for table for all indexes for all indexed columns;  
  6.   
  7. Table analyzed.  
  8.   
  9. SQL> select substr(segment_name,1,30) segment_name, bytes/1024/1024 "Size in MB"  
  10.   2  from user_segments  
  11.   3* where segment_name in ('TEST_RANDOM','RANDOM_EMPNO_BMX');  
  12.    
  13. SEGMENT_NAME                               Size in MB  
  14. ------------------------------------       ---------------  
  15. TEST_RANDOM                                50  
  16. RANDOM_EMPNO_BMX                           28  
  17.   
  18. SQL> select index_name, clustering_factor from user_indexes;  
  19.   
  20. INDEX_NAME                             CLUSTERING_FACTOR  
  21. ------------------------------         ---------------------------------  
  22. RANDOM_EMPNO_BMX                       1000000  

再次,索引上的統計結果(大小和聚簇因子)和在表test_normal中是相同的:

  1. SQL> select * from test_random where empno=&empno;
  2. Enter value for empno: 1000
  3. old 1: select * from test_random where empno=&empno
  4. new 1: select * from test_random where empno=1000
  5. Elapsed: 00:00:00.01
  6. Execution Plan
  7. ----------------------------------------------------------
  8. 0 SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=1 Bytes=34)
  9. 1 0 TABLE ACCESS (BY INDEX ROWID) OF 'TEST_RANDOM' (Cost=4 Card=1 Bytes=34)
  10. 2 1 BITMAP CONVERSION (TO ROWIDS)
  11. 3 2 BITMAP INDEX (SINGLE VALUE) OF 'RANDOM_EMPNO_BMX'
  12. Statistics
  13. ----------------------------------------------------------
  14. 0 recursive calls
  15. 0 db block gets
  16. 5 consistent gets
  17. 0 physical reads
  18. 0 redo size
  19. 515 bytes sent via SQL*Net to client
  20. 499 bytes received via SQL*Net from client
  21. 2 SQL*Net roundtrips to/from client
  22. 0 sorts (memory)
  23. 0 sorts (disk)
  24. 1 rows processed
  1. SQL> select * from test_random where empno=&empno;  
  2. Enter value for empno: 1000  
  3. old   1: select * from test_random where empno=&empno  
  4. new   1: select * from test_random where empno=1000  
  5.   
  6. Elapsed: 00:00:00.01  
  7.   
  8. Execution Plan  
  9. ----------------------------------------------------------  
  10.    0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=1 Bytes=34)  
  11.    1    0   TABLE ACCESS (BY INDEX ROWID) OF 'TEST_RANDOM' (Cost=4 Card=1 Bytes=34)  
  12.    2    1     BITMAP CONVERSION (TO ROWIDS)  
  13.    3    2       BITMAP INDEX (SINGLE VALUE) OF 'RANDOM_EMPNO_BMX'  
  14.   
  15. Statistics  
  16. ----------------------------------------------------------  
  17.           0  recursive calls  
  18.           0  db block gets  
  19.           5  consistent gets  
  20.           0  physical reads  
  21.           0  redo size  
  22.         515  bytes sent via SQL*Net to client  
  23.         499  bytes received via SQL*Net from client  
  24.           2  SQL*Net roundtrips to/from client  
  25.           0  sorts (memory)  
  26.           0  sorts (disk)  
  27.           1  rows processed  

步驟2.2(在表test_random上)

現在,和步驟1.2一樣,我們刪除EMPNO列上的位圖索引並且創建一個B樹索引。

  1. SQL> drop index RANDOM_EMPNO_BMX;
  2. Index dropped.
  3. SQL> create index random_empno_idx on test_random(empno);
  4. Index created.
  5. SQL> analyze table test_random compute statistics for table for all indexes for all indexed columns;
  6. Table analyzed.
  7. SQL> select substr(segment_name,1,30) segment_name, bytes/1024/1024 "Size in MB"
  8. 2 from user_segments
  9. 3 where segment_name in ('TEST_RANDOM','RANDOM_EMPNO_IDX');
  10. SEGMENT_NAME Size in MB
  11. ---------------------------------- ---------------
  12. TEST_RANDOM 50
  13. RANDOM_EMPNO_IDX 18
  14. SQL> select index_name, clustering_factor from user_indexes;
  15. INDEX_NAME CLUSTERING_FACTOR
  16. ---------------------------------- ----------------------------------
  17. RANDOM_EMPNO_IDX 999830
  1. SQL> drop index RANDOM_EMPNO_BMX;  
  2.   
  3. Index dropped.  
  4.   
  5. SQL> create index random_empno_idx on test_random(empno);  
  6.   
  7. Index created.  
  8.   
  9. SQL> analyze table test_random compute statistics for table for all indexes for all indexed columns;  
  10.   
  11. Table analyzed.  
  12.   
  13. SQL> select substr(segment_name,1,30) segment_name, bytes/1024/1024 "Size in MB"  
  14.   2  from user_segments  
  15.   3  where segment_name in ('TEST_RANDOM','RANDOM_EMPNO_IDX');  
  16.   
  17. SEGMENT_NAME                               Size in MB  
  18. ----------------------------------         ---------------  
  19. TEST_RANDOM                                50  
  20. RANDOM_EMPNO_IDX                           18  
  21.   
  22. SQL> select index_name, clustering_factor from user_indexes;  
  23.   
  24. INDEX_NAME                            CLUSTERING_FACTOR  
  25. ----------------------------------    ----------------------------------  
  26. RANDOM_EMPNO_IDX                      999830  

該表的索引大小和表test_normal是一樣的,但是聚簇因子更接近於行數,這就使得該索引對於範圍查詢不再高效。該聚簇因子不影響相等性查詢,因爲該列的值是唯一的,每個鍵對應1行記錄。

現在,在相同的結果集上執行相等性查詢。

  1. SQL> select * from test_random where empno=&empno;
  2. Enter value for empno: 1000
  3. old 1: select * from test_random where empno=&empno
  4. new 1: select * from test_random where empno=1000
  5. Elapsed: 00:00:00.01
  6. Execution Plan
  7. ----------------------------------------------------------
  8. 0 SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=1 Bytes=34)
  9. 1 0 TABLE ACCESS (BY INDEX ROWID) OF 'TEST_RANDOM' (Cost=4 Card=1 Bytes=34)
  10. 2 1 INDEX (RANGE SCAN) OF 'RANDOM_EMPNO_IDX' (NON-UNIQUE) (Cost=3 Card=1)
  11. Statistics
  12. ----------------------------------------------------------
  13. 0 recursive calls
  14. 0 db block gets
  15. 5 consistent gets
  16. 0 physical reads
  17. 0 redo size
  18. 515 bytes sent via SQL*Net to client
  19. 499 bytes received via SQL*Net from client
  20. 2 SQL*Net roundtrips to/from client
  21. 0 sorts (memory)
  22. 0 sorts (disk)
  23. 1 rows processed
  1. SQL> select * from test_random where empno=&empno;  
  2. Enter value for empno: 1000  
  3. old   1: select * from test_random where empno=&empno  
  4. new   1: select * from test_random where empno=1000  
  5.   
  6. Elapsed: 00:00:00.01  
  7.   
  8. Execution Plan  
  9. ----------------------------------------------------------  
  10.    0      SELECT STATEMENT Optimizer=CHOOSE (Cost=4 Card=1 Bytes=34)  
  11.    1    0   TABLE ACCESS (BY INDEX ROWID) OF 'TEST_RANDOM' (Cost=4 Card=1 Bytes=34)  
  12.    2    1     INDEX (RANGE SCAN) OF 'RANDOM_EMPNO_IDX' (NON-UNIQUE) (Cost=3 Card=1)  
  13.   
  14. Statistics  
  15. ----------------------------------------------------------  
  16.           0  recursive calls  
  17.           0  db block gets  
  18.           5  consistent gets  
  19.           0  physical reads  
  20.           0  redo size  
  21.         515  bytes sent via SQL*Net to client  
  22.         499  bytes received via SQL*Net from client  
  23.           2  SQL*Net roundtrips to/from client  
  24.           0  sorts (memory)  
  25.           0  sorts (disk)  
  26.           1  rows processed  

再次表明,結果和步驟1.1和1.2幾乎相同。對於唯一列來說,數據分佈不影響邏輯和物理I/O。

步驟3.1(在表test_normal上)

在該步中,我們將創建一個位圖索引。我們知道索引的聚簇因子大小和表中的行數相同。現在我們執行一些範圍查詢。

  1. SQL> select * from test_normal where empno between &range1 and &range2;
  2. Enter value for range1: 1
  3. Enter value for range2: 2300
  4. old 1: select * from test_normal where empno between &range1 and &range2
  5. new 1: select * from test_normal where empno between 1 and 2300
  6. 2300 rows selected.
  7. Elapsed: 00:00:00.03
  8. Execution Plan
  9. ----------------------------------------------------------
  10. 0 SELECT STATEMENT Optimizer=CHOOSE (Cost=451 Card=2299 Bytes=78166)
  11. 1 0 TABLE ACCESS (BY INDEX ROWID) OF 'TEST_NORMAL' (Cost=451 Card=2299 Bytes=78166)
  12. 2 1 BITMAP CONVERSION (TO ROWIDS)
  13. 3 2 BITMAP INDEX (RANGE SCAN) OF 'NORMAL_EMPNO_BMX'
  14. Statistics
  15. ----------------------------------------------------------
  16. 0 recursive calls
  17. 0 db block gets
  18. 331 consistent gets
  19. 0 physical reads
  20. 0 redo size
  21. 111416 bytes sent via SQL*Net to client
  22. 2182 bytes received via SQL*Net from client
  23. 155 SQL*Net roundtrips to/from client
  24. 0 sorts (memory)
  25. 0 sorts (disk)
  26. 2300 rows processed
  1. SQL> select * from test_normal where empno between &range1 and &range2;  
  2. Enter value for range1: 1  
  3. Enter value for range2: 2300  
  4. old   1: select * from test_normal where empno between &range1 and &range2  
  5. new   1: select * from test_normal where empno between 1 and 2300  
  6.   
  7. 2300 rows selected.  
  8.   
  9. Elapsed: 00:00:00.03  
  10.   
  11. Execution Plan  
  12. ----------------------------------------------------------  
  13.    0      SELECT STATEMENT Optimizer=CHOOSE (Cost=451 Card=2299 Bytes=78166)  
  14.    1    0   TABLE ACCESS (BY INDEX ROWID) OF 'TEST_NORMAL' (Cost=451 Card=2299 Bytes=78166)  
  15.    2    1     BITMAP CONVERSION (TO ROWIDS)  
  16.    3    2       BITMAP INDEX (RANGE SCAN) OF 'NORMAL_EMPNO_BMX'  
  17.   
  18. Statistics  
  19. ----------------------------------------------------------  
  20.           0  recursive calls  
  21.           0  db block gets  
  22.         331  consistent gets  
  23.           0  physical reads  
  24.           0  redo size  
  25.      111416  bytes sent via SQL*Net to client  
  26.        2182  bytes received via SQL*Net from client  
  27.         155  SQL*Net roundtrips to/from client  
  28.           0  sorts (memory)  
  29.           0  sorts (disk)  
  30.        2300  rows processed  

步驟3.2(在表test_normal上)

該步中,我們在test_normal的B樹索引上執行查詢。

  1. SQL> select * from test_normal where empno between &range1 and &range2;
  2. Enter value for range1: 1
  3. Enter value for range2: 2300
  4. old 1: select * from test_normal where empno between &range1 and &range2
  5. new 1: select * from test_normal where empno between 1 and 2300
  6. 2300 rows selected.
  7. Elapsed: 00:00:00.02
  8. Execution Plan
  9. ----------------------------------------------------------
  10. 0 SELECT STATEMENT Optimizer=CHOOSE (Cost=23 Card=2299 Bytes=78166)
  11. 1 0 TABLE ACCESS (BY INDEX ROWID) OF 'TEST_NORMAL' (Cost=23 Card=2299 Bytes=78166)
  12. 2 1 INDEX (RANGE SCAN) OF 'NORMAL_EMPNO_IDX' (NON-UNIQUE) (Cost=8 Card=2299)
  13. Statistics
  14. ----------------------------------------------------------
  15. 0 recursive calls
  16. 0 db block gets
  17. 329 consistent gets
  18. 15 physical reads
  19. 0 redo size
  20. 111416 bytes sent via SQL*Net to client
  21. 2182 bytes received via SQL*Net from client
  22. 155 SQL*Net roundtrips to/from client
  23. 0 sorts (memory)
  24. 0 sorts (disk)
  25. 2300 rows processed
  1. SQL> select * from test_normal where empno between &range1 and &range2;  
  2. Enter value for range1: 1  
  3. Enter value for range2: 2300  
  4. old   1: select * from test_normal where empno between &range1 and &range2  
  5. new   1: select * from test_normal where empno between 1 and 2300  
  6.   
  7. 2300 rows selected.  
  8.   
  9. Elapsed: 00:00:00.02  
  10.   
  11. Execution Plan  
  12. ----------------------------------------------------------  
  13.    0      SELECT STATEMENT Optimizer=CHOOSE (Cost=23 Card=2299 Bytes=78166)  
  14.    1    0   TABLE ACCESS (BY INDEX ROWID) OF 'TEST_NORMAL' (Cost=23 Card=2299 Bytes=78166)  
  15.    2    1     INDEX (RANGE SCAN) OF 'NORMAL_EMPNO_IDX' (NON-UNIQUE) (Cost=8 Card=2299)  
  16.   
  17. Statistics  
  18. ----------------------------------------------------------  
  19.           0  recursive calls  
  20.           0  db block gets  
  21.         329  consistent gets  
  22.          15  physical reads  
  23.           0  redo size  
  24.      111416  bytes sent via SQL*Net to client  
  25.        2182  bytes received via SQL*Net from client  
  26.         155  SQL*Net roundtrips to/from client  
  27.           0  sorts (memory)  
  28.           0  sorts (disk)  
  29.        2300  rows processed  

在不同的範圍上執行的查詢結果如下:

BITMAP EMPNO (Range) B-TREE
Consistent Reads Physical Reads Consistent Reads Physical Reads
331 0 1-2300 329 0
285 0 8-1980 283 0
346 19 1850-4250 344 16
427 31 28888-31850 424 28
371 27 82900-85478 367 23
2157 149 984888-1000000 2139 35

可以看到,在兩種索引上需要的邏輯和物理IO基本上是相同的。最後一個範圍(984888-1000000)差不多返回了15,000行,是所有範圍查詢中最大的。當我們執行全表掃描時(通過/*+ full(test_normal) */ ),物理和邏輯IO的次數是7239和5663.

步驟4.1(在表test_random上)

在該步中,我們將在表test_random的位圖索引上執行範圍查詢,在這兒,你將看到聚簇因子的影響。

  1. SQL>select * from test_random where empno between &range1 and &range2;
  2. Enter value for range1: 1
  3. Enter value for range2: 2300
  4. old 1: select * from test_random where empno between &range1 and &range2
  5. new 1: select * from test_random where empno between 1 and 2300
  6. 2300 rows selected.
  7. Elapsed: 00:00:08.01
  8. Execution Plan
  9. ----------------------------------------------------------
  10. 0 SELECT STATEMENT Optimizer=CHOOSE (Cost=453 Card=2299 Bytes=78166)
  11. 1 0 TABLE ACCESS (BY INDEX ROWID) OF 'TEST_RANDOM' (Cost=453 Card=2299 Bytes=78166)
  12. 2 1 BITMAP CONVERSION (TO ROWIDS)
  13. 3 2 BITMAP INDEX (RANGE SCAN) OF 'RANDOM_EMPNO_BMX'
  14. Statistics
  15. ----------------------------------------------------------
  16. 0 recursive calls
  17. 0 db block gets
  18. 2463 consistent gets
  19. 1200 physical reads
  20. 0 redo size
  21. 111416 bytes sent via SQL*Net to client
  22. 2182 bytes received via SQL*Net from client
  23. 155 SQL*Net roundtrips to/from client
  24. 0 sorts (memory)
  25. 0 sorts (disk)
  26. 2300 rows processed
  1. SQL>select * from test_random where empno between &range1 and &range2;  
  2. Enter value for range1: 1  
  3. Enter value for range2: 2300  
  4. old   1: select * from test_random where empno between &range1 and &range2  
  5. new   1: select * from test_random where empno between 1 and 2300  
  6.   
  7. 2300 rows selected.  
  8.   
  9. Elapsed: 00:00:08.01  
  10.   
  11. Execution Plan  
  12. ----------------------------------------------------------  
  13.    0      SELECT STATEMENT Optimizer=CHOOSE (Cost=453 Card=2299 Bytes=78166)  
  14.    1    0   TABLE ACCESS (BY INDEX ROWID) OF 'TEST_RANDOM' (Cost=453 Card=2299 Bytes=78166)  
  15.    2    1     BITMAP CONVERSION (TO ROWIDS)  
  16.    3    2       BITMAP INDEX (RANGE SCAN) OF 'RANDOM_EMPNO_BMX'  
  17.   
  18. Statistics  
  19. ----------------------------------------------------------  
  20.           0  recursive calls  
  21.           0  db block gets  
  22.        2463  consistent gets  
  23.        1200  physical reads  
  24.           0  redo size  
  25.      111416  bytes sent via SQL*Net to client  
  26.        2182  bytes received via SQL*Net from client  
  27.         155  SQL*Net roundtrips to/from client  
  28.           0  sorts (memory)  
  29.           0  sorts (disk)  
  30.        2300  rows processed  

步驟4.2(在表test_random上)

在該步中,我們將在test_random的B樹索引上執行範圍查詢。回想一下,該索引上的聚簇因子接近於表中記錄的行數。下面是優化器的輸出:

  1. SQL> select * from test_random where empno between &range1 and &range2;
  2. Enter value for range1: 1
  3. Enter value for range2: 2300
  4. old 1: select * from test_random where empno between &range1 and &range2
  5. new 1: select * from test_random where empno between 1 and 2300
  6. 2300 rows selected.
  7. Elapsed: 00:00:03.04
  8. Execution Plan
  9. ----------------------------------------------------------
  10. 0 SELECT STATEMENT Optimizer=CHOOSE (Cost=613 Card=2299 Bytes=78166)
  11. 1 0 TABLE ACCESS (FULL) OF 'TEST_RANDOM' (Cost=613 Card=2299 Bytes=78166)
  12. Statistics
  13. ----------------------------------------------------------
  14. 0 recursive calls
  15. 0 db block gets
  16. 6415 consistent gets
  17. 4910 physical reads
  18. 0 redo size
  19. 111416 bytes sent via SQL*Net to client
  20. 2182 bytes received via SQL*Net from client
  21. 155 SQL*Net roundtrips to/from client
  22. 0 sorts (memory)
  23. 0 sorts (disk)
  24. 2300 rows processed
  1. SQL> select * from test_random where empno between &range1 and &range2;  
  2. Enter value for range1: 1  
  3. Enter value for range2: 2300  
  4. old   1: select * from test_random where empno between &range1 and &range2  
  5. new   1: select * from test_random where empno between 1 and 2300  
  6.   
  7. 2300 rows selected.  
  8.   
  9. Elapsed: 00:00:03.04  
  10.   
  11. Execution Plan  
  12. ----------------------------------------------------------  
  13.    0      SELECT STATEMENT Optimizer=CHOOSE (Cost=613 Card=2299 Bytes=78166)  
  14.    1    0   TABLE ACCESS (FULL) OF 'TEST_RANDOM' (Cost=613 Card=2299 Bytes=78166)  
  15.   
  16. Statistics  
  17. ----------------------------------------------------------  
  18.           0  recursive calls  
  19.           0  db block gets  
  20.        6415  consistent gets  
  21.        4910  physical reads  
  22.           0  redo size  
  23.      111416  bytes sent via SQL*Net to client  
  24.        2182  bytes received via SQL*Net from client  
  25.         155  SQL*Net roundtrips to/from client  
  26.           0  sorts (memory)  
  27.           0  sorts (disk)  
  28.        2300  rows processed  

因爲聚簇因子的緣故,優化器選擇了全表掃描而不是使用索引:

BITMAP EMPNO (Range) B-TREE
Consistent Reads Physical Reads Consistent Reads Physical Reads
2463 1200 1-2300 6415 4910
2114 31 8-1980 6389 4910
2572 1135 1850-4250 6418 4909
3173 1620 28888-31850 6456 4909
2762 1358 82900-85478 6431 4909
7254 3329 984888-1000000 7254 4909

僅對於最後一個範圍(984888-1000000),對於位圖索引優化器選擇了全表掃描。然而,對於B樹索引,全部使用全表掃描。引起這種差異的原因是聚簇因子:優化器在產生執行計劃時不考慮位圖索引的聚簇因子,但是對於B樹索引來說,則需要 考慮聚簇因子。在上面的情況中,位圖索引比B樹索引更有效。

下面的步驟揭示了這些索引更有趣的方面。

步驟5.1(在表test_normal上)

在表test_normal的SAL列上建立一個位圖索引,該列擁有普通的cardinality。

  1. SQL> create bitmap index normal_sal_bmx on test_normal(sal);
  2. Index created.
  3. SQL> analyze table test_normal compute statistics for table for all indexes for all indexed columns;
  4. Table analyzed.
  1. SQL> create bitmap index normal_sal_bmx on test_normal(sal);  
  2.   
  3. Index created.  
  4.   
  5. SQL> analyze table test_normal compute statistics for table for all indexes for all indexed columns;  
  6.   
  7. Table analyzed.  

得到索引的大小和聚簇因子:

  1. SQL>select substr(segment_name,1,30) segment_name, bytes/1024/1024 "Size in MB"
  2. 2* from user_segments
  3. 3* where segment_name in ('TEST_NORMAL','NORMAL_SAL_BMX');
  4. SEGMENT_NAME Size in MB
  5. ------------------------------ --------------
  6. TEST_NORMAL 50
  7. NORMAL_SAL_BMX 4
  8. SQL> select index_name, clustering_factor from user_indexes;
  9. INDEX_NAME CLUSTERING_FACTOR
  10. ------------------------------ ----------------------------------
  11. NORMAL_SAL_BMX 6001
  1. SQL>select substr(segment_name,1,30) segment_name, bytes/1024/1024 "Size in MB"  
  2.   2* from user_segments  
  3.   3* where segment_name in ('TEST_NORMAL','NORMAL_SAL_BMX');  
  4.   
  5. SEGMENT_NAME                                Size in MB  
  6. ------------------------------              --------------  
  7. TEST_NORMAL                                 50  
  8. NORMAL_SAL_BMX                              4  
  9.   
  10. SQL> select index_name, clustering_factor from user_indexes;  
  11.   
  12. INDEX_NAME                             CLUSTERING_FACTOR  
  13. ------------------------------         ----------------------------------  
  14. NORMAL_SAL_BMX                         6001  

下面執行查詢,首先執行相等性查詢:

  1. SQL> set autot trace
  2. SQL> select * from test_normal where sal=&sal;
  3. Enter value for sal: 1869
  4. old 1: select * from test_normal where sal=&sal
  5. new 1: select * from test_normal where sal=1869
  6. 164 rows selected.
  7. Elapsed: 00:00:00.08
  8. Execution Plan
  9. ----------------------------------------------------------
  10. 0 SELECT STATEMENT Optimizer=CHOOSE (Cost=39 Card=168 Bytes=4032)
  11. 1 0 TABLE ACCESS (BY INDEX ROWID) OF 'TEST_NORMAL' (Cost=39 Card=168 Bytes=4032)
  12. 2 1 BITMAP CONVERSION (TO ROWIDS)
  13. 3 2 BITMAP INDEX (SINGLE VALUE) OF 'NORMAL_SAL_BMX'
  14. Statistics
  15. ----------------------------------------------------------
  16. 0 recursive calls
  17. 0 db block gets
  18. 165 consistent gets
  19. 0 physical reads
  20. 0 redo size
  21. 8461 bytes sent via SQL*Net to client
  22. 609 bytes received via SQL*Net from client
  23. 12 SQL*Net roundtrips to/from client
  24. 0 sorts (memory)
  25. 0 sorts (disk)
  26. 164 rows processed
  1. SQL> set autot trace  
  2. SQL> select * from test_normal where sal=&sal;  
  3. Enter value for sal: 1869  
  4. old   1: select * from test_normal where sal=&sal  
  5. new   1: select * from test_normal where sal=1869  
  6.   
  7. 164 rows selected.  
  8.   
  9. Elapsed: 00:00:00.08  
  10.   
  11. Execution Plan  
  12. ----------------------------------------------------------  
  13.    0      SELECT STATEMENT Optimizer=CHOOSE (Cost=39 Card=168 Bytes=4032)  
  14.    1    0   TABLE ACCESS (BY INDEX ROWID) OF 'TEST_NORMAL' (Cost=39 Card=168 Bytes=4032)  
  15.    2    1     BITMAP CONVERSION (TO ROWIDS)  
  16.    3    2       BITMAP INDEX (SINGLE VALUE) OF 'NORMAL_SAL_BMX'  
  17.   
  18. Statistics  
  19. ----------------------------------------------------------  
  20.           0  recursive calls  
  21.           0  db block gets  
  22.         165  consistent gets  
  23.           0  physical reads  
  24.           0  redo size  
  25.        8461  bytes sent via SQL*Net to client  
  26.         609  bytes received via SQL*Net from client  
  27.          12  SQL*Net roundtrips to/from client  
  28.           0  sorts (memory)  
  29.           0  sorts (disk)  
  30.         164  rows processed  

接下來是範圍查詢:

  1. SQL> select * from test_normal where sal between &sal1 and &sal2;
  2. Enter value for sal1: 1500
  3. Enter value for sal2: 2000
  4. old 1: select * from test_normal where sal between &sal1 and &sal2
  5. new 1: select * from test_normal where sal between 1500 and 2000
  6. 83743 rows selected.
  7. Elapsed: 00:00:05.00
  8. Execution Plan
  9. ----------------------------------------------------------
  10. 0 SELECT STATEMENT Optimizer=CHOOSE (Cost=601 Card=83376 Bytes
  11. =2001024)
  12. 1 0 TABLE ACCESS (FULL) OF 'TEST_NORMAL' (Cost=601 Card=83376
  13. Bytes=2001024)
  14. Statistics
  15. ----------------------------------------------------------
  16. 0 recursive calls
  17. 0 db block gets
  18. 11778 consistent gets
  19. 5850 physical reads
  20. 0 redo size
  21. 4123553 bytes sent via SQL*Net to client
  22. 61901 bytes received via SQL*Net from client
  23. 5584 SQL*Net roundtrips to/from client
  24. 0 sorts (memory)
  25. 0 sorts (disk)
  26. 83743 rows processed
  1. SQL> select * from test_normal where sal between &sal1 and &sal2;  
  2. Enter value for sal1: 1500  
  3. Enter value for sal2: 2000  
  4. old   1: select * from test_normal where sal between &sal1 and &sal2  
  5. new   1: select * from test_normal where sal between 1500 and 2000  
  6.   
  7. 83743 rows selected.  
  8.   
  9. Elapsed: 00:00:05.00  
  10.   
  11. Execution Plan  
  12. ----------------------------------------------------------  
  13.    0      SELECT STATEMENT Optimizer=CHOOSE (Cost=601 Card=83376 Bytes  
  14.           =2001024)  
  15.    1    0   TABLE ACCESS (FULL) OF 'TEST_NORMAL' (Cost=601 Card=83376  
  16.           Bytes=2001024)  
  17.   
  18. Statistics  
  19. ----------------------------------------------------------  
  20.           0  recursive calls  
  21.           0  db block gets  
  22.       11778  consistent gets  
  23.        5850  physical reads  
  24.           0  redo size  
  25.     4123553  bytes sent via SQL*Net to client  
  26.       61901  bytes received via SQL*Net from client  
  27.        5584  SQL*Net roundtrips to/from client  
  28.           0  sorts (memory)  
  29.           0  sorts (disk)  
  30.       83743  rows processed  

現在,刪除test_normal上的位圖索引並且建立一個B樹索引。

  1. SQL> create index normal_sal_idx on test_normal(sal);
  2. Index created.
  3. SQL> analyze table test_normal compute statistics for table for all indexes for all indexed columns;
  4. Table analyzed.
  1. SQL> create index normal_sal_idx on test_normal(sal);  
  2.   
  3. Index created.  
  4.   
  5. SQL> analyze table test_normal compute statistics for table for all indexes for all indexed columns;  
  6.   
  7. Table analyzed.  

查看索引大小和聚簇因子:

  1. SQL> select substr(segment_name,1,30) segment_name, bytes/1024/1024 "Size in MB"
  2. 2 from user_segments
  3. 3 where segment_name in ('TEST_NORMAL','NORMAL_SAL_IDX');
  4. SEGMENT_NAME Size in MB
  5. ------------------------------ ---------------
  6. TEST_NORMAL 50
  7. NORMAL_SAL_IDX 17
  8. SQL> select index_name, clustering_factor from user_indexes;
  9. INDEX_NAME CLUSTERING_FACTOR
  10. ------------------------------ ----------------------------------
  11. NORMAL_SAL_IDX 986778
  1. SQL> select substr(segment_name,1,30) segment_name, bytes/1024/1024 "Size in MB"  
  2.   2  from user_segments  
  3.   3  where segment_name in ('TEST_NORMAL','NORMAL_SAL_IDX');  
  4.   
  5. SEGMENT_NAME                         Size in MB  
  6. ------------------------------       ---------------  
  7. TEST_NORMAL                          50  
  8. NORMAL_SAL_IDX                       17  
  9.   
  10. SQL> select index_name, clustering_factor from user_indexes;  
  11.   
  12. INDEX_NAME                           CLUSTERING_FACTOR  
  13. ------------------------------       ----------------------------------  
  14. NORMAL_SAL_IDX                       986778  

從上表可以看出,B樹索引大於相同列上的位圖索引,它的聚簇因子接近於表中的行數。

現在,先執行相等性查詢:

  1. SQL> set autot trace
  2. SQL> select * from test_normal where sal=&sal;
  3. Enter value for sal: 1869
  4. old 1: select * from test_normal where sal=&sal
  5. new 1: select * from test_normal where sal=1869
  6. 164 rows selected.
  7. Elapsed: 00:00:00.01
  8. Execution Plan
  9. ----------------------------------------------------------
  10. 0 SELECT STATEMENT Optimizer=CHOOSE (Cost=169 Card=168 Bytes=4032)
  11. 1 0 TABLE ACCESS (BY INDEX ROWID) OF 'TEST_NORMAL' (Cost=169 Card=168 Bytes=4032)
  12. 2 1 INDEX (RANGE SCAN) OF 'NORMAL_SAL_IDX' (NON-UNIQUE) (Cost=3 Card=168)
  13. Statistics
  14. ----------------------------------------------------------
  15. 0 recursive calls
  16. 0 db block gets
  17. 177 consistent gets
  18. 0 physical reads
  19. 0 redo size
  20. 8461 bytes sent via SQL*Net to client
  21. 609 bytes received via SQL*Net from client
  22. 12 SQL*Net roundtrips to/from client
  23. 0 sorts (memory)
  24. 0 sorts (disk)
  25. 164 rows processed
  1. SQL> set autot trace  
  2. SQL> select * from test_normal where sal=&sal;  
  3. Enter value for sal: 1869  
  4. old   1: select * from test_normal where sal=&sal  
  5. new   1: select * from test_normal where sal=1869  
  6.   
  7. 164 rows selected.  
  8.   
  9. Elapsed: 00:00:00.01  
  10.   
  11. Execution Plan  
  12. ----------------------------------------------------------  
  13.    0      SELECT STATEMENT Optimizer=CHOOSE (Cost=169 Card=168 Bytes=4032)  
  14.    1    0   TABLE ACCESS (BY INDEX ROWID) OF 'TEST_NORMAL' (Cost=169 Card=168 Bytes=4032)  
  15.    2    1     INDEX (RANGE SCAN) OF 'NORMAL_SAL_IDX' (NON-UNIQUE) (Cost=3 Card=168)  
  16.   
  17. Statistics  
  18. ----------------------------------------------------------  
  19.           0  recursive calls  
  20.           0  db block gets  
  21.         177  consistent gets  
  22.           0  physical reads  
  23.           0  redo size  
  24.        8461  bytes sent via SQL*Net to client  
  25.         609  bytes received via SQL*Net from client  
  26.          12  SQL*Net roundtrips to/from client  
  27.           0  sorts (memory)  
  28.           0  sorts (disk)  
  29.         164  rows processed  

接下來是範圍查詢:

  1. SQL> select * from test_normal where sal between &sal1 and &sal2;
  2. Enter value for sal1: 1500
  3. Enter value for sal2: 2000
  4. old 1: select * from test_normal where sal between &sal1 and &sal2
  5. new 1: select * from test_normal where sal between 1500 and 2000
  6. 83743 rows selected.
  7. Elapsed: 00:00:04.03
  8. Execution Plan
  9. ----------------------------------------------------------
  10. 0 SELECT STATEMENT Optimizer=CHOOSE (Cost=601 Card=83376 Bytes
  11. =2001024)
  12. 1 0 TABLE ACCESS (FULL) OF 'TEST_NORMAL' (Cost=601 Card=83376
  13. Bytes=2001024)
  14. Statistics
  15. ----------------------------------------------------------
  16. 0 recursive calls
  17. 0 db block gets
  18. 11778 consistent gets
  19. 3891 physical reads
  20. 0 redo size
  21. 4123553 bytes sent via SQL*Net to client
  22. 61901 bytes received via SQL*Net from client
  23. 5584 SQL*Net roundtrips to/from client
  24. 0 sorts (memory)
  25. 0 sorts (disk)
  26. 83743 rows processed
  1. SQL> select * from test_normal where sal between &sal1 and &sal2;  
  2. Enter value for sal1: 1500  
  3. Enter value for sal2: 2000  
  4. old   1: select * from test_normal where sal between &sal1 and &sal2  
  5. new   1: select * from test_normal where sal between 1500 and 2000  
  6.   
  7. 83743 rows selected.  
  8.   
  9. Elapsed: 00:00:04.03  
  10.   
  11. Execution Plan  
  12. ----------------------------------------------------------  
  13.    0      SELECT STATEMENT Optimizer=CHOOSE (Cost=601 Card=83376 Bytes  
  14.           =2001024)  
  15.    1    0   TABLE ACCESS (FULL) OF 'TEST_NORMAL' (Cost=601 Card=83376  
  16.           Bytes=2001024)  
  17.   
  18.   
  19. Statistics  
  20. ----------------------------------------------------------  
  21.           0  recursive calls  
  22.           0  db block gets  
  23.       11778  consistent gets  
  24.        3891  physical reads  
  25.           0  redo size  
  26.     4123553  bytes sent via SQL*Net to client  
  27.       61901  bytes received via SQL*Net from client  
  28.        5584  SQL*Net roundtrips to/from client  
  29.           0  sorts (memory)  
  30.           0  sorts (disk)  
  31.       83743  rows processed  

在不同的數據集上執行查詢的結果如下,可以看出邏輯和物理I/O的次數基本上是相同的。

BITMAP
SAL (Equality)
B-TREE Rows Fetched
Consistent Reads Physical Reads Consistent Reads Physical Reads
165 0 1869 177 164  
169 163 3548 181 167  
174 166 6500 187 172  
75 69 7000 81 73  
177 163 2500 190 175  

BITMAP
SAL (Range)
B-TREE Rows Fetched
Consistent Reads Physical Reads Consistent Reads Physical Reads
11778 5850 1500-2000 11778 3891 83743
11765 5468 2000-2500 11765 3879 83328
11753 5471 2500-3000 11753 3884 83318
17309 5472 3000-4000 17309 3892 166999
39398 5454 4000-7000 39398 3973 500520

對於範圍查詢,優化器選擇了全表掃描,根本沒有使用索引。但是對於相等性查詢,優化器使用了索引。再次,邏輯和物理I/O是相同的。

因此,可以得出結論,對於一個具有normal-cardinality的列來說,優化器對於兩種類型的索引的選擇是相同的,並且沒有明顯的I/O差異。

步驟6(增加GENDER列)

在測試low-cardinality列之前,我們先增加一個GENDER列並且把它的值更新成M,F或者null。

  1. SQL> alter table test_normal add GENDER varchar2(1);
  2. Table altered.
  3. SQL> select GENDER, count(*) from test_normal group by GENDER;
  4. S COUNT(*)
  5. - ----------
  6. F 333769
  7. M 499921
  8. 166310
  9. 3 rows selected.
  1. SQL> alter table test_normal add GENDER varchar2(1);  
  2.   
  3. Table altered.  
  4.   
  5. SQL> select GENDER, count(*) from test_normal group by GENDER;  
  6.   
  7. S     COUNT(*)  
  8. -     ----------  
  9. F     333769  
  10. M     499921  
  11.       166310  
  12.   
  13. 3 rows selected.  

該列上位圖索引的大小大約爲570KB,如下表所示:

  1. SQL> create bitmap index normal_GENDER_bmx on test_normal(GENDER);
  2. Index created.
  3. Elapsed: 00:00:02.08
  4. SQL> select substr(segment_name,1,30) segment_name, bytes/1024/1024 "Size in MB"
  5. 2 from user_segments
  6. 3 where segment_name in ('TEST_NORMAL','NORMAL_GENDER_BMX');
  7. SEGMENT_NAME Size in MB
  8. ------------------------------ ---------------
  9. TEST_NORMAL 50
  10. NORMAL_GENDER_BMX .5625
  11. 2 rows selected.
  1. SQL> create bitmap index normal_GENDER_bmx on test_normal(GENDER);  
  2.   
  3. Index created.  
  4.   
  5. Elapsed: 00:00:02.08  
  6.   
  7. SQL> select substr(segment_name,1,30) segment_name, bytes/1024/1024 "Size in MB"  
  8.   2  from user_segments  
  9.   3  where segment_name in ('TEST_NORMAL','NORMAL_GENDER_BMX');  
  10.   
  11. SEGMENT_NAME                        Size in MB  
  12. ------------------------------      ---------------  
  13. TEST_NORMAL                         50  
  14. NORMAL_GENDER_BMX                   .5625  
  15.   
  16. 2 rows selected.  

相對而言,改列上的B樹索引的大小爲13M,比位圖索引大的多。

  1. SQL> create index normal_GENDER_idx on test_normal(GENDER);
  2. Index created.
  3. SQL> select substr(segment_name,1,30) segment_name, bytes/1024/1024 "Size in MB"
  4. 2 from user_segments
  5. 3 where segment_name in ('TEST_NORMAL','NORMAL_GENDER_IDX');
  6. SEGMENT_NAME Size in MB
  7. ------------------------------ ---------------
  8. TEST_NORMAL 50
  9. NORMAL_GENDER_IDX 13
  10. 2 rows selected.
  1. SQL> create index normal_GENDER_idx on test_normal(GENDER);  
  2.   
  3. Index created.  
  4.   
  5. SQL> select substr(segment_name,1,30) segment_name, bytes/1024/1024 "Size in MB"  
  6.   2  from user_segments  
  7.   3  where segment_name in ('TEST_NORMAL','NORMAL_GENDER_IDX');  
  8.   
  9. SEGMENT_NAME                       Size in MB  
  10. ------------------------------     ---------------  
  11. TEST_NORMAL                        50  
  12. NORMAL_GENDER_IDX                  13  
  13.   
  14. 2 rows selected.  

現在,執行相等性查詢,優化器將不使用該索引,不論是位圖索引還是B樹索引,它將使用全部掃描。

  1. SQL> select * from test_normal where GENDER is null;
  2. 166310 rows selected.
  3. Elapsed: 00:00:06.08
  4. Execution Plan
  5. ----------------------------------------------------------
  6. 0 SELECT STATEMENT Optimizer=CHOOSE (Cost=601 Card=166310 Bytes=4157750)
  7. 1 0 TABLE ACCESS (FULL) OF 'TEST_NORMAL' (Cost=601 Card=166310 Bytes=4157750)
  8. SQL> select * from test_normal where GENDER='M';
  9. 499921 rows selected.
  10. Elapsed: 00:00:16.07
  11. Execution Plan
  12. ----------------------------------------------------------
  13. 0 SELECT STATEMENT Optimizer=CHOOSE (Cost=601 Card=499921 Bytes=12498025)
  14. 1 0 TABLE ACCESS (FULL) OF 'TEST_NORMAL' (Cost=601 Card=499921Bytes=12498025)
  15. SQL>select * from test_normal where GENDER='F'
  16. /
  17. 333769 rows selected.
  18. Elapsed: 00:00:12.02
  19. Execution Plan
  20. ----------------------------------------------------------
  21. 0 SELECT STATEMENT Optimizer=CHOOSE (Cost=601 Card=333769 Byte
  22. s=8344225)
  23. 1 0 TABLE ACCESS (FULL) OF 'TEST_NORMAL' (Cost=601 Card=333769
  24. Bytes=8344225)
  1. SQL> select * from test_normal where GENDER is null;  
  2.   
  3. 166310 rows selected.  
  4.   
  5. Elapsed: 00:00:06.08  
  6.   
  7. Execution Plan  
  8. ----------------------------------------------------------  
  9.    0      SELECT STATEMENT Optimizer=CHOOSE (Cost=601 Card=166310 Bytes=4157750)  
  10.    1    0   TABLE ACCESS (FULL) OF 'TEST_NORMAL' (Cost=601 Card=166310 Bytes=4157750)  
  11.   
  12. SQL> select * from test_normal where GENDER='M';  
  13.   
  14. 499921 rows selected.  
  15.   
  16. Elapsed: 00:00:16.07  
  17.   
  18. Execution Plan  
  19. ----------------------------------------------------------  
  20.    0      SELECT STATEMENT Optimizer=CHOOSE (Cost=601 Card=499921 Bytes=12498025)  
  21.    1    0   TABLE ACCESS (FULL) OF 'TEST_NORMAL' (Cost=601 Card=499921Bytes=12498025)  
  22.   
  23. SQL>select * from test_normal where GENDER='F'  
  24.  /  
  25.   
  26. 333769 rows selected.  
  27.   
  28. Elapsed: 00:00:12.02  
  29.   
  30. Execution Plan  
  31. ----------------------------------------------------------  
  32.    0      SELECT STATEMENT Optimizer=CHOOSE (Cost=601 Card=333769 Byte  
  33.           s=8344225)  
  34.    1    0   TABLE ACCESS (FULL) OF 'TEST_NORMAL' (Cost=601 Card=333769  
  35.            Bytes=8344225)  

結論

現在我們瞭解了優化器對於這些技術做出的反應,現在我們來看對於位圖索引和B樹索引最適合的程序。

保持GENDER列上的位圖索引,在SAL列上再建立一個位圖索引然後執行一些查詢。對這些列上的B樹索引執行同樣的查詢。

在表test_normal中,你需要所有工資等於下列值的所有女性僱員的僱員號碼:

1000
1500
2000
2500
3000
3500
4000
4500

因此:

  1. SQL>select * from test_normal
  2. where sal in (1000,1500,2000,2500,3000,3500,4000,4500,5000) and GENDER='M';
  1. SQL>select * from test_normal   
  2. where sal in (1000,1500,2000,2500,3000,3500,4000,4500,5000) and GENDER='M';  

這是一個典型的數據倉庫查詢,絕對不要在OLTP系統中執行該查詢。下面是兩列上具有位圖索引時的結果:

  1. SQL>select * from test_normal
  2. where sal in (1000,1500,2000,2500,3000,3500,4000,4500,5000) and GENDER='M';
  3. 1453 rows selected.
  4. Elapsed: 00:00:02.03
  5. Execution Plan
  6. ----------------------------------------------------------
  7. 0 SELECT STATEMENT Optimizer=CHOOSE (Cost=198 Card=754 Bytes=18850)
  8. 1 0 TABLE ACCESS (BY INDEX ROWID) OF 'TEST_NORMAL' (Cost=198 Card=754 Bytes=18850)
  9. 2 1 BITMAP CONVERSION (TO ROWIDS)
  10. 3 2 BITMAP AND
  11. 4 3 BITMAP OR
  12. 5 4 BITMAP INDEX (SINGLE VALUE) OF 'NORMAL_SAL_BMX'
  13. 6 4 BITMAP INDEX (SINGLE VALUE) OF 'NORMAL_SAL_BMX'
  14. 7 4 BITMAP INDEX (SINGLE VALUE) OF 'NORMAL_SAL_BMX'
  15. 8 4 BITMAP INDEX (SINGLE VALUE) OF 'NORMAL_SAL_BMX'
  16. 9 4 BITMAP INDEX (SINGLE VALUE) OF 'NORMAL_SAL_BMX'
  17. 10 4 BITMAP INDEX (SINGLE VALUE) OF 'NORMAL_SAL_BMX'
  18. 11 4 BITMAP INDEX (SINGLE VALUE) OF 'NORMAL_SAL_BMX'
  19. 12 4 BITMAP INDEX (SINGLE VALUE) OF 'NORMAL_SAL_BMX'
  20. 13 4 BITMAP INDEX (SINGLE VALUE) OF 'NORMAL_SAL_BMX'
  21. 14 3 BITMAP INDEX (SINGLE VALUE) OF 'NORMAL_GENDER_BMX'
  22. Statistics
  23. ----------------------------------------------------------
  24. 0 recursive calls
  25. 0 db block gets
  26. 1353 consistent gets
  27. 920 physical reads
  28. 0 redo size
  29. 75604 bytes sent via SQL*Net to client
  30. 1555 bytes received via SQL*Net from client
  31. 98 SQL*Net roundtrips to/from client
  32. 0 sorts (memory)
  33. 0 sorts (disk)
  34. 1453 rows processed
  1. SQL>select * from test_normal   
  2. where sal in (1000,1500,2000,2500,3000,3500,4000,4500,5000) and GENDER='M';  
  3.   
  4. 1453 rows selected.  
  5.   
  6. Elapsed: 00:00:02.03  
  7.   
  8. Execution Plan  
  9. ----------------------------------------------------------  
  10.    0      SELECT STATEMENT Optimizer=CHOOSE (Cost=198 Card=754 Bytes=18850)  
  11.    1    0   TABLE ACCESS (BY INDEX ROWID) OF 'TEST_NORMAL' (Cost=198 Card=754 Bytes=18850)  
  12.    2    1     BITMAP CONVERSION (TO ROWIDS)  
  13.    3    2       BITMAP AND  
  14.    4    3         BITMAP OR  
  15.    5    4           BITMAP INDEX (SINGLE VALUE) OF 'NORMAL_SAL_BMX'  
  16.    6    4           BITMAP INDEX (SINGLE VALUE) OF 'NORMAL_SAL_BMX'  
  17.    7    4           BITMAP INDEX (SINGLE VALUE) OF 'NORMAL_SAL_BMX'  
  18.    8    4           BITMAP INDEX (SINGLE VALUE) OF 'NORMAL_SAL_BMX'  
  19.    9    4           BITMAP INDEX (SINGLE VALUE) OF 'NORMAL_SAL_BMX'  
  20.   10    4           BITMAP INDEX (SINGLE VALUE) OF 'NORMAL_SAL_BMX'  
  21.   11    4           BITMAP INDEX (SINGLE VALUE) OF 'NORMAL_SAL_BMX'  
  22.   12    4           BITMAP INDEX (SINGLE VALUE) OF 'NORMAL_SAL_BMX'  
  23.   13    4           BITMAP INDEX (SINGLE VALUE) OF 'NORMAL_SAL_BMX'  
  24.   14    3         BITMAP INDEX (SINGLE VALUE) OF 'NORMAL_GENDER_BMX'  
  25.   
  26. Statistics  
  27. ----------------------------------------------------------  
  28.           0  recursive calls  
  29.           0  db block gets  
  30.        1353  consistent gets  
  31.         920  physical reads  
  32.           0  redo size  
  33.       75604  bytes sent via SQL*Net to client  
  34.        1555  bytes received via SQL*Net from client  
  35.          98  SQL*Net roundtrips to/from client  
  36.           0  sorts (memory)  
  37.           0  sorts (disk)  
  38.        1453  rows processed  

下面是B樹索引時的結果:

  1. SQL>select * from test_normal
  2. where sal in (1000,1500,2000,2500,3000,3500,4000,4500,5000) and GENDER='M';
  3. 1453 rows selected.
  4. Elapsed: 00:00:03.01
  5. Execution Plan
  6. ----------------------------------------------------------
  7. 0 SELECT STATEMENT Optimizer=CHOOSE (Cost=601 Card=754 Bytes=18850)
  8. 1 0 TABLE ACCESS (FULL) OF 'TEST_NORMAL' (Cost=601 Card=754 Bytes=18850)
  9. Statistics
  10. ----------------------------------------------------------
  11. 0 recursive calls
  12. 0 db block gets
  13. 6333 consistent gets
  14. 4412 physical reads
  15. 0 redo size
  16. 75604 bytes sent via SQL*Net to client
  17. 1555 bytes received via SQL*Net from client
  18. 98 SQL*Net roundtrips to/from client
  19. 0 sorts (memory)
  20. 0 sorts (disk)
  21. 1453 rows processed
  1. SQL>select * from test_normal   
  2. where sal in (1000,1500,2000,2500,3000,3500,4000,4500,5000) and GENDER='M';  
  3.   
  4. 1453 rows selected.  
  5.   
  6. Elapsed: 00:00:03.01  
  7.   
  8. Execution Plan  
  9. ----------------------------------------------------------  
  10.    0      SELECT STATEMENT Optimizer=CHOOSE (Cost=601 Card=754 Bytes=18850)  
  11.    1    0   TABLE ACCESS (FULL) OF 'TEST_NORMAL' (Cost=601 Card=754 Bytes=18850)  
  12.   
  13. Statistics  
  14. ----------------------------------------------------------  
  15.           0  recursive calls  
  16.           0  db block gets  
  17.        6333  consistent gets  
  18.        4412  physical reads  
  19.           0  redo size  
  20.       75604  bytes sent via SQL*Net to client  
  21.        1555  bytes received via SQL*Net from client  
  22.          98  SQL*Net roundtrips to/from client  
  23.           0  sorts (memory)  
  24.           0  sorts (disk)  
  25.        1453  rows processed  

可以看出,如果使用B樹索引,優化器使用全表掃描;而對於位圖索引,則使用索引。從獲取結果需要的I/O次數可以推斷出性能。

總之,基於如下的原因,位圖索引適用於決策支持系統,而不管cardinality的高低:

  • 使用位圖索引,優化器可以高效地執行包含AND,OR或者XOR的查詢。
  • 使用位圖索引,優化器可以回答對null的查詢和計數。null值在位圖索引時同樣被加上索引(不像B樹索引)。
  • 最重要的是,在決策支持系統中,位圖索引支持特殊的查詢,但B樹索引則不能。具體來說,如果你有一個包含50列的表,用戶經常查詢其中的10列 ---- 10列的組合或者有時是其中一列,創建B樹索引會比價困難。如果你在這些列上建立10個位圖索引,這些查詢都可以通過索引回答,不論你查詢的是全部10列,還是10列中的4列或者6列,或者其中的一列。

相反,B樹索引非常適合於OLTP系統,其用戶執行的都是常規的查詢。因爲在OLTP系統中,數據會頻繁地更新和刪除,如果使用位圖索引將會引起嚴重的鎖定性能問題。

兩種索引都有一個共同的目的:儘快地得到結果。但是你應該依據程序的類型來選擇其中之一,而不是根據cardinality水平。

發佈了4 篇原創文章 · 獲贊 2 · 訪問量 17萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章