對關系型數據庫產品(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 fromuser_tableswhere 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 wheresegment_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_75124IOT - 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 whereobject_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的基本知識和概念。本篇繼續來介紹IOT相關的內容。
4、IOT日常維護
相對于堆表heap結構,索引組織表最大的特點在于將數據行全部內容作為葉子節點保存在索引結構中。IOT中只包括索引段(Index Segment)結構,沒有對應的數據表段(Table Segment)結構。
在日常運維工作中,我們經常需要對索引結構進行定期的重構rebuild操作,來消除索引無效節點(Dead Node)。那么,IOT結構中,我們維護工作需要注意些什么問題呢?
我們依然使用上篇的IOT數據表T_IOT和堆表T_HEAP來進行比對實驗。
SQL> select index_name from user_indexes where table_name='T_IOT';
INDEX_NAME
------------------------------
SYS_IOT_TOP_75124
數據表T_IOT對應的主鍵索引名稱為SYS_IOT_TOP_75124。該索引段大致空間為2M。
SQL> desc t_iot;
Name Type Nullable Default Comments
----------- ------------- -------- ------- --------
OBJECT_ID NUMBER(10)
OBJECT_NAME VARCHAR2(100) Y
SQL> select count(*) from t_iot;
COUNT(*)
----------
72638
SQL> select segment_name, bytes/1024/1024 from user_segments where segment_name='SYS_IOT_TOP_75124';
SEGMENT_NAME BYTES/1024/1024
------------------------------ ---------------
SYS_IOT_TOP_75124 2
我們刪除一批數據,形成死葉子節點。
SQL> delete t_iot where rownum<40000;
39999 rows deleted
SQL> commit;
Commit complete
SQL> exec dbms_stats.gather_table_stats(user,'T_IOT',cascade => true);
PL/SQL procedure successfully completed
SQL> select segment_name, bytes/1024/1024 from user_segments where segment_name='SYS_IOT_TOP_75124';
SEGMENT_NAME BYTES/1024/1024
------------------------------ ---------------
SYS_IOT_TOP_75124 2
數據行被刪除,索引段HWM沒有收縮。我們可以使用analyze index命令進行索引健康程度檢查。
SQL> analyze index SYS_IOT_TOP_75124 validate structure;
Index analyzed
QL> select height, blocks, name, lf_rows, DEL_LF_ROWS, pct_used from index_stats;
HEIGHT BLOCKS NAME LF_ROWS DEL_LF_ROWS PCT_USED
---------- ---------- ------------------------------ ---------- ----------- ----------
2 256 SYS_IOT_TOP_75124 72638 39999 90
從index_stats視圖中,我們可以清晰看到有接近四萬葉子節點是Dead狀態,索引樹高度為2。我們進行索引rebuild,是常用的整理索引操作。
SQL> alter index SYS_IOT_TOP_75124 rebuild;
alter index SYS_IOT_TOP_75124 rebuild
ORA-28650: IOT中的主索引不能重建
SQL> alter table t_iot disable constraint SYS_IOT_TOP_75124;
alter table t_iot disable constraint SYS_IOT_TOP_75124
ORA-25188:對于索引表或排序散列簇,無法刪除/禁用/延遲主鍵約束條件
常用的rebuild操作不能使用在IOT主鍵索引中,而且disable索引也沒有辦法實現。整理IOT的方法,可以選擇數據表的move方法。
SQL> alter table t_iot move;
Table altered
SQL> exec dbms_stats.gather_table_stats(user,'T_IOT',cascade => true);
PL/SQL procedure successfully completed
SQL> select segment_name, bytes/1024/1024 from user_segments where segment_name='SYS_IOT_TOP_75124';
SEGMENT_NAME BYTES/1024/1024
------------------------------ ---------------
SYS_IOT_TOP_75124 0.6875
整理數據表t_iot move操作后,索引高水位線下降。
SQL> analyze index SYS_IOT_TOP_75124 validate structure;
Index analyzed
SQL> select height, blocks, name, lf_rows, DEL_LF_ROWS, pct_used from index_stats;
HEIGHT BLOCKS NAME LF_ROWS DEL_LF_ROWS PCT_USED
---------- ---------- ------------------------------ ---------- ----------- ----------
2 88 SYS_IOT_TOP_75124 32639 0 89
從分析結果看,我們消除了死葉子節點。那么,我們是否可以對數據表開啟row movement呢?這個操作是move操作的替代品。
SQL> alter table t_iot enable row movement;
alter table t_iot enable row movement
ORA-14066:未分區的索引表的選項非法
SQL> alter table t_heap enable row movement;
Table altered
從實驗結果看,row movement不能應用到IOT上。
5、IOT Index Overflow Segment
對IOT表而言,我們需要考慮Overflow Segment的問題。B樹索引葉子節點存在一個長期讓我們爭議的問題,就是葉子塊分裂、合并的問題。
索引結構成樹過程和維持過程,是一個索引樹不斷分裂葉子節點、拷貝數據的過程。當一個新葉子節點值加入索引樹的時候,索引結構需要將其有序的分配在特定的葉子“位置”上。這點和堆表heap table的隨機保存策略差異很大。如果這個位置所在的數據塊已經寫“滿”,就需要進行數據塊分裂(5/5算法或者9/1算法),找一個新的空白塊,將溢出的數據葉子節點信息寫入到新塊中。這個過程同時伴隨著分支節點的調整。
維持B樹平衡過程是很復雜的過程,一般數據表為了維持對應索引的同步結構通常要損失一個數量級的DML操作效率。
對于IOT來說,這種B樹平衡過程代表更加復雜的消耗。因為IOT表的所有數據行都要保存在葉子塊中,維持樹過程中的拷貝和分裂操作更加劇烈。Oracle為了緩解這個情況,引入了IOT Overflow Segment概念。
通常來說,我們使用IOT表是需要進行考量的。我們很傾向選擇數據主鍵列相對較大,列數相對較少的數據表作為IOT表。同時,讀多寫少也是IOT的重要定性指標。
Overflow Segment(溢出段)的理念很簡單,通過設置一個閾值(PCTThreshold),來規定將數據行轉移存儲位置。如果我們將PCTThreshold值設置為10,那么如果一個數據行空間占有比例超過了10%數據塊大小,非主鍵列都會被“溢”出到IOT索引之外進行保存。這個溢出空間我們稱之為“Overflow Segment”,我們也可以為溢出段指定單獨的表空間進行保存。
Overflow Segment存在的表空間,我們稱之為Overflow Segment Tablespace。下面我們創建一個全新的IOT,設置專門的PCTThreshold值。
SQL> create table t_iotbig
2 (object_id number primary key,
3 object_name varchar2(200),
4 object_type varchar2(100),
5 EDITION_NAME varchar2(100),
6 last_ddl_time date)
7 organization index tablespace users
8 pctthreshold 5
9 overflow tablespace example;
Table created
SQL> insert into t_iotbig select object_id, object_name, object_type, edition_name, last_ddl_time from dba_objects;
72604 rows inserted
SQL> commit;
Commit complete
SQL> exec dbms_stats.gather_table_stats(user,'T_IOTBIG',cascade => true);
PL/SQL procedure successfully completed
數據表段(本質是索引段)所在表空間指定,是通過organization index tablespace指定的。Pctthreshold參數來指定溢出段閾值,我們試驗中設置為5%。溢出段overflow segment通過overflow tablespace來指定。
裝載約7萬余條數據之后,我們檢查數據段的情況。
SQL> col tablespace_name for a10;
SQL> col iot_name for a10;
SQL> select table_name, tablespace_name, num_rows, iot_type, iot_name from dba_tables where wner='SYS' and table_name='T_IOTBIG';
TABLE_NAME TABLESPACE NUM_ROWS IOT_TYPE IOT_NAME
------------------------------ ---------- ---------- ------------ ----------
T_IOTBIG 72604 IOT
SSQL> select index_name, index_type, PCT_THRESHOLD, tablespace_name from dba_indexes where table_name='T_IOTBIG' and wner='SYS';
INDEX_NAME INDEX_TYPE PCT_THRESHOLD TABLESPACE
------------------------------ --------------------------- ------------- ----------
SYS_IOT_TOP_75137 IOT - TOP 5 USERS
索引具備屬性pct_threshold=5。同時,我們在dba_tables中,可以看到溢出段的情況。
SQL> select table_name, tablespace_name, iot_name, iot_type from dba_tables where wner='SYS' and iot_name='T_IOTBIG';
TABLE_NAME TABLESPACE IOT_NAME IOT_TYPE
------------------------------ ---------- ---------- ------------
SYS_IOT_OVER_75137 EXAMPLE T_IOTBIG IOT_OVERFLOW
在數據表視圖中,我們發現IOT_NAME中對應IOT數據表名稱的對象中,存在一個特殊的隱含數據表,命名為系統自動命名。這個數據表和IOT不同,明確表示存在表空間EXAMPLE中,IOT_TYPE也明確標注出IOT_OVERFLOW類型。
我們從段空間分配的角度,看IOT表T_IOTBIG的情況。
SQL> select segment_name, segment_type, tablespace_name, extents, blocks from dba_segments where wner='SYS' and segment_name in ('SYS_IOT_OVER_75137','SYS_IOT_TOP_75137','T_IOTBIG');
SEGMENT_NAME SEGMENT_TYPE TABLESPACE EXTENTS BLOCKS
-------------------- ------------------ ---------- ---------- ----------
SYS_IOT_TOP_75137 INDEX USERS 20 640
SYS_IOT_OVER_75137 TABLE EXAMPLE 1 8
從dba_segments中,可以清楚看到IOT表的空間使用情況:索引段是有空間分配的、溢出段也是有空間分配的。而且兩者可以在不同的表空間。
參數pctthreshold是可以指定這個溢出段閾值。如果不指定,Oracle會選擇一個默認值50%。我們的IOT表t_iot就是這樣的方式。
SQL> select index_name, index_type, PCT_THRESHOLD, tablespace_name from dba_indexes where table_name='T_IOT' and wner='SYS';
INDEX_NAME INDEX_TYPE PCT_THRESHOLD TABLESPACE
------------------------------ --------------------------- ------------- ----------
SYS_IOT_TOP_75124 IOT - TOP 50 SYSTEM
SQL> select count(*) from dba_tables where wner='SYS' and iot_name='T_IOT';
COUNT(*)
----------
0
下面我們討論一下由于數據存儲為索引而帶來的Rowid、Secondary Index問題。
下面我們討論一下由于數據存儲為索引而帶來的Rowid、Secondary Index問題。
6、Logical Rowid & Secondary Index
在IOT的環境下,我們是不能保證一個固定的物理Rowid的。
堆表(Heap Table)中,一行數據被保存在一個物理位置(file no. + block no.)之后,在正常保存行為中,即使發生行遷移現象,它的rowid是不會發生變化的。只有在進行數據表存儲重構,如move和shrink space的時候才會發生新的rowid賦予。
堆表rowid的固定給我們帶來一個好處,就是連帶的數據表索引葉子節點上面的的rowid永遠有效的,除非發生move或者和shrink space操作(此時索引失效)。
但是,IOT存在一些問題。索引葉子節點的分裂操作是相當頻繁的,我們很難保證一個數據行維持在一個rowid不會發生大的變化。當然,如果我們保證每次訪問數據表都是通過主鍵primary key方式,變化的rowid不會有任何影響。問題出在非主鍵的索引,IOT中稱之為“二級索引”Secondary Index上。
對于一般的二級索引,如果葉子節點上保留數據行的rowid,那么失效的rowid意味著所有對應的二級索引非常容易變為invalid狀態。
在很多數據庫版本,包括早期的Oracle版本中,對于Secondary Index是不支持的。最近的oracle中,引入了Logical Rowid和Physical Guess的方法,才最終解決了Secondary Index問題。
SQL> select rowid, object_id from t_iot where rownum<5;
ROWID OBJECT_ID
------------------------------- -----------
*BABBVmoCwQP+ 2
*BABBVmoCwQT+ 3
*BABBVmoCwQX+ 4
*BABBVmoCwQb+ 5
對IOT而言,rowid基本上是不合乎我們常見的heap table rowid格式的。我們可以對t_iot添加secondary index。
SQL> create index idx_t_iot_name on t_iot(object_name);
Index created
SQL> exec dbms_stats.gather_table_stats(user,'T_IOT',cascade => true);
PL/SQL procedure successfully completed
從數據字典中看,索引idx_t_iot_name沒有什么額外的差異,只是對于一般索引來說,clustering factor取值略高。
SQL> select index_Name, index_type, clustering_factor from dba_indexes where wner='SYS' and index_name='IDX_T_IOT_NAME';
INDEX_NAME INDEX_TYPE CLUSTERING_FACTOR
------------------------------ --------------------------- -----------------
IDX_T_IOT_NAME NORMAL 55006
SQL> select count(*) from t_iot;
COUNT(*)
----------
72604
SQL> select sum(bytes)/1024/1024, count(*) from dba_extents where wner='SYS' and segment_name='IDX_T_IOT_NAME';
SUM(BYTES)/1024/1024 COUNT(*)
-------------------- ----------
4 19
對于一個7萬余條記錄的數據表索引,占到了19個分區,總看空間4M。那么,如果是一般的heap table index呢?空間如何?
SQL> desc t_heap;
Name Type Nullable Default Comments
----------- ------------- -------- ------- --------
OBJECT_ID NUMBER(10)
OBJECT_NAME VARCHAR2(100) Y
SQL> create index idx_t_heap_name on t_heap(object_name);
Index created
SQL> select count(*) from t_heap;
COUNT(*)
----------
72605
SQL> select sum(bytes)/1024/1024, count(*) from dba_extents where wner='SYS' and segment_name='IDX_T_HEAP_NAME';
SUM(BYTES)/1024/1024 COUNT(*)
-------------------- ----------
3 18
相同取值,正常index只有3M空間,約占到18個分區。說明:Secondary Index對比一些其他索引,有很多特殊的信息在其中。
SQL> col object_name for a20;
SQL> select object_id, object_name from dba_objects where object_name in ('IDX_T_HEAP_NAME','IDX_T_IOT_NAME');
OBJECT_ID OBJECT_NAME
---------- --------------------
75146 IDX_T_HEAP_NAME
75143 IDX_T_IOT_NAME
我們嘗試將兩個索引樹dump出來,探索其結構差異。
SQL> select value from v$diag_info where name='Default Trace File';
VALUE
--------------------------------------------------------------------------------
/u01/diag/rdbms/wilson/wilson/trace/wilson_ora_9101.trc
--堆表索引結構
SQL> alter system set events 'immediate trace name treedump level 75146';
System altered
--IOT表索引結構
SQL> alter system set events 'immediate trace name treedump level 75143';
System altered
首先,我們分析一下一般堆表的索引情況。由于篇幅原因,只截取部分內容。
*** ACTION NAME:(Command Window - New) 2012-10-05 02:43:39.561
----- begin tree dump
branch:0x415c01 4283393(0: nrow: 2, level: 2)
branch:0x415d3b 4283707(-1: nrow: 312, level: 1)
leaf:0x415c02 4283394(-1: nrow: 184 rrow: 184)
leaf: 0x415c03 4283395 (0: nrow: 184 rrow: 184)
leaf: 0x415c04 4283396 (1: nrow: 188 rrow: 188)
leaf: 0x415c05 4283397 (2: nrow: 190 rrow: 190)
leaf: 0x415c06 4283398 (3: nrow: 184 rrow: 184)
leaf: 0x415c07 4283399 (4: nrow: 186 rrow: 186)
leaf: 0x415c08 4283400 (5: nrow: 185 rrow: 185)
從Dump結果上,我們可以清晰看到IDX_T_HEAP_NAME是一個兩層索引結構。根節點地址為0x415c01(file=1, block=89089)。
SQL> select to_number('415c01','xxxxxx') from dual;
TO_NUMBER('415C01','XXXXXX')
----------------------------
4283393
其中的一個數據塊0x415c06進行試驗,轉化為十進制地址為4283398,二進制地址為:10000010101110000000110。根據rfile解析規則,最終地址為:file_no=1,block_no=89094。
SQL> alter system dump datafile 1 block 89094;
System altered
Dump文件中葉子節點的內容為:
row#0[8000] flag: ------, lock: 0, len=32
col 0; len 22; (22):
2f 31 34 64 63 62 36 32 32 5f 53 79 6e 74 68 4c 61 62 65 6c 55 49
col 1; len 6; (6): 00 41 55 91 00 4b
row#1[7968] flag: ------, lock: 0, len=32
col 0; len 22; (22):
2f 31 34 64 63 62 36 32 32 5f 53 79 6e 74 68 4c 61 62 65 6c 55 49
col 1; len 6; (6): 00 41 55 91 00 4c
row#2[7928] flag: ------, lock: 0, len=40
col 0; len 30; (30):
2f 31 34 65 33 63 31 31 32 5f 50 4e 47 45 6e 63 6f 64 65 50 61 72 61 6d 50
61 6c 65 74 74
col 1; len 6; (6): 00 41 5b 9a 00 9d
從結構上猜測,col0和col1分別表示索引列取值和對應rowid信息。而IOT的secondary index如何呢?
*** 2012-10-05 02:43:55.944
----- begin tree dump
branch: 0x4154b9 4281529 (0: nrow: 2, level: 2)
branch: 0x415acd 4283085 (-1: nrow: 330, level: 1)
leaf: 0x4154ba 4281530 (-1: nrow: 160 rrow: 160)
leaf: 0x4154bb 4281531 (0: nrow: 158 rrow: 158)
leaf: 0x4154bc 4281532 (1: nrow: 163 rrow: 163)
leaf: 0x4154bd 4281533 (2: nrow: 162 rrow: 162)
leaf: 0x4154be 4281534 (3: nrow: 163 rrow: 163)
leaf: 0x4154bf 4281535 (4: nrow: 160 rrow: 160)
leaf: 0x4154c0 4281536 (5: nrow: 159 rrow: 159)
leaf: 0x4154c1 4281537 (6: nrow: 161 rrow: 161)
leaf: 0x4154c2 4281538 (7: nrow: 160 rrow: 160)
葉子節點0x4154bc,對應具體的二進制為:10000010101010010111100。分析獲得的位置為:file_no=1,block_no=87228。
我們將該塊dump出。
SQL> alter system dump datafile 1 block 87228;
System altered
row#0[7986] flag: K-----, lock: 0, len=46
col 0; len 30; (30):
2f 31 32 30 66 34 37 30 38 5f 46 75 6c 6c 48 54 4d 4c 44 6f 63 75 6d 65 6e
74 61 74 69 6f
col 1; len 4; (4): c3 04 39 24
tl: 8 fb: --H-FL-- lb: 0x0 cc: 1
col 0: [ 4] 00 41 59 3e
row#1[7940] flag: K-----, lock: 0, len=46
col 0; len 30; (30):
2f 31 32 30 66 64 37 36 64 5f 4f 72 61 63 6c 65 44 61 74 61 62 61 73 65 4d
65 74 61 44 61
col 1; len 4; (4): c3 02 50 1a
tl: 8 fb: --H-FL-- lb: 0x0 cc: 1
col 0: [ 4] 00 41 54 a1
row#2[7894] flag: K-----, lock: 0, len=46
col 0; len 30; (30):
2f 31 32 30 66 64 37 36 64 5f 4f 72 61 63 6c 65 44 61 74 61 62 61 73 65 4d
65 74 61 44 61
col 1; len 4; (4): c3 04 4c 1a
tl: 8 fb: --H-FL-- lb: 0x0 cc: 1
col 0: [ 4] 00 41 54 a2
上面標紅的部分是我們看到了和Heap Index的差異,正式由于這部分信息的差異,才讓IOT Secondary Index體積略大。
從概念上,Secondary Index包括三部分葉子節點內容:索引鍵值、logical rowid和對應數據行的主鍵值。在進行檢索的時候,Oracle首先用logical rowid進行初步的試探,看看對應的位置是否可以找到對應數據。這個過程我們稱為physical guess。
如果找到了對應數據行,那么皆大歡喜。如果沒有,oracle就只能通過數據行的主鍵值,進行IOT索引樹定位。這個過程,要重復多讀一個段結構。
具體Secondary Index的分析,留待下次進行更加詳細的說明。
7、IOT的使用
我們已經在一個系列中,詳細介紹了IOT的特性,最后我們聊一聊IOT應用的場景。總的來說,筆者認為IOT在一般系統的應用中,是沒有很廣泛的發揮場景的。在沒有明確的分析和POC實驗基礎上,我們不要輕易進行IOT決策。具體來說,有如下的幾點:
ü IOT環境下,有更多的限制
我們常使用的堆表,雖然有各種問題,但是是目前我們可以得到的適應性最廣,優化策略最多的一種表存儲結構。IOT則要受到很多的限制,例如:IOT必須要制定主鍵,也就是定義出核心訪問方式;PCTThreshold參數如果設置了,但是沒有指定overflew segment,那么超出閾值的數據行是不會被接受,要拋出異常。IOT表中分區和Lob類型不能同時使用。IOT維護工作要更多。
ü 單一讀取,讀多寫少的操作類型
我們定義出IOT后,實際上就是規定了數據表的核心訪問方式。當我們使用主鍵條件時,IOT可以方便的幫助我們定位記錄。但是其他查詢條件應用secondary index的效率就是一個問題。而且secondary index也是不久前才支持的Oracle特性。如果我們的數據表應用是一個多種檢索方式并存的操作表,那么IOT不是理想的選擇。
索引操作本身對增加、修改和刪除等DML操作是具有性能影響的。在IOT環境下,這種影響只會讓其更加劇烈。所以,如果數據表不是很少修改的數據表,那么使用IOT不是最好的選擇。
ü 主鍵列和列數目的約束
索引葉子節點中就能將所有數據行列保存在葉子節點上。而索引葉子節點是變化分裂頻繁的對象。所以,如果數據行列數很多,或者數據主鍵列相對很小,那么IOT的效果是不好的。
8、結論
Heap,IOT和Cluster是數據表的三大基本存儲類型。我們在實際中,要把握原則是:以堆表為核心,默認都使用Heap Table。如果在架構分析、性能測試和試運行階段,發現性能問題,可以考慮使用IOT或者Cluster。但是,在選型的時候,一定要明確兩種表結構的優缺點和適用范圍。
注:適用于基于主鍵訪問表,可考慮替換為iot
新聞熱點
疑難解答