點陣圖索引(Bitmap Index)——從B*樹索引到點陣圖索引

realkid4發表於2011-05-09

 

索引是我們最常使用的一種效能最佳化手段。本質上講,使用索引的優勢就是透過付出一個額外的索引塊掃描過程,獲取到符合條件的rowid集合,之後依據rowid集合訪問資料表塊。從而節省下進行全表搜尋的I/O消耗。

 

在各型別索引中,我們已經習慣使用的就是B*樹索引,也就是將索引列的取值構建為一顆平衡二叉樹。索引列每個非空行對應一個葉子節點,葉子節點上的內容包括索引取值和對應的rowidB*樹的特點就是從根節點搜尋到葉子節點,所經過的路徑和消耗相同。

 

 

我們說B*樹索引是目前資料庫解決方案中普適性最好的一種索引型別。首先,B*索引適應各種型別資料庫,可以支援目前海量資料庫的大多數資料訪問需求。其次,對一般選擇性好的資料列,B*索引通常可以提供比較優秀的索引樹訪問執行計劃。最後,以Index Range ScanIndex Unique ScanIndex Fast ScanIndex Skip Scan為主體的Index執行計劃已經比較成熟,Oracle在處理此類問題上有很多獨有的技術可以使用。

 

 

但是,隨著資料分佈和取值的一些特殊性,在一些場合下,B*樹索引存在一些力不從心的現象。

 

ü        低基數資料列索引結構空間冗餘。B*樹結構中,索引列值和rowid都會儲存在索引的葉子節點上。如果索引列的選擇性差,只有少數的幾個可選取值,那麼在索引葉子節點上就是存在大量相鄰相同鍵值的葉子節點。這樣是一種嚴重的空間浪費和IO掃描浪費;

ü        單一索引路徑選擇。在通常的SQL條件中,常常包括多個條件列,這些條件列可能分別屬於不同的多個索引。但是由於B*樹索引的特殊性,我們的執行計劃中只能出現一個索引的執行路徑,其他列條件只能作為一種篩選條件出現。這樣,很多付出維護成本的索引就失去了意義;

ü        Null值和OR條件的限制。傳統的B*樹索引,Null值是不會進入到索引結構的。同時對OR條件也存在很大的限制;

 

諸如此類,在一些情況下,B*樹索引是存在限制的。而這些問題,可以考慮透過點陣圖索引(Bitmap Index)來解決。

 

首先,點陣圖索引也是一種和資料表分開儲存的段segment結構,也是透過儘可能少讀塊block獲取結果集合rowid來實現最佳化目的的物件。點陣圖索引和B*樹索引的相同點,在於都是將平衡樹作為初始的結構。區別在於B*樹索引的葉子節點對應的是每一個索引列的對應關係,而點陣圖索引的每個葉子節點對應的是一種索引鍵值取值。每個點陣圖索引的葉子節點上,包括三部分的內容:首先是鍵值取值,第二部分為rowid範圍,最後為點陣圖對應向量。

 

ü        Bitmap Index與傳統的B*樹結構最大的區別就是葉子節點的結構。對Bitmap Index來說,一個出現的索引列取值就對應著一個葉子節點。而B*樹是一個資料行對應一個葉子節點。這樣,也就意味著當索引列取值是低基數的時候,Bitmap Index結構要遠遠小於B*樹索引結構;

ü        第二部分的rowid範圍是用於進行偏移計算使用的。這部分記錄索引列對應的資料行起始和結束的rowid取值範圍;

ü        最後也是Bitmap的重中之重,就是點陣圖向量。在Bitmap Index中,使用計算機最小的計數單位:位bit來表示行取值情況。每一個資料行對應一位bit 0/1取值,如果資料表有1000行,那麼對應的向量就有1000位長度。針對某一位來說,如果對應行的取值和該向量所在葉子節點上對應索引取值相同,取值為1,否則為0

ü         

 

