簡述Oracle IOT(Index Organized Table)(上)

壹頁書發表於2014-02-05

 

對關係型資料庫產品(RDBMS)而言,一個重要特性就是:資料資訊都被組織為二維資料表,資訊的表達可以透過一系列的關聯(Join)來完成。具體資料庫產品在實現這個標準的時候,又有千差萬別的特點。就是一個特定的資料庫RDBMS產品,往往也提供不同的實現方法。

 

1、從堆表(Heap Table)到索引組織表(Index Organization Table)

 

Oracle作為一款成熟的資料庫軟體產品,就提供了多種資料表儲存結構。我們最常見的就是三種,分別為堆表(Heap Table)、索引組織表(Index Organization Table,簡稱為IOT)和聚簇表(Cluster Table)。

 

Heap Table是我們在Oracle中最常使用的資料表,也是Oracle的預設資料表儲存結構。在Heap Table中,資料行是按照“隨機存取”的方式進行管理。從段頭塊之後,一直到高水位線一下的空間,Oracle都是按照隨機的方式進行“粗放式”管理。當一條資料需要插入到資料表中時,預設情況下,Oracle會在高水位線以下尋找有沒有空閒的地方,能夠容納這個新資料行。如果可以找到這樣的地方,Oracle就將這行資料放在空位上。注意,這個空位選擇完全依“能放下”的原則,這個空位可能是被刪除資料行的覆蓋位。

 

如果Heap Table段的HWM下沒有找到合適的位置,Oracle堆表才去向上推高水位線。在資料行儲存上,Heap Table的資料行是完全沒有次序之分的。我們稱之為“隨機存取”特徵。

 

對Heap Table,索引獨立段的新增一般可以有效的緩解由於隨機存取帶來的檢索壓力。Index葉子節點上記錄的資料行鍵值和Rowid取值,可以讓Server Process直接定位到資料行的塊位置。

 

聚簇(Cluster Table)是一種合併段儲存的情況。Oracle認為,如果一些資料表更新頻率不高,但是經常和另外一個資料表進行連線查詢(Join)顯示,就可以將其組織在一個儲存結構中,這樣可以最大限度的提升效能效率。對聚簇表而言,多個資料表按照連線鍵的順序儲存在一起。

 

通常系統環境下,我們使用Cluster Table的情況不太多。Oracle中的資料字典大量的使用聚簇。相比是各種關聯的基表之間固定連線檢索的場景較多,從而確定的方案。

 

最後就是本系列的IOT(Index Organization Table)。同Cluster Table一樣,IOT是在Oracle資料表策略的一種“非主流”,應用的場景比較窄。但是一些情況下使用它,往往可以起到非常好的效果。

 

簡單的說,IOT區別於堆表的最大特點,就在於資料行的組織並不是隨機的,而是依據資料表主鍵,按照索引樹進行儲存。從段segment結構上看,IOT索引段就包括了所有資料行列,不存在單獨的資料表段。

 

IOT在儲存結構上有一些特殊之處,應用在一些特殊的場景之下。本系列將逐個分析IOT的一些特徵,最後討論我們究竟在什麼樣的場景下,可以選擇IOT作為資料表方案。

 

2、IOT基礎

 

在建立使用IOT上,我們要強調Primary Key的作用。對一般的堆表而言,Primary Key是可有可無的。一種說法是:當一個堆表沒有設定主鍵的時候,rowid偽列就是對應的主鍵值。而且,Primary Key可以在資料表建立之後進行追加設定。

 

但是,IOT對於主鍵的設定格外嚴格,要求建立表的時候就必須指定明確的主鍵列。下面我們透過一系列的實驗來證明,實驗環境為Oracle 11g。

 

 

SQL> select * from v$version;

BANNER

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

Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Production

PL/SQL Release 11.2.0.1.0 - Production

CORE    11.2.0.1.0  Production

 

 

我們使用相同的結構,來建立出IOT和Heap Table對照。

 

--不指定主鍵,是無法建立IOT

