Oracle高水位線(HWM)及效能優化

hcj40141發表於2014-05-28

說到HWM,我們首先要簡要的談談ORACLE的邏輯儲存管理.我們知道,ORACLE在邏輯儲存上分4個粒度:表空間,段,區和塊.

(1):是粒度最小的儲存單位,現在標準的塊大小是8K,ORACLE每一次I/O操作也是按塊來操作的,也就是說當ORACLE從資料檔案讀資料時,是讀取多少個塊,而不是多少行.

(2):由一系列相鄰的塊而組成,這也是ORACLE空間分配的基本單位,舉個例子來說,當我們建立一個表PM_USER時,首先ORACLE會分配一區的空間給這個表,隨著不斷的INSERT資料到PM_USER,原來的這個區容不下插入的資料時,ORACLE是以區為單位進行擴充套件的,也就是說再分配多少個區給PM_USER,而不是多少個塊.

(3):是由一系列的區所組成,一般來說,當建立一個物件時(表,索引),就會分配一個段給這個物件.所以從某種意義上來說,段就是某種特定的資料.如CREATE TABLE PM_USER,這個段就是資料段,而CREATE INDEX ON PM_USER(NAME),ORACLE同樣會分配一個段給這個索引,但這是一個索引段了.查詢段的資訊可以通過資料字典: SELECT * FROM USER_SEGMENTS來獲得,

(4)表空間:包含段,區及塊.表空間的資料物理上儲存在其所在的資料檔案中.一個資料庫至少要有一個表空間.

OK,我們現在回到HWM上來,那麼,什麼是高水位標記呢?這就跟ORACLE的段空間管理相關了.

(一)ORACLE用HWM來界定一個段中使用的塊和未使用的塊.

舉個例子來說,當我們建立一個表:PT_SCHE_DETAIL時,ORACLE就會為這個物件分配一個段.在這個段中,即使我們未插入任何記錄,也至少有一個區被分配,第一個區的第一個塊就稱為段頭(SEGMENT HEAD),段頭中就儲存了一些資訊,基中HWM的資訊就儲存在此.此時,因為第一個區的第一塊用於儲存段頭的一些資訊,雖然沒有儲存任何實際的記錄,但也算是被使用,此時HWM是位於第2個塊.當我們不斷插入資料到PM_USER後,第1個塊已經放不下後面新插入的資料,此時,ORACLE將高水位之上的塊用於儲存新增資料,同時,HWM本身也向上移.也就是說,當我們不斷插入資料時,HWM會往不斷上移,這樣,在HWM之下的,就表示使用過的塊,HWM之上的就表示已分配但從未使用過的塊.

(二)HWM在插入資料時,當現有空間不足而進行空間的擴充套件時會向上移,但刪除資料時不會往下移.

這就好比是水庫的水位,當漲水時,水位往上移,當水退出後,最高水位的痕跡還是清淅可見.
考慮讓我們看一個段,如一張表,其中填滿了塊,如圖 1 所示。在正常操作過程中,刪除了一些行,如圖 2 所示。現有就有了許多浪費的空間:(I) 在表的上一個末端和現有的塊之間,以及 (II) 在塊內部,其中還有一些沒有刪除的行。

HWM01
圖1:分配給該表的塊。用灰色正方形表示行

ORACLE 不會釋放空間以供其他物件使用,有一條簡單的理由:由於空間是為新插入的行保留的,並且要適應現有行的增長。被佔用的最高空間稱為最高使用標記 (HWM),如圖 2 所示。

HWM02
圖2:行後面的塊已經刪除了;HWM 仍保持不變

(三)HWM的資訊儲存在段頭當中.

HWM本身的資訊是儲存在段頭.在段空間是手工管理方式時,ORACLE是通過FREELIST(一個單向連結串列)來管理段內的空間分配.在段空間是自動管理方式時(ASSM),ORACLE是通過BITMAP來管理段內的空間分配.