那麼,我們已經可以初步考慮到Oraclebitmap Index的基本結構。一顆葉子節點很少的樹(當列基數較小的時候,也就是列取值較小的時候),每個葉子節點對應一種可能的列取值。每個葉子節點上攜帶一個點陣圖向量,表示所有行對應取值的資訊。

 

 

下面,我們透過幾個簡單實驗,看看Bitmap Index的建立和使用情況。

 

首先是環境準備:

 

//Oracle 11gR2上進行試驗;

SQL> select * from v$version where rownum<2;

 

BANNER

--------------------------------------------------------------------------------

Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Production

 

SQL> create table t as select object_id, owner, owner owner2 from dba_objects;

Table created

 

SQL> create index idx_t_owner on t(owner);

Index created

 

SQL> create bitmap index idx_t_ownerbit on t(owner2);

Index created

 

SQL> select count(distinct owner) from t;

 

COUNT(DISTINCTOWNER)

--------------------

                  30

 

SQL> select count(*) from t;

 

  COUNT(*)

----------

     72544

//構建一些空值

SQL> update t set wner=null, owner2=null where wner='ORDDATA';

248 rows updated

 

SQL> commit;

Commit complete

 

SQL> exec dbms_stats.gather_table_stats(user,'T',cascade => true);

PL/SQL procedure successfully completed

 

 

 

儲存結構上比較

 

在儲存結構上,資料表T包括超過7萬行記錄。對應的ownerowner2列取值完全一樣,差異在於構建的索引結構上。我們檢查一下兩個索引段(Index Segment)的情況。

 

 

SQL> col segment_name for a15;

SQL> select segment_name, segment_type, bytes, extents, blocks from dba_segments where wner='SYS' and segment_name like 'IDX_T%';

 

SEGMENT_NAME    SEGMENT_TYPE       BYTES    EXTENTS     BLOCKS

--------------- ------------------ ---------- ---------- ----------

IDX_T_OWNER     INDEX                 2097152         17        256

IDX_T_OWNERBIT  INDEX                   65536          1          8

 

 

資料表相同、取值相同,不同索引型別的儲存差異顯而易見。普通索引使用空間大小超過2M,共使用17個分割槽extents,摺合256block。而Bitmap Index所消耗的空間還沒有超過初始物件分配的8個塊block大小。所以,在適當的索引值下,Bitmap Index儲存空間效率上有明顯優勢。

 

 

究其原因,也比較好理解。普通索引結構中,資料表增加一行,意味著要增加一個葉子節點。如果要超過原有的塊容量,還要進行分支節點、葉子節點的拆分問題。而Bitmap Index只需要在所有的葉節點點陣圖向量中增加一個bit位而已。

 

開篇我們提到過,索引的意義價值在於讀儘可能少的資料塊,獲取到rowid列表後到資料表中定位。在獲取資料集合相同的情況下,索引結構越小,帶來的IO損耗就越少,進而成本就越低。這種特性對於資料海量的OLAPDataware系統來說,至關重要!

 

 

普通索引列條件

 

對索引搜尋,Bitmap Index具有更高的執行成本優勢。

 

//普通索引結構

SQL> select * from t where wner='SCOTT';

已選擇36行。

已用時間:  00: 00: 00.01

 

執行計劃

----------------------------------------------------------

Plan hash value: 1516787156

-------------------------------------------------------------------------------------------

| Id  | Operation           | Name        | Rows  | Bytes | Cost (%CPU)| Time     |

-------------------------------------------------------------------------------------------

|   0 | SELECT STATEMENT  |      |   103 |  1751 |     4   (0)| 00:00:01 |

|   1 |  TABLE ACCESS BY INDEX ROWID| T  |   103 |  1751 |     4   (0)| 00:00:01 |

|*  2 |   INDEX RANGE SCAN  | IDX_T_OWNER |   103 |   |     3   (0)| 00:00:01 |

-------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

   2 - access("OWNER"='SCOTT')

統計資訊

