Oracle壓縮黑科技(一)—基礎表壓縮

沃趣科技發表於2017-11-20

原文連結 https://www.red-gate.com/simple-talk/sql/oracle/compression-oracle-basic-table-compression/

譯者  周天鵬 


在關於Oracle壓縮的這一系列文章中,我們會研究下傳統Oracle資料庫系統的各類壓縮方式,這意味著該系列文章的目錄結構大概是:

1. 基礎表壓縮 

2. OLTP表壓縮

3. 索引壓縮

但是,不討論Exadata的hybrid columnar compression (HCC)。


在這三種壓縮技術中,索引壓縮和基礎表壓縮是產品自帶的核心元件,但是,OLTP壓縮需要獨立的“Advanced Compression Option (ACO)” license授權。再第一篇文章中,我們先用基礎表壓縮造一些資料,把對資料更新刪除的問題留到第二篇文章中,最後基於前兩篇的鋪墊,我們再研究下OLTP的壓縮。索引壓縮單獨留在第四、第五篇中探討。


本文主要目的是解答一些關於表壓縮相關的經常被問到的問題。

基礎表壓縮何時起作用?

人們經常問道,“我如何造壓縮資料”,“Oracle如何解壓這些資料塊”,“壓縮對效能會造成什麼影響”,還有一個人們在使用任何新特性前都會問的問題“有啥不為人知的副作用嗎?”

回答第一個問題最簡單的方法就是通過一個實際例子。這裡有5條SQL,跑完後我們先收集表的統計資訊,然後查一下表裡有多少資料塊和一些其他相當資訊。


--    1. Baseline CTAS
create table t1
as
select * from all_objects where rownum <= 50000;

--    2. CTAS with basic compression enabled
create table t1 compress basic
as
select * from all_objects where rownum <= 50000;

--    3. Normal insert into empty table defined as compressed
create table t1 compress basic
as
select * from all_objects where rownum = 0;

insert into t1 select * from all_objects where rownum <= 50000

--    4. Direct path insert into empty table defined as compressed
create table t1 compress basic
as
select * from all_objects where rownum = 0;

insert /*+ append */ into t1 select * from all_objects where rownum <= 50000

--    5. CTAS without compression, then change to compressed
create table t1
as
select * from all_objects where rownum <= 50000;

alter table t1 compress basic; 

alter table t1 move


每一條SQL執行完我都會執行下面的SQL查詢資料塊的資訊:


select  blocks, pct_free , compression, compress_for
from    user_tables
where   table_name = 'T1';


當然也有其他方法,我們可以將表空間定義為壓縮的,這樣在裡面建立的所有表就會被預設進行壓縮;我們還可以將分割槽表的分割槽或者子分割槽進行壓縮;我們甚至可以將分割槽表定義為預設壓縮,這樣新增的分割槽就都是壓縮的了。

我用下面這個圖表總結了上述sql程式碼的結果:

test5有兩個結果,一個是alter table move之前的,一個是之後的

Oracle壓縮黑科技(一)—基礎表壓縮

當我在CTAS(create table as select)加了壓縮選項時, Oracle自動將pctfree置為0 —— 這將資料塊的數量顯著減少,只用了189個資料塊。pctfree為0意味著Oracle認為這張表將會變成read only的。但是,pctfree當然也可以設定為一個非空的值,這在後面的章節會講。

在第三第四個測試中,我建立了一個啟用了壓縮的空表,然後插入資料。正如你所看到的,只有使用direct path insert,插入的資料才會被壓縮。普通的insert操作並不會壓縮資料。(insert後的資料塊644個,相比CTAS 714個要少一些的原因是因為pctfree從10變為了0)

最後一個測試告訴我們,將表從非壓縮改為壓縮之後,對現存的資料並沒有影響。如果你想將未壓縮的資料進行壓縮,需要先改變表的定義,然後move表。但是,move後需要立即重建表上的所有索引。

壓縮原理並非如我們所想

Oracle如何進行壓縮的呢?實際上,Oracle並不會進行壓縮。他做的僅僅是塊級別的深度複製。想象一下,你在一個資料塊裡有下面三行資料:


(‘XXXX’, ‘abcdef’, 254.32, ‘CLOSED’)
(‘XXXX’, ‘pqrstu’, 17.12,  ‘CLOSED’)
(‘AAAA’, ‘abcdef’, 99.99,  ‘CLOSED’)


Oracle會發現‘XXXX’出現了兩次,‘abcdef’出現了兩次,‘CLOSED’出現了三次。這樣,就可以用這個塊裡重複的值建立一個字典表。壓縮後的資料如下


T1 (‘XXXX’)
T2 (‘abcdef’)
T3 (‘CLOSED’)
(T1, T2, 254.32, T3)
(T1, ‘pqrstu’, 17.12, T3)
(‘AAAA’, T2, 99.99, T3)