(四)ORACLE的全表掃描是讀取高水位標記(HWM)以下的所有塊.

所以問題就產生了.當使用者發出一個全表掃描時,ORACLE 始終必須從段一直掃描到 HWM,即使它什麼也沒有發現。該任務延長了全表掃描的時間。

(五)當用直接路徑插入行時 — 例如,通過直接載入插入(用 APPEND 提示插入)或通過 SQL*LOADER 直接路徑 — 資料塊直接置於 HWM 之上。它下面的空間就浪費掉了。

我們來分析這兩個問題,後者只是帶來空間的浪費,但前者不僅是空間的浪費,而且會帶來嚴重的效能問題.我們來看看下面的例子:

(A)我們先來搭建測試的環境,第一步先建立一個段空間為手工管理的表空間:

CREATE TABLESPACE "RAINNY" 
LOGGING 
DATAFILE 'D:ORACLE_HOMEORADATARAINNYRAINNY.ORA' SIZE 5M 
AUTOEXTEND 
ON NEXT 10M MAXSIZE UNLIMITED EXTENT MANAGEMENT LOCAL 
SEGMENT SPACE MANAGEMENT MANUAL;

(B)建立一個表,注意,此表的第二個欄位我故意設成是CHAR(100),以讓此表在插入1千萬條記錄後,空間有足夠大:

CREATE TABLE TEST_TAB(C1 NUMBER(10),C2 CHAR(100)) TABLESPACE RAINNY;

插入記錄

DECLARE
    I NUMBER(10);
BEGIN
    FOR I IN 1..10000000 LOOP
        INSERT INTO TEST_TAB VALUES(I,'TESTSTRING');
    END LOOP;
    COMMIT;
END;
/

©我們來查詢一下,看在插入一千萬條記錄後所訪問的塊數和查詢所用時間:

SQL> SET TIMING ON
SQL> SET AUTOTRACE TRACEONLY
SQL> SELECT COUNT(*) FROM TEST_TAB;
 
ELAPSED: 00:01:03.05
 
EXECUTION PLAN
------------------------------------------------------------
0 SELECT STATEMENT OPTIMIZER=CHOOSE (COST=15056 CARD=1)
1 0 SORT (AGGREGATE)
2 1 TABLE ACCESS (FULL) OF 'TEST_TAB' (COST=15056 CARD=10000
000)
 
STATISTICS
----------------------------------------------------------
0      RECURSIVE CALLS
0      DB BLOCK GETS
156310 CONSISTENT GETS
154239 PHYSICAL READS
0      REDO SIZE
379    BYTES SENT VIA SQL*NET TO CLIENT
503    BYTES RECEIVED VIA SQL*NET FROM CLIENT
2      SQL*NET ROUNDTRIPS TO/FROM CLIENT
0      SORTS (MEMORY)
0      SORTS (DISK)
1      ROWS PROCESSED

我們來看上面的執行計劃,這句SQL總供耗時是:1分3秒.訪問方式是採用全表掃描方式(FTS),邏輯讀了156310個BLOCK,物理讀了154239個BLOCK.
我們來分析一下這個表:

BEGIN
DBMS_STATS.GATHER_TABLE_STATS(OWNNAME=> 'TEST', 
TABNAME=> 'TEST_TAB',
PARTNAME=> NULL);END;
/

發現這個表目前使用的BLOCK有: 156532,未使用的BLOCK(EMPTY_BLOCKS)為:0,總行數為(NUM_ROWS):1000 0000

(D)接下來我們把此表的記錄用DELETE方式刪掉,然後再來看看SELECT COUNT(*) FROM TEST_TAB所花的時間:

DELETE FROM TEST_TAB; 
COMMIT;
 
SQL> SELECT COUNT(*) FROM TEST_TAB; 
 
ELAPSED: 00:01:04.03 
 
