Oracle-HWM(High Water Mark) 高水位解读
讀前須知:Oracle的邏輯存儲管理
ORACLE在邏輯存儲上分4個粒度 ,由大到小為: 表空間, 段, 區 和 塊.
塊Block
塊:是粒度最小的存儲單位,現在標準的塊大小是8K,ORACLE每一次I/O操作也是按塊來操作的,也就是說當ORACLE從數據文件讀數據時,是讀取多少個塊,而不是多少行. 每一個Block里可以包含多個row.
數據塊的大小是通過kb字節個數來指定的,默認為8KB。相關參數為db_block_size
SQL> show parameter db_block_sizeNAME TYPE VALUE ----------------------- --------------------- --------- db_block_size integer 8192區Extent
由一系列相鄰的塊而組成,這也是ORACLE空間分配的基本單位.
區extent是比數據塊大一級的存儲結構,表示的是一連串連續的數據塊集合。
在進行存儲數據信息的時候,Oracle將分配數據塊進行存儲,但是不能保證所有分配的數據塊都是連續的結構。
所以,出現分區extent的概念,表示一系列連續的數據塊集合。
舉個例子來說,當我們創建一個表時,首先ORACLE會分配一區的空間給這個表,隨著數據不斷地增長,原來的這個區容不下插入的數據時,ORACLE是以區為單位進行擴展的,也就是說再分配多少個區給這個表,而不是多少個塊.
視圖dba_extents(或者all_extents、user_extents)是我們研究分區結構和存儲構成的重要手段。
段Segment
段: 是由一系列的區extent所組成
數據段是與數據庫對象相對應,一般一個數據庫對象對應一個數據段。
多個extent是對應一個數據段,每個數據段實際上就是數據庫一個對象的代表。
一般來說, 當創建一個對象時(表,索引),就會分配一個段給這個對象.
從dba_segments、user_segments視圖中,可以比較清楚看清數據段的結構。
表空間Tablespace
TableSpace是存儲結構中的最高層結構。建立一個表空間的時候,是需要指定存儲的文件。一個表空間可以指定多個數據文件,多個文件可以在不同的物理存儲上。也就是說,表空間是可以跨物理存儲的。
但是有一點就是,表空間下一級對象數據段的存儲,是不能指定存儲在那個文件里的。所以,要想讓數據對象訪問IO負載均衡,需要指定不同的數據對象在不同的表空間里。這也就是為什么將數據表和索引建立在不同的表空間的原因。
表空間通過v$tablespace進行訪問
其中兩個參數需要注意一下。
一個是bigfile,是一個標志位,標志表空間是不是所謂的大文件表空間。
大文件表空間是在10g中推出的一個新特性,處于性能考慮,可以設置表空間為大文件表空間,存儲超過百T的數據,但是要求數據文件只能有一個。
另一個是flashback_on,表示表空間的閃回特性是否開啟。
還有 dba_tablespaces 、 user_tablespaces。
Oracle表段中的高水位線HWM
在數據庫表剛建立的時候,由于沒有任何數據,所以這個時候水位線是空的,也就是說HWM為最低值。
當插入了數據以后,高水位線就會上漲,但如果你采用delete語句刪除數據的話,數據雖然被刪除了,但是高水位線卻沒有降低,還是你剛才刪除數據以前那么高的水位。
也就是說,這條高水位線在日常的增刪操作中只會上漲,不會下跌。
HWM通常增長的幅度為一次5個數據塊.
Select語句會對表中的數據進行一次掃描,但是究竟掃描多少數據存儲塊呢,這個并不是說數據庫中有多少數據,Oracle就掃描這么大的數據塊,而是Oracle會掃描高水位線以下的數據塊。
試想一下,新建的一個空表,Select一下,由于高水位線HWM在最低的0位置上,所以沒有數據塊需要被掃描,掃描時間會極短。
如果這個時候你首先插入了一千萬條數據,然后再用delete語句刪除這一千萬條數據。由于插入了一千萬條數據,所以這個時候的高水位線就在一千萬條數據這里。后來刪除這一千萬條數據的時候,由于delete語句不影響高水位線,所以高水位線依然在一千萬條數據這里。
這個時候再一次用select語句進行掃描,雖然這個時候表中沒有數據,但是由于掃描是按照高水位線來的,所以需要把一千萬條數據的存儲空間都要掃描一次,也就是說這次掃描所需要的時間和掃描一千萬條數據所需要的時間是一樣多的。所以有時候有人總是經常說,怎么我的表中沒有幾條數據,但是還是這么慢呢,這個時候其實奧秘就是這里的高水位線了。
那有沒有辦法讓高水位線下降呢 ? 采用TRUNCATE語句刪除一個表的數據的時候,類似于重新建立了表,不僅把數據都刪除了,還把HWM給清空恢復為0。
所以如果需要把表清空,在有可能利用TRUNCATE語句來刪除數據的時候就利用TRUNCATE語句來刪除表,特別是那種數據量有可能很大的臨時存儲表。
在手動段空間管理(Manual Segment Space Management)中,段中只有一個HWM,
但是在Oracle 9i Release1才添加的自動段空間管理(Automatic Segment Space Management)中,又有了一個低HWM的概念出來。
為什么有了HWM還又有一個低HWM呢,這個是因為自動段空間管理的特性造成的。在手段段空間管理中,當數據插入以后,如果是插入到新的數據塊中,數據塊就會被自動格式化等待數據訪問。
而在自動段空間管理中,數據插入到新的數據塊以后,數據塊并沒有被格式化,而是在第一次訪問這個數據塊的時候才格式化這個塊。
所以我們又需要一條水位線,用來標示已經被格式化的塊。這條水位線就叫做低HWM。一般來說,低HWM肯定是低于等于HWM的。
降低ORACLE表的高水位線
在ORACLE中,執行對表的刪除操作不會降低該表的高水位線。而全表掃描將始終讀取一個段(extent)中所有低于高水位線標記的塊。如果在執行刪除操作后不降低高水位線標記,則將導致查詢語句的性能低下。
rebuild, truncate, shrink,move 等操作會降低高水位。
執行表重建指令 alter table table_name move
在線轉移表空間ALTER TABLE … MOVE TABLESPACE ..
當你創建了一個對象如表以后,不管你有沒有插入數據,它都會占用一些塊,ORACLE也會給它分配必要的空間.
同樣,用ALTER TABLE MOVE釋放自由空間后,還是保留了一些空間給這個表.
ALTER TABLE … MOVE 后面不跟參數也行,不跟參數表還是在原來的表空間,Move后記住重建索引.
如果以后還要繼續向這個表增加數據,沒有必要move, 只是釋放出來的空間,只能這個表用,其他的表或者segment無法使用該空間。
執行alter table table_name shrink space-10g新功能
此命令為Oracle 10g新增功能,再執行該指令之前必須允許行移動 alter table table_name enable row movement;
如果要同時壓縮表的索引,可以發布:alter table test_tab shrink space cascade
重建表
復制要保留的數據到臨時表t,drop原表,然后rename臨時表t為原表
用邏輯導入導出: Emp/Imp
Alter table table_name deallocate unused
DEALLOCATE UNUSED會釋放HWM上面的未使用空間,但是并不會釋放HWM下面的自由空間,也不會移動HWM的位置.
truncate(推薦使用)
truncate table xxx
HWM的特征
ORACLE用HWM來界定一個段中使用的塊和未使用的塊
當我們創建一個表時,ORACLE就會為這個對象分配一個段.在這個段中,即使我們未插入任何記錄,也至少有一個區被分配,第一個區的第一個塊就稱為段頭(SEGMENT HEADE),段頭中就儲存了一些信息,HWM的信息就存儲在此.
我們不斷插入數據時,HWM會往不斷上移,這樣,在HWM之下的,就表示使用過的塊,HWM之上的就表示已分配但從未使用過的塊.
HWM在插入數據時,當現有空間不足而進行空間的擴展時會向上移,但刪除數據時不會往下移.
ORACLE 不會釋放空間以供其他對象使用,有一條簡單的理由:由于空間是為新插入的行保留的,并且要適應現有行的增長。被占用的最高空間稱為最高使用標記 (HWM).
HWM的信息存儲在段頭當中.
HWM本身的信息是儲存在段頭.
在段空間是手工管理方式時,ORACLE是通過FREELIST(一個單向鏈表)來管理段內的空間分配.
在段空間是自動管理方式時(ASSM),ORACLE是通過BITMAP來管理段內的空間分配.
ORACLE的全表掃描是讀取高水位標記(HWM)以下的所有塊.
所以問題就產生了.當用戶發出一個全表掃描時,ORACLE 始終必須從段一直掃描到 HWM,即使它什么也沒有發現。
該任務延長了全表掃描的時間。
當用直接路徑插入行時,即使HWM以下有空閑的數據庫塊,鍵入在插入數據時使用了append關鍵字,則在插入時使用HWM以上的數據塊,此時HWM會自動增大。
例如,通過直接加載插入(用 APPEND 提示插入)或通過 SQL*LOADER 直接路徑 數據塊直接置于 HWM 之上。它下面的空間就浪費掉了。
栗子
數據庫版本 Connected to Oracle Database 11g Enterprise Edition Release 11.2.0.4.0
創建測試表
SQL> create table tt(id number);Table created此時表沒有分析,是原始的數據,即8個數據塊。
--空的 SQL> SELECT segment_name,segment_type,blocks FROM dba_segments a WHERE a.segment_name = 'TT';SEGMENT_NAME SEGMENT_TYPE BLOCKS -------------------------------------------------------------------------------- ------------------ ------------空的 SQL> SELECT table_name,num_rows,blocks,empty_blocks FROM user_tables WHERE table_name='TT';TABLE_NAME NUM_ROWS BLOCKS EMPTY_BLOCKS ------------------------------ ---------- ---------- ------------ TT向表中插入一些測試數據
SQL> declare2 i number;3 begin4 for i in 1 .. 10000 loop5 insert into tt values (i);6 end loop;7 commit;8 end;9 /PL/SQL procedure successfully completed重新查詢表的信息
此時表TT 占用的塊已經是24個了.
但是user_tables 顯示的信息還是為空。 因為沒有做統計分析。
收集統計信息
SQL> exec DBMS_STATS.GATHER_TABLE_STATS(user,'TT');PL/SQL procedure successfully completed再此查詢一下
此時user_tables 已經有了數據,顯示的使用了20個數據塊。 但是empty_blocks 還是為空。 這里要注意的地方。 empty_blocks 這個字段只有使用analyze 收集統計信息之后才會有數據。
使用analyze搜集統計信息
SQL> analyze table tt compute statistics;Table analyzeddelete 數據,不會降低高水位
可以發現 分析前后,blocks 和 empty_blocks 都沒有發生變化。
truncate 表,可以降低高水位
--truncate 表 SQL> truncate table tt;Table truncated--查詢段信息,blocks由24降到了8 SQL> SELECT segment_name,segment_type,blocks FROM dba_segments WHERE segment_name='TT';SEGMENT_NAME SEGMENT_TYPE BLOCKS --------- ---------- ---------- TT TABLE 8--查詢表信息,沒有改變 SQL> SELECT table_name,num_rows,blocks,empty_blocks FROM user_tables WHERE table_name='TT';TABLE_NAME NUM_ROWS BLOCKS EMPTY_BLOCKS -------- ---------- ------ ------ TT 0 20 4------------------------------------------------------------ ------------------------------------------------------------ --收集下表信息 SQL> exec dbms_stats.gather_table_stats(user,'TT');PL/SQL procedure successfully completed--重新統計下段信息,一樣 SQL> SELECT segment_name,segment_type,blocks FROM dba_segments WHERE segment_name='TT';SEGMENT_NAME SEGMENT_TYPE BLOCKS --------- ---------- ---------- TT TABLE 8--重新查詢表信息 BLOCKS 由20降為0, 但是empyt_blocks 還是4個 SQL> SELECT table_name,num_rows,blocks,empty_blocks FROM user_tables WHERE table_name='TT';TABLE_NAME NUM_ROWS BLOCKS EMPTY_BLOCKS ---------- ----- ---------- ------------ TT 0 0 4--analyze 分析下 更改EMPTY_BLOCKS的值 SQL> analyze table tt compute statistics;Table analyzed--重新查詢段信息, SQL> SELECT segment_name,segment_type,blocks FROM dba_segments WHERE segment_name='TT';SEGMENT_NAME SEGMENT_TYPE BLOCKS --------- ---------- ---------- TT TABLE 8--重新查詢表信息 SQL> SELECT table_name,num_rows,blocks,empty_blocks FROM user_tables WHERE table_name='TT';TABLE_NAME NUM_ROWS BLOCKS EMPTY_BLOCKS ---------- ----- ---------- ------------ TT 0 0 8SQL>總共8個數據塊,8個為空
Alter table move 和Shrink的區別
Shrink
在10g之后,整理碎片消除行遷移的新增功能shrink space
語法
alter table <table_name> shrink space [ <null> | compact | cascade ];- compact: 這個參數當系統的負載比較大時可以用,不降低HWM。如果系統負載較低時,直接用alter table table_name shrink space就一步到位了
- cascade:這個參數是在shrink table的時候自動級聯索引,相當于rebulid index。
基于普通表
--shrink必須開啟行遷移功能。 alter table table_name enable row movement ;--保持HWM,相當于把塊中數據打結實了 alter table table_name shrink space compact;--回縮表與降低HWM alter table table_name shrink space;--回縮表與相關索引,降低HWM alter table table_name shrink space cascade;--回縮索引與降低HWM alter index index_name shrink space雖然在10g中可以用shrink ,但也有些限制:
- 1). 對cluster,cluster table,或具有Long,lob類型列的對象 不起作用。
- 2). 不支持具有function-based indexes 或 bitmap join indexes的表
- 3). 不支持mapping 表或index-organized表。
- 4). 不支持compressed 表
shrink栗子
SQL> create table tt(id number);SQL> declarei number; beginfor i in 1 .. 10000 loopinsert into tt values (i);end loop;commit; end;SQL> exec DBMS_STATS.GATHER_TABLE_STATS(user,'TT');SQL> analyze table tt compute statistics;SQL> delete from tt;SQL> commit ;SQL> alter table tt enable row movement ; SQL> alter table tt shrink space; SQL> analyze table tt compute statistics;SQL> SELECT segment_name,segment_type,blocks FROM dba_segments WHERE segment_name='TT';SQL> SELECT table_name,num_rows,blocks,empty_blocks FROM user_tables WHERE table_name='TT';Move
通過desc table_name 來檢查表中是否有LOB 字段,
表中沒有lob字段
如果表沒有LOB字段, 直接 alter table move; 然后 rebuild index
表中包含了LOB字段
alter table owner.table_name move tablespace tablespace_name lob (lob_column) store as lobsegment tablespace tablespace_name;也可以單獨move lob,但是表上的index 同樣會失效. 所以在操作結束,需要對索引進行rebuild。
alter table owner.table_name move lob(lob_column) store as lobsegment tablespace tablespace_name ;索引的rebuild:
首先用下面的SQL查看表上面有哪類索引:
SELECT a.owner,a.index_name,a.index_type,a.partitioned,a.status,b.status p_status,b.compositeFROM dba_indexes aLEFT JOINdba_ind_partitions bON a.owner = b.index_owner AND a.index_name = b.index_nameWHERE a.owner = '&owner' AND a.table_name = '&table_name';對于普通索引直接rebuild online nologging parallel,
對于分區索引,必須單獨rebuild 每個分區,
對于組合分區索引,必須單獨rebuild 每個子分區。
總結
Move 通過移動數據來來降低HWM,因此需要更多的磁盤空間。
Shrink 通過delete 和 insert, 會產生較多的undo 和redo。
shrink space收縮到數據存儲的最小值,alter table move(不帶參數)收縮到initial指定值,也可以用alter table test move storage(initial 500k)指定收縮的大小,這樣可以達到shrink space效果。
總之,使用Move 效率會高點,但是會導致索引失效。Shrink 會產生undo 和redo,速度相對也慢一點。
博文學習至David Dai 大神,再此向大神致敬!
總結
以上是生活随笔為你收集整理的Oracle-HWM(High Water Mark) 高水位解读的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Shell脚本攻略03-使用shell进
- 下一篇: Oracle优化02-锁和阻塞