SQL> create table m (id number) organization index;

 

create table m (id number) organization index

ORA-25175: 未找到任何 PRIMARY KEY 約束條件

 

 

在create table語句後面使用organization index,就指定資料表建立結構是IOT。但是在不指定主鍵Primary Key的情況下,是不允許建表的。

 

 

SQL> create table t_iot (object_id number(10) primary key, object_name varchar2(100)) organization index;

Table created

 

SQL> create table t_heap (object_id number(10) primary key, object_name varchar2(100));

Table created

 

(插入相同資料來源行……)

 

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

PL/SQL procedure successfully completed

 

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

PL/SQL procedure successfully completed

 

 

從資料字典的層面上,我們分析一下兩個資料表的差異,一窺IOT的特點。

 

 

SQL> select table_name, tablespace_name, blocks, num_rows from user_tables where table_name in ('T_IOT','T_HEAP');

 

TABLE_NAME          TABLESPACE_NAME                    BLOCKS   NUM_ROWS

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

T_HEAP                SYSTEM                                157      72638

T_IOT                                                                72638

 

SQL> select segment_name, blocks, extents from user_segments where segment_name in ('T_IOT','T_HEAP');

 

SEGMENT_NAME             BLOCKS    EXTENTS

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

T_HEAP                      256         17

 

 

上面兩句SQL揭示了幾個問題。首先,Oracle承認IOT是一個資料表,並且統計了資料行數。但是對資料表的儲存表空間和大小沒有明確的說明,user_tables檢視中這部分的內容為空。

 

其次,從段結構來看,Oracle明確不承認存在T_IOT段。因為如果有段segment物件,就意味有空間分配。但是資料表有資料,是存放在哪裡呢?

 

我們知道,給資料表新增索引的時候,Oracle會自動的新增一個唯一索引。那麼我們去檢查一下這部分的結構情況。

 

 

SQL> select index_name, index_type, table_name, PCT_THRESHOLD, CLUSTERING_FACTOR from user_indexes where table_name in ('T_IOT','T_HEAP');

 

INDEX_NAME    INDEX_TYPE           TABLE_NAME PCT_THRESHOLD CLUSTERING_FACTOR

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

SYS_C0012408    NORMAL               T_HEAP                                 256

SYS_IOT_TOP_75124 IOT - TOP      T_IOT                 50                 0

 

SQL> select segment_name, blocks, extents from user_segments where segment_name in ('SYS_C0012408','SYS_IOT_TOP_75124');

 

SEGMENT_NAME             BLOCKS    EXTENTS

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

SYS_C0012408                256         17

SYS_IOT_TOP_75124           256         17

 

 

索引段是存在的,而且明確標註索引型別為IOT索引。這說明幾個問題:

 

首先,對於IOT而言,只有索引段,沒有資料段。一般的索引而言,葉子節點上只有索引列的取值和rowid。而對於IOT而言,主鍵索引上對應就是資料行和索引列取值。

 

其次,IOT的溢位段閾值(PCT_THRESHOLD)。這是Oracle IOT的特殊策略。簡單的說,當我們把全部資料行儲存在葉子節點上,一旦發生主鍵值的變化、新值插入、刪除等動作,索引葉子塊的分裂動作是頻繁的。資料行儲存在葉子節點上只會讓這樣的分裂動作更加頻繁和後果嚴重。Oracle提出將一部分的非主鍵列單獨儲存,這個引數就是比例值。

 