EXECUTION PLAN 
---------------------------------------------------------- 
0 SELECT STATEMENT OPTIMIZER=CHOOSE (COST=15056 CARD=1)
1 0 SORT (AGGREGATE) 
2 1 TABLE ACCESS (FULL) OF 'TEST_TAB' (COST=15056 CARD=1)
 
STATISTICS 
---------------------------------------------------------- 
0      RECURSIVE CALLS 
0      DB BLOCK GETS 
156310 CONSISTENT GETS 
155565 PHYSICAL READS 
0      REDO SIZE 
378    BYTES SENT VIA SQL*NET TO CLIENT 
503    BYTES RECEIVED VIA SQL*NET FROM CLIENT 
2      SQL*NET ROUNDTRIPS TO/FROM CLIENT 
0      SORTS (MEMORY) 
0      SORTS (DISK) 
1      ROWS PROCESSED

大家來看,在DELETE表後,此時表中已沒有一條記錄,為什麼SELECT COUNT(*) FROM TEST_TAB花的時間為1分4秒, 反而比有記錄稍微長點,這是為什麼呢?而且大家看,其邏輯讀了156310個 BLOCK,跟之前有一千萬行記錄時差不多,ORACLE怎麼會這麼笨啊?

我們在DELETE表後再次分析表,看看有什麼變化:
這時, TEST_TAB表目前使用的BLOCK是: 156532,未使用的BLOCK(EMPTY_BLOCKS)為:0,總行數為(NUM_ROWS)已變成:0
為什麼表目前使的BLOCK數還是156532呢?

問題的根源就在於ORACLE的HWM.也就是說,在新增記錄時,HWM會慢慢往上移,但是在刪除記錄後,HWM卻不會往下移,也就是說,DELETE一千萬條記錄後,此表的HWM根本沒移動,還在原來的那個位置,所以,HWM以下的塊數同樣也是一樣的.ORACLE的全表掃描是讀取ORACLE高水位標記下的所有BLOCK,也就是說,不管HWM下的BLOCK現在實際有沒有存放資料,ORACLE都會一一讀取,這樣,大家可想而知,在我們DELETE表後,ORACLE讀了大量的空塊,耗去了大量的時間.

我們再來看DELETE表後段空間實際使用的狀況:

SQL> EXEC SHOW_SPACE('TEST_TAB','TEST');
TOTAL BLOCKS............................164352 --總共164352塊
TOTAL BYTES.............................1346371584
UNUSED BLOCKS...........................7168 --有7168塊沒有用過,也就是在HWM上面的塊數
UNUSED BYTES............................58720256
LAST USED EXT FILEID....................9
LAST USED EXT BLOCKID...................158856-- BLOCK ID 是針對資料檔案來編號的,表示最後使用的一個EXTENT的第一個BLOCK的編號
LAST USED BLOCK.........................1024 --在最後使用的一個EXTENT 中一共用了1024塊
 
PL/SQL PROCEDURE SUCCESSFULLY COMPLETED

總共用了164352塊,除了一個SEGMENT HEADER,實際總共用了164351個塊,有7168塊從來沒有使用過。LAST USED BLOCK表示在最後一個使用的EXTENT 中使用的BLOCK, 結合LAST USED EXT BLOCK ID可以計算HWM 位置 :

LAST USED EXT BLOCK ID + LAST USED BLOCK -1 = HWM 所在的資料檔案的BLOCK編號

代入得出: 158856+1024-1=159879,這個就是HWM所有的BLOCK編號
HWM所在的塊:TOTAL BLOCKS- UNUSED BLOCKS=164352-7168=157184,也就是說,HWM在第157184個塊,其BLOCKID是159879

(E)結下來,我們再做幾個試驗:

第一步:執行ALTER TABLE TEST_TAB DEALLOCATE UNUSED;
我們看看段空間的使用狀況:

SQL> EXEC SHOW_SPACE('TEST_TAB','TEST');
 
