用Oracle數據庫10g通過回收浪費的空間、聯機重組表格和評估增長的趨勢,有效地在段中進行存儲管理。近來,有人要求我評估一個與 Oracle 數據庫競爭的RDBMS。在供應商的演示過程中,觀眾認為“最棒”的特性是,對聯機重組的支持——該產品可以聯機重新部署數據塊,以使段的等價物更簡潔,並且不會影響當前的用戶。
那時,Oracle還沒有在Oracle 9i數據庫中提供這種功能。但是現在,有了Oracle數據庫10g,就可以輕松地聯機回收浪費的空間和壓縮對象——正好適合於起步者。
不過,在檢驗該特性之前,讓我們看一看處理這項任務的“傳統的”方法。
當前慣例
考慮讓我們看一個段,如一張表,其中填滿了塊,如圖1 所示。在正常操作過程中,刪除了一些行,如圖2 所示。現有就有了許多浪費的空間:(i)在表的上一個末端和現有的塊之間,以及(ii)在塊內部,其中還有一些沒有刪除的行。
圖 1:分配給該表的塊。用灰色正方形表示行
Oracle不會釋放空間以供其他對象使用,有一條簡單的理由:由於空間是為新插入的行保留的,並且要適應現有行的增長。被占用的最高空間稱為最高使用標記(HWM),如圖2所示:
圖 2:行後面的塊已經刪除了;HWM 仍保持不變
但是,這種方法有兩個主要的問題:
當用戶發出一個全表掃描時,Oracle始終必須從段一直掃描到HWM,即使它什麼也沒有發現。該任務延長了全表掃描的時間。
當用直接路徑插入行時 — 例如,通過直接加載插入(用APPEND提示插入)或通過SQL*Loader直接路徑 — 數據塊直接置於HWM之上。它下面的空間就浪費掉了。
在Oracle9i及其以前的版本中,可以通過刪除表,然後重建表並重新加載數據來回收空間;或通過使用ALTER TABLE MOVE命令把表移動到一個不同的表空間中來回收空間。這兩種處理方式都必須脫機進行。另外,可以使用聯機表重組特性,但是這需要至少雙倍的現有表空間。
在10g中,該任務已經變得微不足道了;假如您的表空間中支持自動段空間管理(ASSM),您現在可以縮小段、表和索引,以回收空閒塊並把它們提供給數據庫以作他用,讓我們看看其中的緣由。
10g中的段管理方式
設想有一個表BOOKINGS,它保存有經由Web站點的聯機登記。當一個登記確認後,就會把它存儲在一個存檔表BOOKINGS_HIST中,並從BOOKINGS表中刪除該行。登記和確認之間的時間間隔依據客戶有很大的不同,由於無法從刪除的行獲得足夠的空間,因此許多行就插入到了表的 HWM 之上。
現在您需要回收浪費的空間。首先,准確地查明在可回收的段中浪費了多少空間。由於它是在支持ASSM的表空間中,您將不得不使用DBMS_SPACE包的SPACE_USAGE過程,如下所示:
declare l_fs1_bytes number; l_fs2_bytes number; l_fs3_bytes number; l_fs4_bytes number; l_fs1_blocks number; l_fs2_blocks number; l_fs3_blocks number; l_fs4_blocks number; l_full_bytes number; l_full_blocks number; l_unformatted_bytes number; l_unformatted_blocks number; begin dbms_space.space_usage( segment_owner => user, segment_name => 'BOOKINGS', segment_type => 'TABLE', fs1_bytes => l_fs1_bytes, fs1_blocks => l_fs1_blocks, fs2_bytes => l_fs2_bytes, fs2_blocks => l_fs2_blocks, fs3_bytes => l_fs3_bytes, fs3_blocks => l_fs3_blocks, fs4_bytes => l_fs4_bytes, fs4_blocks => l_fs4_blocks, full_bytes => l_full_bytes, full_blocks=> l_full_blocks, unformatted_blocks => l_unformatted_blocks, unformatted_bytes => l_unformatted_bytes ); dbms_output.put_line(' FS1 Blocks = ' ||l_fs1_blocks||' Bytes = '||l_fs1_bytes); dbms_output.put_line(' FS2 Blocks = ' ||l_fs2_blocks||' Bytes = '||l_fs1_bytes); dbms_output.put_line(' FS3 Blocks = ' ||l_fs3_blocks||' Bytes = '||l_fs1_bytes); dbms_output.put_line(' FS4 Blocks = ' ||l_fs4_blocks||' Bytes = '||l_fs1_bytes); dbms_output.put_line('Full Blocks = ' ||l_full_blocks||' Bytes = ||l_full_bytes); end; /
輸出結果如下:
FS1 Blocks = 0 Bytes = 0 FS2 Blocks = 0 Bytes = 0 FS3 Blocks = 0 Bytes = 0 FS4 Blocks = 4148 Bytes = 0 Full Blocks = 2 Bytes = 16384
這個輸出結果顯示有4,148個塊,具有75-100%的空閒空間(FS4);沒有其他空閒塊可用。這裡僅有兩個得到完全使用的塊。4,148個塊都可以回收。
接下來,您必須確保該表支持行移動。如果不支持,您可以使用如下命令來支持它:
alter table bookings enable row movement;
或通過Administration頁上的企業管理器10g。您還要確保在該表上禁用所有基於行id的觸發器,這是因為行將會移動,行id可能會發生改變。
最後,您可以通過以下命令重組該表中現有的行:
alter table bookings shrink space compact;
該命令將會在塊內重新分配行,如圖3所示,這就在HWM之下產生了更多的空閒塊,但是HWM自身不會進行分配。
圖 3:重組行後的表中的塊
在執行該操作後,讓我們看一看空間利用率所發生的改變。使用在第一步展示的PL/SQL塊,可以看到塊現在是如何組織的:
FS1 Blocks = 0 Bytes = 0 FS2 Blocks = 0 Bytes = 0 FS3 Blocks = 1 Bytes = 0 FS4 Blocks = 0 Bytes = 0 Full Blocks = 2 Bytes = 16384
注意這裡的重要改變:FS4塊(具有75-100%的空閒空間)的數量現在從4,148降為0。我們還看到FS3塊(具有50-75%的空閒空間)的數量從0增加到1。但是,由於HWM沒有被重置,總的空間利用率仍然是相同的。我們可以用如下命令檢查使用的空間:
SQL> select blocks from user_segments where segment_name = 'BOOKINGS'; BLOCKS --------- 4224
由該表占用的塊的數量(4,224)仍然是相同的,這是因為並沒有把 HWM 從其原始位置移開。可以把HWM移動到一個較低的位置,並用如下命令回收空間:
alter table bookings shrink space;
注意子句COMPACT 沒有出現。該操作將把未用的塊返回給數據庫並重置HWM。可以通過檢查分配給表的空間來對其進行測試:
SQL> select blocks from user_segments where segment_name = 'BOOKINGS'; BLOCKS ---------- 8
塊的數量從4,224降為8;該表內所有未用的空間都返回給表空間,以讓其他段使用,如圖4 所示。
圖 4:在收縮後,把空閒塊返回給數據庫
這個收縮操作完全是在聯機狀態下發生的,並且不會對用戶產生影響。
也可以用一條語句來壓縮表的索引:
alter table bookings shrink space cascade;
聯機shrink命令是一個用於回收浪費的空間和重置HWM的強大的特性。我把後者(重置HWM)看作該命令最有用的結果,因為它改進了全表掃描的性能。
找到收縮合適選擇
在執行聯機收縮前,用戶可能想通過確定能夠進行最完全壓縮的段,以找出最大的回報。只需簡單地使用dbms_space包中的內置函數verify_shrink_candidate。如果段可以收縮到1,300,000字節,則可以使用下面的PL/SQL代碼進行測試:
begin if (dbms_space.verify_shrink_candidate ('ARUP','BOOKINGS','TABLE',1300000) then :x := 'T'; else :x := 'F'; end if; end; /
PL/SQL過程成功完成。
SQL> print x X -------------------------------- T 如果目標收縮使用了一個較小的數,如 3,000: begin if (dbms_space.verify_shrink_candidate ('ARUP','BOOKINGS','TABLE',30000) then :x := 'T'; else :x := 'F'; end if; end;
變量x 的值被設置成'F',意味著表無法收縮到3,000字節。現在假定您將著手在一個表上,或者也許是一組表上創建一個索引的任務。除了普通的結構元素,如列和單值性外,您將不得不考慮的最重要的事情是索引的預期大小 — 必須確保表空間有足夠的空間來存放新索引。
在Oracle數據庫9i 及其以前的版本中,許多DBA使用了大量的工具(從電子數據表到獨立程序)來估計將來索引的大小。在10g中,通過使用DBMS_SPACE包,使這項任務變得極其微不足道。