----------------------------------------------------------

          1  recursive calls

          0  db block gets

         17  consistent gets

         11  physical reads

          0  redo size

       1013  bytes sent via SQL*Net to client

        398  bytes received via SQL*Net from client

          4  SQL*Net roundtrips to/from client

          0  sorts (memory)

          0  sorts (disk)

         36  rows processed

 

//Bitmap Index搜尋路徑

SQL> select * from t where owner2='SCOTT';

已選擇36行。

 

已用時間:  00: 00: 00.01

執行計劃

----------------------------------------------------------

Plan hash value: 2207144928

-----------------------------------------------------------------------------------------------

| Id  | Operation        | Name           | Rows  | Bytes | Cost (%CPU)| Time     |

-----------------------------------------------------------------------------------------------

|   0 | SELECT STATEMENT    |        |   103 |  1751 |    21   (0)| 00:00:01 |

|   1 |  TABLE ACCESS BY INDEX ROWID | T  |   103 |  1751 |    21   (0)| 00:00:01 |

|   2 |   BITMAP CONVERSION TO ROWIDS|      |       |       |            | |

|*  3 |    BITMAP INDEX SINGLE VALUE | IDX_T_OWNERBIT |       |       |  |  |

-----------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

   3 - access("OWNER2"='SCOTT')

 

統計資訊

----------------------------------------------------------

          1  recursive calls

          0  db block gets

         14  consistent gets

          2  physical reads

          0  redo size

       1013  bytes sent via SQL*Net to client

        398  bytes received via SQL*Net from client

          4  SQL*Net roundtrips to/from client

          0  sorts (memory)

          0  sorts (disk)

         36  rows processed

 

 

 

對比關係上,雖然兩者實際執行時間接近,但是使用Bitmap Index的執行計劃在IO處理量上具有比較明顯的優勢。

 

空值Null檢索性

 

在準備資料環境時,我們已經有意的構建一部分null進入owner/owner2資料列。下面我們來看看實際的效果。

 

//普通索引列

SQL> explain plan for select * from t where owner is null;

 

Explained

 

SQL> select * from table(dbms_xplan.display);

 

PLAN_TABLE_OUTPUT

--------------------------------------------------------------------------------

Plan hash value: 1601196873

--------------------------------------------------------------------------

| Id  | Operation         | Name | Rows  | Bytes | Cost (%CPU)| Time     |

--------------------------------------------------------------------------

|   0 | SELECT STATEMENT  |      |   248 |  4216 |    60   (0)| 00:00:01 |

|*  1 |  TABLE ACCESS FULL| T    |   248 |  4216 |    60   (0)| 00:00:01 |

--------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

   1 - filter("OWNER" IS NULL)

13 rows selected

 

//Bitmap Index索引列

SQL> explain plan for select * from t where owner2 is null;

 

Explained

 

SQL> select * from table(dbms_xplan.display);

 

PLAN_TABLE_OUTPUT

--------------------------------------------------------------------------------

Plan hash value: 2207144928

--------------------------------------------------------------------------------

| Id  | Operation                    | Name           | Rows  | Bytes | Cost (%C

--------------------------------------------------------------------------------

|   0 | SELECT STATEMENT             |                |   248 |  4216 |    32

|   1 |  TABLE ACCESS BY INDEX ROWID | T              |   248 |  4216 |    32

|   2 |   BITMAP CONVERSION TO ROWIDS|                |       |       |

|*  3 |    BITMAP INDEX SINGLE VALUE | IDX_T_OWNERBIT |       |       |

--------------------------------------------------------------------------------

Predicate Information (identified by operation id):

---------------------------------------------------

   3 - access("OWNER2" IS NULL)

15 rows selected

 

 

可以看出,對Null來說,是不會進入普通的B*樹,所以執行is null也就不可能出現索引路徑。而null作為一個可選取值列值,是可以作為一個葉子節點出現在Bitmap Index上的。

 

 

結論:B*樹索引在普適性上的優勢,可以滿足絕大多數的實際需求。但是一些特殊場景下,我們可以使用Bitmap索引來解決問題,提高資料表,特別是OLAP海量資料表的檢索效率問題。

 

 

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

相關文章