TOTAL BLOCKS............................157184
TOTAL BYTES.............................1287651328
UNUSED BLOCKS...........................0
UNUSED BYTES............................0
LAST USED EXT FILEID....................9
LAST USED EXT BLOCKID...................158856
LAST USED BLOCK.........................1024

此時我們再代入上面的公式,算出HWM的位置: 157184-0=157184 HWM所在的BLOCK ID158856+1024-1=159879,跟剛剛的沒有變化,也就是說執行ALTER TABLE TEST_TAB DEALLOCATE UNUSED後,段的高水位標記的位置沒有改變,但是大家看看UNUSED BLOCKS變為0了,總的塊數減少到157184,這證明,DEALLOCATE UNUSED為釋放HWM上面的未使用空間,但是並不會釋放HWM下面的自由空間,也不會移動HWM的位置.

第二步:我們再來看看執行ALTER TABLE TEST_TAB MOVE後段空間的使用狀況:

SQL> EXEC SHOW_SPACE('TEST_TAB','TEST');
 
TOTAL BLOCKS............................8
TOTAL BYTES.............................65536
UNUSED BLOCKS...........................5
UNUSED BYTES............................40960
LAST USED EXT FILEID....................9
LAST USED EXT BLOCKID...................2632
LAST USED BLOCK.........................3

此時,總共用到的塊數已變為8, 我們再代入上面的公式,算出HWM的位置: 8-5=3 HWM所在的BLOCK ID是2632+3-1=2634,

OK,我們發現,此時HWM的位置已經發生變化,現在HWM的位置是在第3個BLOCK,其BLOCK ID是2634,所有資料檔案的ID是9(這個沒有發生變化,資料檔案還是原來的那個資料檔案,只是釋放了原來的自由空間),最後使用的塊數也變為3,也就是說已經使用了3塊,HWM就是在最後一個使用的塊上,即第3個塊上.大家可能會覺得奇怪,為什麼釋放空間後,未使用的塊還有5個啊?也就是說HWM之上還是有5個已分配但從未使用的塊.答案就跟HWM移動的規律有關.當我們在插入資料時,ORACLE首先在HWM之下的塊當中定位自由空間(通過自由列表FREELIST),如果FREELIST當中沒有自由塊了,ORACLE就開始往上擴充套件,而HWM也跟著往上移,每5塊移動一次.我們來看ORACLE的說明:

The high water mark is: 
-Recorded in the segment header block 
-Set to the beginning of the segment on the creation 
-Incremented in five-block increments as rows are inserted 
-Reset by the truncate command 
-Never reset by the delete command 
-Space above the high-water-mark can be reclaimed at the table level by using the following command: 
ALTER TABLE DEALLOCATE UNUSED… 

我們再來看看:SELECT COUNT(*) FROM TEST_TAB所花的時間:

SQL> SELECT COUNT(*) FROM TEST_TAB; 
 
ELAPSED: 00:00:00.00 
EXECUTION PLAN 
----------------------------------------------------------
0 SELECT STATEMENT OPTIMIZER=CHOOSE 
1 0 SORT (AGGREGATE) 
2 1 TABLE ACCESS (FULL) OF 'TEST_TAB'
 
STATISTICS 
---------------------------------------------------------- 
0     RECURSIVE CALLS 
0     DB BLOCK GETS
3     CONSISTENT GETS 
0     PHYSICAL READS 
0     REDO SIZE 
378   BYTES SENT VIA SQL*NET TO CLIENT 
503   BYTES RECEIVED VIA SQL*NET FROM CLIENT 
2     SQL*NET ROUNDTRIPS TO/FROM CLIENT 
0     SORTS (MEMORY) 
0     SORTS (DISK) 
1     ROWS PROCESSED 

很快,不到1秒.

