轉:http://blog.itpub.net/17203031/viewspace-744477數據庫
對關係型數據庫產品(RDBMS)而言,一個重要特性就是:數據信息都被組織爲二維數據表,信息的表達能夠經過一系列的關聯(Join)來完成。具體數據庫產品在實現這個標準的時候,又有千差萬別的特色。就是一個特定的數據庫RDBMS產品,每每也提供不一樣的實現方法。ide
一、從堆表(Heap Table)到索引組織表(Index Organization Table)性能
Oracle做爲一款成熟的數據庫軟件產品,就提供了多種數據表存儲結構。咱們最多見的就是三種,分別爲堆表(Heap Table)、索引組織表(Index Organization Table,簡稱爲IOT)和聚簇表(Cluster Table)。spa
Heap Table是咱們在Oracle中最常使用的數據表,也是Oracle的默認數據表存儲結構。在Heap Table中,數據行是按照「隨機存取」的方式進行管理。從段頭塊以後,一直到高水位線一下的空間,Oracle都是按照隨機的方式進行「粗放式」管理。當一條數據須要插入到數據表中時,默認狀況下,Oracle會在高水位線如下尋找有沒有空閒的地方,可以容納這個新數據行。若是能夠找到這樣的地方,Oracle就將這行數據放在空位上。注意,這個空位選擇徹底依「能放下」的原則,這個空位多是被刪除數據行的覆蓋位。.net
若是Heap Table段的HWM下沒有找到合適的位置,Oracle堆表纔去向上推高水位線。在數據行存儲上,Heap Table的數據行是徹底沒有次序之分的。咱們稱之爲「隨機存取」特徵。orm
對Heap Table,索引獨立段的添加通常能夠有效的緩解因爲隨機存取帶來的檢索壓力。Index葉子節點上記錄的數據行鍵值和Rowid取值,可讓Server Process直接定位到數據行的塊位置。對象
聚簇(Cluster Table)是一種合併段存儲的狀況。Oracle認爲,若是一些數據表更新頻率不高,可是常常和另一個數據表進行鏈接查詢(Join)顯示,就能夠將其組織在一個存儲結構中,這樣能夠最大限度的提高性能效率。對聚簇表而言,多個數據表按照鏈接鍵的順序保存在一塊兒。blog
一般系統環境下,咱們使用Cluster Table的狀況不太多。Oracle中的數據字典大量的使用聚簇。相比是各類關聯的基表之間固定鏈接檢索的場景較多,從而肯定的方案。索引
最後就是本系列的IOT(Index Organization Table)。同Cluster Table同樣,IOT是在Oracle數據表策略的一種「非主流」,應用的場景比較窄。可是一些狀況下使用它,每每能夠起到很是好的效果。產品
簡單的說,IOT區別於堆表的最大特色,就在於數據行的組織並非隨機的,而是依據數據表主鍵,按照索引樹進行保存。從段segment結構上看,IOT索引段就包括了全部數據行列,不存在單獨的數據表段。
IOT在保存結構上有一些特殊之處,應用在一些特殊的場景之下。本系列將逐個分析IOT的一些特徵,最後討論咱們究竟在什麼樣的場景下,能夠選擇IOT做爲數據表方案。
二、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,由於索引的順序就是數據行的順序,二者存儲順序相同,絕對一致。
三、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維護等其餘內容。