最後,我們探討一下IOT索引的Clustering Factor。Clustering Factor是反映索引葉子節點順序和資料儲存行直接離散程度的綜合性指標。一般來說,堆表的Clustering Factor是隨著DML操作不斷退化的過程。Clustering Factor是影響到Oracle索引路徑成本的一個重要引數(http://space.itpub.net/17203031/viewspace-680936),會影響到CBO的成本決策。 IOT的索引這部分的值永遠為0,因為索引的順序就是資料行的順序,兩者儲存順序相同,絕對一致。

 

 

3、IOT與執行計劃

 

在IOT資料表下,我們通常的執行計劃會如何呢?普通Heap Table和IOT在這部分的差異很大。

 

通常而言,Heap Table的索引路徑伴隨著兩次段結構的讀取——索引段和資料段。先讀取索引段段頭,經歷根節點、分支節點、葉子節點,最後獲取到結果集合rowid列表。之後進行回表操作,使用rowid依次查詢資料表的行。

 

但是IOT表可以不同。索引和資料保留在一起,理論上拿到了葉子節點,也就是拿到了資料行。IOT是不存在回表操作的,所以相對heap table來說,回表部分成本是節省的。

 

下面我們透過執行計劃,來看IOT的特徵。

 

 

SQL> explain plan for select * from t_iot where object_id=1000;

Explained

 

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

 

PLAN_TABLE_OUTPUT

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

Plan hash value: 2277898128

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

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

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

|   0 | SELECT STATEMENT  |                 |     1 |    11 |     1   (0)| 00:

|*  1 |  INDEX UNIQUE SCAN| SYS_IOT_TOP_75124 |    1 |    11 |     1   (0)| 00:

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

Predicate Information (identified by operation id):

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

   1 - access("OBJECT_ID"=1000)

13 rows selected

 

SQL> explain plan for select * from t_iot;

Explained

 

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

 

PLAN_TABLE_OUTPUT

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

Plan hash value: 4201110863

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

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

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

|   0 | SELECT STATEMENT     |                   | 72638 |   780K|    47   (0)|

|   1 |  INDEX FAST FULL SCAN| SYS_IOT_TOP_75124 | 72638 |   780K|    47   (0)|

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

8 rows selected

 

 

對於IOT,我們要保證訪問的資料表的方式是主鍵路徑為主。在上面的兩個執行計劃中,我們按照主鍵進行檢索,路徑為Index Unique Scan。全表掃描為Index Fast Full Scan。兩者都沒有明顯的回表動作。

 

試想,如果資料表較小,Index Full Scan也是IOT表常常出現的執行路徑。

 

對一般的Heap Table,執行路徑如何呢?

 

 

SQL> explain plan for select * from t_heap where object_id=1000;

Explained

 

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

 

PLAN_TABLE_OUTPUT

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

Plan hash value: 1833345710

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

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

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

|   0 | SELECT STATEMENT           |              |     1 |    11 |     2   (0)

|   1 |  TABLE ACCESS BY INDEX ROWID| T_HEAP      |     1 |    11 |     2   (0)

|*  2 |   INDEX UNIQUE SCAN        | SYS_C0012408 |     1 |       |     1   (0)

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

Predicate Information (identified by operation id):

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

   2 - access("OBJECT_ID"=1000)

14 rows selected

 

 

SQL> explain plan for select * from t_heap;

Explained

 

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

PLAN_TABLE_OUTPUT

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

Plan hash value: 1253663840

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

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

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

|   0 | SELECT STATEMENT  |        | 72638 |   780K|    42   (0)| 00:00:01 |

|   1 |  TABLE ACCESS FULL| T_HEAP | 72638 |   780K|    42   (0)| 00:00:01 |

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

 

8 rows selected

 

 

普通堆表都不能避免出現回表動作。

 

最後,我們要宣告一下回表動作的成本影響。IOT和Heap Table一個很大的執行計劃差異,就是回表。但是從成本上計算,CBO並不是因為回表動作才確定執行計劃,而是Clustering Factor的影響。

 

對堆表而言,Clustering Factor都是一個很大的問題,無論是CBO的成本公式上,還是不斷Degrade的前景。IOT一個突出優勢就是直接消滅了Clustering Factor的成本因素。

 

但是這也就帶來一個問題,一個資料表只能按照主鍵的順序進行組織,輔助索引(Secondary Index)的問題是很多版本Oracle和IOT使用者爭議的話題。Secondary Index問題我們在後面會繼續討論到。

 

下篇中我們會繼續討論有關IOT維護等其他內容。

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

相關文章