其實,Oracle比這還要聰明,它可以重新排列塊中的欄位順序,使得多個欄位可以用一個標誌代替。在我們的例子中,三行資料都有T1和T3。Oracle可以重排列這些欄位,讓這些標誌儘可能的在一塊,以至於可以用建立一個標誌來代替兩個標誌的組合。最終資料會變成這樣:


T1 (‘XXXX’, T2)        -- 這是一個由數值和標誌組合成的標誌
T2 (‘CLOSED’)
T3 (‘abcdef’)
(T1, T3, 254.32)    -- 注意這行只有了三列
(T1, ‘pqrstu’, 17.12)    -- 同上
(‘AAAA’, T2, T3, 99.99)


讓我們通過dump資料塊裡的資料來更進一步觀察壓縮的內部實現原理。這裡是一個壓縮表中的資料塊中的第一個片段:


perm_9ir2[4]={ 2 0 1 3 }


這個表有4個資料塊,但是對於這個塊,Oracle重新排列了欄位的順序,意思是:欄位0放在了第二位,欄位1在第三位,欄位2在第一位,欄位3在第四位。


0x24:pti[0]     nrow=65    offs=0
0x28:pti[1]     nrow=400   offs=65


如上,這是資料塊裡的兩個“表”,第一個是存放標誌的“表”(其實就是字典表),有65個標誌,在塊的行目錄中從0開始。第二個是真正的“表”,有400行,在塊的行目錄中從65開始。這意味著這個塊的行目錄一共有465個條目。

如果我們從第二個“表”(真正的資料表,而不是字典表)開始看,我們會發現這和普通的堆表中的資料塊dump出來的一行沒什麼兩樣。但這裡有一些特殊的點需要注意。


tab 1, row 0, @0x1b28
tl: 5 fb: --H-FL-- lb: 0x0  cc: 4
col  0: [ 4]  41 41 41 41
col  1: [10]  41 41 41 41 41 41 41 41 41 41
col  2: [ 2]  c1 02
col  3: [10]  20 20 20 20 20 20 20 20 20 31
bindmp: 2c 00 01 04 31


基於列的長度(方括號中的資料),行的長度是26個位元組(4+10+2+10),加上四個列4個位元組 和 flag byte(fb:),lock byte(lb:),column count(cc:)每個1位元組 - 但總的實際長度(tl:)只有5位元組。而且最後一行也展示了這5個位元組實際的資料。這5個位元組分別是flag byte (0x2c = ‘–H-FL’), lock byte和儲存的列數量。然後剩下2位元組告訴我們有一個列是一個標誌代表4個連續的值,而且我們需要到字典表中找0x31號標誌。接下來讓我們看下字典表中的49行(0x31):


tab 0, row 49, @0x1ed0
tl: 19 fb: --H-FL-- lb: 0x0  cc: 4
col  0: [ 4]  41 41 41 41
col  1: [10]  41 41 41 41 41 41 41 41 41 41
col  2: [ 2]  c1 02
col  3: [10]  20 20 20 20 20 20 20 20 20 31
bindmp: 00 08 04 36 40 ca c1 02 d2 20 20 20 20 20 20 20 20 20 31


這個標誌看起來幾乎和行一樣 - 但是標誌的總長是19位元組。所以我們看下dump出來的資料。前兩個位元組告訴我們這個標誌在這個塊裡用了8次。下一個位元組告訴我們標誌中有4個列,通過一些編碼,剩下的兩個位元組告訴我們這個標誌的前兩個欄位的值實際儲存在在0x36(54)和0x40(64)號標誌中。後兩個欄位直接就是實際的資料了。

所以,通過我們的方法,從行目錄到行、標誌,我們可以擴充套件一個5位元組的條目到一個完整的26位元組的行。

通過我們對資料塊dump出的資料進行跟蹤,這裡還有許多知識值得學習。

1. Oracle不會解壓這些資料,他只是根據你的需求,用字典表和資料表中的資料將行重構出來。
2. 重構行的時候很可能會消耗一些額外的CPU,在做全表掃描時將尤為明顯。
3. 有一個副作用,為了能重構行,Oracle必須持有某些塊一段時間。所以你可能發現你的sql很少發生“consistent gets – examination”的等待,因為大部分時間花在了“cache buffers chains”的latch上面。

總 結

依然有很多關於壓縮的副作用值得一提,尤其是刪除和更新表的時候,這也講引導著我們去實現OLTP的壓縮 - 將來的文章會講。

我們從這第一篇文章中發現看到了:
1. 基礎壓縮只有在direct path inserts時有效,普通的DML不會壓縮資料。
2. Oracle會預設把壓縮表的PCTFREE置為0,這也很好的表明,Oracle認為建表後你不會再修改資料。
3. 基礎表壓縮僅僅是把重複的值進行深度複製,但Oracle足夠聰明來最小化資料佔用的空間。
4. 這種深度複製機制意味著Oracle不需要解壓資料,只需要把塊cache在buffer cache中然後在PGA裡重構行即可,該操作屬於CPU密集型。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/28218939/viewspace-2147453/,如需轉載,請註明出處,否則將追究法律責任。

相關文章