我們最後再來對錶作一次分析, 此時這個表目前使用的BLOCK為: 0,未使用的BLOCK(EMPTY_BLOCKS)為:0,總行數為(NUM_ROWS):0
從中我們也可以發現,分析表和SHOW_SPACE顯示的資料有點不一致.那麼哪個是準的呢?其實這兩個都是準的,只不過計算的方法有點不同.事實上,當你建立了一個物件如表以後,不管你有沒有插入資料,它都會佔用一些塊,ORACLE也會給它分配必要的空間.同樣,用ALTER TABLE MOVE釋放自由空間後,還是保留了一些空間給這個表.
最後,我們再來執行TRUNCATE命令,截斷這個表,看看段空間的使用狀況:

TRUNCATE TABLE TEST_TAB; 
 
SQL> EXEC SHOW_SPACE('TEST_TAB','TEST'); 
 
TOTAL BLOCKS............................8 
TOTAL BYTES.............................65536 
UNUSED BLOCKS...........................5 
UNUSED BYTES............................40960 
LAST USED EXT FILEID....................9 
LAST USED EXT BLOCKID...................2632 
LAST USED BLOCK.........................3 
 
PL/SQL PROCEDURE SUCCESSFULLY COMPLETED 
 
SQL> 

我們發現TRUNCATE後和MOVE沒有什麼變化.

為了,最終驗證一下我上面的觀點,我再DROP一下表,然後新建這個表,看看這時在沒有插入任何資料之前,是否ORACLE確實有給這個物件分配必要的空間:

DROP TABLE TEST_TAB; 
 
CREATE TABLE TEST_TAB(C1 NUMBER(10),C2 CHAR(100)) TABLESPACE RAINNY; 
 
SQL> EXEC SHOW_SPACE('TEST_TAB','TEST'); 
TOTAL BLOCKS............................8 
TOTAL BYTES.............................65536 
UNUSED BLOCKS...........................5 
UNUSED BYTES............................40960 
LAST USED EXT FILEID....................9 
LAST USED EXT BLOCKID...................2112 
LAST USED BLOCK.........................3

大家看,即使我沒有插入任何一行記錄,ORACLE還是給它分配了8個塊.當然這個跟建表語句的INITIAL 引數及MINEXTENTS引數有關:請看TEST_TAB的儲存引數:

S TORAGE
(
INITIAL 64K
MINEXTENTS 1
MAXEXTENTS UNLIMITED
); 

也就是說,在這個物件建立以後,ORACLE至少給它分配一個區,初始大小是64K,一個標準塊的大小是8K,剛好是8個BLOCK.

總結:

在9I中:

(1)如果MINEXTENT 可以使ALTER TABLE TABLENAME DEALLOCATE UNUSEDHWM以上所有沒使用的空間釋放
(2)如果MINEXTENT >HWM 則釋放MINEXTENTS 以上的空間。如果要釋放HWM以上的空間則使用KEEP 0

ALTER TABLE TABLESNAME DEALLOCATE UNUSED KEEP 0;

(3) TRUNCATE TABLE DROP STORAGE(預設值)命令可以將MINEXTENT 之上的空間完全釋放(交還給作業系統),並且重置HWM
(4)如果僅是要移動HWM,而不想讓表長時間鎖住,可以用TRUNCATE TABLE REUSE STORAGE,僅將HWM重置。
(5)ALTER TABLE MOVE會將HWM移動,但在MOVE時需要雙倍的表空間,而且如果表上有索引的話,需要重構索引
(6)DELETE表不會重置HWM,也不會釋放自由的空間(也就是說DELETE空出來的空間只能給物件本身將來的INSERT/UPDATE使用,不能給其它的物件使用)

ORACLE 10G:

可以使用ALTER TABLE TEST_TAB SHRINK SPACE命令來聯機移動HWM,
如果要同時壓縮表的索引,可以釋出:ALTER TABLE TEST_TAB SHRINK SPACE CASCADE
注意:在使用此命令時需要先使行可遷移row movement(具體見例子)。
與使用ALTER TABLE MOVE 不同的是執行此命令後並不需要重構索引。

Oracle 官方說明

Shrinking Database Segments Online
You use online segment shrink to reclaim fragmented free space below the high water mark in an Oracle Database segment. The benefits of segment shrink are these:
* Compaction of data leads to better cache utilization, which in turn leads to better online transaction processing (OLTP) performance.
* The compacted data requires fewer blocks to be scanned in full table scans, which in turns leads to better decision support system (DSS) performance.
Segment shrink is an online, in-place operation. DML operations and queries can be issued during the data movement phase of segment shrink. Concurrent DML operation are blocked for a short time at the end of the shrink operation, when the space is deallocated. Indexes are maintained during the shrink operation and remain usable after the operation is complete. Segment shrink does not require extra disk space to be allocated.
Segment shrink reclaims unused space both above and below the high water mark. In contrast, space deallocation reclaims unused space only above the high water mark. In shrink operations, by default, the database compacts the segment, adjusts the high water mark, and releases the reclaimed space.
Segment shrink requires that rows be moved to new locations. Therefore, you must first enable row movement in the object you want to shrink and disable any rowid-based triggers defined on the object.
Shrink operations can be performed only on segments in locally managed tablespaces with automatic segment space management (ASSM). Within an ASSM tablespace, all segment types are eligible for online segment shrink except these:
* IOT mapping tables
* Tables with rowid based materialized views
* Tables with function-based indexes

操作的過程:

SQL> create table demo as select * from dba_source;
Table created.
Elapsed: 00:00:05.83
 
SQL> select count(*) from demo;
 
  COUNT(*)
----------
    210992
Elapsed: 00:00:01.06
 
SQL> insert into demo select * from demo;
210992 rows created.
Elapsed: 00:00:59.83
 
SQL> commit;
Commit complete.
 
//得到一個40萬條記錄的表,下面來檢視這個表空間分佈情況。
 
SQL> exec show_space('demo','auto');
 
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.07
 
SQL> set serveroutput on
 
SQL>  exec show_space('demo','auto');
 
Total Blocks............................9216
Total Bytes.............................75497472
Unused Blocks...........................768
Unused Bytes............................6291456
Last Used Ext FileId....................4
Last Used Ext BlockId...................8328
Last Used Block.........................256

一共有9216個資料塊,HWM在9216-768=8448這個塊.
也可以通過檢視extents得到HWM=8*16+128*63+256=8192+256=8448

 
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.01
 
SQL> delete from demo where rownum<220000;
 
219999 rows deleted.
Elapsed: 00:00:40.99
 
SQL> commit;
 
Commit complete.
Elapsed: 00:00:00.01
 
SQL>  exec show_space('demo','auto');
 
Total Blocks............................9216
Total Bytes.............................75497472
Unused Blocks...........................768
Unused Bytes............................6291456
Last Used Ext FileId....................4
Last Used Ext BlockId...................8328
Last Used Block.........................256
 
PL/SQL procedure successfully completed.
 
//刪除操作後表的HWM沒有變化,還是在第8448塊這個位置。
Elapsed: 00:00:00.00
 
SQL> alter table demo shrink space;
alter table demo shrink space
*
ERROR at line 1:
ORA-10636: ROW MOVEMENT is not enabled
//先要enable row movement才能shrink
Elapsed: 00:00:00.09
 
SQL> alter table demo enable row movement;
 
Table altered.
Elapsed: 00:00:00.10
 
SQL>  alter table demo shrink space;
 
Table altered.
Elapsed: 00:01:35.51
 
SQL>  exec show_space('demo','auto');
 
Total Blocks............................3656
Total Bytes.............................29949952
Unused Blocks...........................0
Unused Bytes............................0
Last Used Ext FileId....................4
Last Used Ext BlockId...................3720
Last Used Block.........................72
 
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.02
//可以看到HWM降到了3656這個塊上面!

相關文章