2020年10月22日 星期四

5.6 Segment 介紹

Oracle Segment 的種類總共分為 Undo Segment 、 Data Segment 、 Index Segment , Temporary Segment 四種。


  • Undo Segment: 作為 rollback Segment 所使用,主要是存放 rollback data ,提供 transaction rollback 或是 instance recovery 所使用,自 Oracle 9i 開始以 Undo Tablespace 取代傳統的 rollback Segment , Undo Tablespace 裡面的 Undo Segment (或 rollback Segment) 交由 Oracle 自動管理,管理方式由參數 undo_management 所決定,預設為 auto (Oracle 自動管理) ,若改為 manual 則還原為傳統 rollback segment 的方式,由 DBA 自行手動管理。


  • Data Segment: 存放資料所使用,在 Tablespace 建立 Table 時就會建立相對應的 Data Segment。


  • Index Segment: 建立 Index 時就會建立相對應的 Index Segment。


  • Temporary Segment: 主要存在於 Temporary Tablespace ,為 Global Temporary Table 或是 SQL 執行過程中的排序行為所使用。


Segment 的管理方式分為 Manual 與 Auto 兩種,同樣可以透過 dba_tablespaces 來得知目前是使用哪一種管理方式:


Manual 的管理方式屬於 Oracle 早期的功能,在 Oracle 9i 以前的版本都是使用此種方式管理,代表 Segment 是使用 Freelists 來管理可用空間。


Freelists 存在於 Segment Header 當中,裡面記錄著此 Segment 有哪些 Block 是可被使用的,當一個 Block 從未被使用或是使用率小於 pctused 就會被放置在 Freelists 當中,每當有資料需要新增進來時,必須先於 Segment Header 查閱 Freelists 得知有哪些 Block 可用,獲得可用的 Block 訊息之後再把資料塞入到這些 Block 當中。透過 dba_segments 可以得知目前 Freelists 的設定:


Freelists 預設為 1 ,代表著 Segment Header 只有一份可用空間的清單,當同時有兩個人要來塞資料時,只有一個人有 Freelists 可以查閱,另一個人由於得不到 Freelists ,所以它也不知道有哪些 Block 可以用來塞資料,此時就必須等待另一個人使用完畢才能夠查閱 Freelists ,這個時候就會產生等待事件 Buffer Busy Wait 。如果這時候有兩份清單存在,那麼另一個人就可以不用等待了,透過 alter table 指令來修改 Data Segment 的 Freelists 數量,例如將它改為2:


由於 Freelists 會產生爭用的情況而影響效能,自 Oracle 9i 開始推出了另一種管理方式 Automatic Segment Space Management(ASSM) ,也就是 Segment Management 為 Auto 的管理方式。


ASSM 同樣的也是把可用的 Block 訊息存放在 Segment Header 當中,只不過不是以 Freelists 形式存放,而是類似於 Index 的 B-Tree 結構存放。 ASSM 首先把一個 Data Block 的使用程度分為四類: 剩餘 25% 可用為 FS1、剩餘 25% ~ 50% 可用為 FS2、剩餘 50% ~ 75% 可用為 FS3,剩餘 75% ~ 100% 可用為 FS4。

FS1 ~ FS4 取代了 pctused 參數,在 ASSM 架構下只須設定 pctfree 參數來控制此 Block 可填滿的程度,這樣分類的好處是可以很快的幫一筆資料找到可以配置的 Data Block ,例如一筆小資料進來只需配置 FS1 的空間就足夠了,或者是大資料進來需要配置到 FS4 的空間甚至 FS4 + FS3 的空間才足夠,這種做法比起從 Freelists 上搜尋可用的 Block 還要有效率。那麼 ASSM 是怎麼找到這些具有 FS1 ~ FS4 空間的 Block ? 這就透過之前提到的 B-Tree 結構來查找。


ASSM 的 B-Tree 結構只有三層,單位是 BMB (Bitmap Block) ,最上層的 是Root Block,也就是 Level 3 (L3) 的 BMB;其次為 Branch Block 、 Level 2 (L2) 的 BMB,最底層為 Leaf Block 、 Level 1 (L1) 的 BMB。L3 BMB 記錄著底下有多少個 L2 BMB 以及前後可參照的其他 L3 BMB 資訊; L2 BMB記錄著底下有多少個 L1 BMB ,而 L1 BMB 則是記錄著包括 FS1 ~ FS4 外加完全使用 (Full) 以及尚未使用 (Unformatted) 等六種 Data Block 的信息,一個 L1 的 BMB 可以容納 16 ~ 1024 個 DBA (Data Block Address),當一筆資料 Insert 進來,則會經由 L3 的 BMB 開始搜尋到 L1 ,然後由 L1 的 BMB 來配置可用的 Data Block 來存放。

由於 BMB 的特性,衍生出了 High “High Water Mark” (HHWM) 與 Low “High Water Mark” (LHWM) 兩種概念。 “High Water Mark” (HWM) 表示 Segment 在此水位之上的所有 Data Block 都未曾被使用過 (Unformatted)、在此水位下的 Data Block 已經被使用 (Formatted),而 BMB 裡面分佈的 Data Block 並非是連續排列的,因此對 ASSM 來說,它的高水位記號 (HWM) 並非只是一條直線,而是一個區間,區間上緣稱作 High “High Water Mark” (HHWM) 、 區間下緣稱作 Low “High Water Mark” (LHWM) , HHWM 之上表示所有的 Data Block 都未曾被使用 (Unformatted) 、 LHWM 之下則表示 Data Block 已被使用 (Formatted) ,而中間的部分則是包含了使用 (Formatted) 與未曾使用 (Unformatted) 的 Block。


雖然 ASSM 這個功能從 Oracle 9i 的時候開始推出,但到了 Oracle 10g 之後才變成預設的 Segment 管理方式,由於 ASSM 的效率優於 Freelists ,對於 Permanent Tablespace來說,現在已經不再使用 Freelists ,也就是 Manual 的 Segment 管理方式了。




2020年9月23日 星期三

5.5 Oracle Data Block

Oracle Data Block 為儲存資料的最小邏輯單位, Oracle 資料庫的所有資料最終都是存放在 Oracle Data Block 當中,而一個 Data Block 在結構上可以分為 Block Header 與 Footer 兩部分:

Block Header 記錄著此 Block 的系統資訊,包括 Block 的空間使用率、interested transaction list (ITL) 、 data block address (dba, 表示 Oracle Data Block 存放在作業系統的上的實體位址)…等,Block Footer 為資料存放的地方,我們可以想像 Block 是一個水桶,儲存的資料為水桶裡面的水位, Block 的使用就猶如倒水一般的把資料從最底下慢慢的往上填滿。


對於 Data Block 的空間使用方式由 pctfree 與 pctused 兩個參數來控制:

  • PCTFREE:

表示要為這個 Block 保留多少百分比的空間,此參數預設為 10 ,表示一個 Block 的使用率需保留 10% 的可用空間,也就是當 Block 的水位(使用率)達到 90% 的時候就表示這個 Block 已經填滿了。保留這 10% 的可用空間是要為將來資料的 update 所使用, update 其實就是一個 insert 與 delete 的過程,所以需要保留一定的可用空間來容納新資料,如果保留的可用空間不夠,就會產生 row migrate 的情況。當然如果在規劃上確保資料將來不會被 update ,那麼也可以將 pctfree 設定為 0 ,讓 Data Block 的使用率達到 100% 。


  • PCTUSED:

隨著資料庫的運行,資料有可能被新增、刪除、修改,那麼在甚麼情況下一個 Data Block 才有可能再被重複使用? pctused 就是用來決定此 Block 是否能夠再被使用的參數。隨著資料的刪除,一個 Block 的水位也會越來越低,當水位低於 pctused 設定的水準時,就將此 Block 視為空 Block 可再度被使用。 pctused 預設為 40 ,也就是說當一個 Block 的使用率小於 40% 時,就可以再度的被使用。


舉例來說,假定 PCTFREE 為 20 、 PCTUSED 為 10 ,那麼就可以將一個空的 Block 從 0 開始填資料直到此 Block的使用率達到 80% 為止,之後就不再允許資料放入此 Block ,未來此 Block 的資料必須一直減少到只剩下 10% 的使用率時,才可以允許再有資料放進來。


由於 pctfree 與 pctused 兩個參數控制著 Block 空間的使用率,如果設置不當的話容易產生 Row Chain 與 Row Migrate 兩個現象。


Row Chain 表示一個 Block 無法容納下一整筆的資料,需要分散在多個 Block 上放置,有可能是資料本身長度過長,或是 Block 的可使用率太低:

假如 pctfree 設置過大,一個 Block 需保留較多的可用空間,相對的能使用的空間就變少,這樣就有可能因為一個 Block 的可使用率太低而造成 Row Chain 的情況。


Row Migrate 表示一個 Block 無法容納下 update 的資料,必須尋找可用空間足夠的 Block 來容納此 Block 的所有資料,然後把新舊資料一併的搬過去:

假如 pctfree 設置過小,那麼就容易造成 Block 保留的可用空間容納不下 update 的新資料而造成 Row Migrate 的情形。


一般來說,如果 update 會造成資料的長度增加,那麼建議將 pctfree 的設置大於預設值,避免因資料長度的增加而造成 Row Migrate 的情況;如果資料的 update 頻率不高,那麼可以將 pctfree 的設置小於預設值,這樣可以增加 Data Block 的可使用率;如果沒有辦法預期資料是否被 update ,那麼就建議使用預設值就好了。


除了 pctfree 與 pctused 是用來控制 Block 的空間使用率外,另外還有兩個參數 initrans 與 maxtrans 控制著同時有多少人可以來更新這個 Block。


當一個 Block 裡面的資料需要被異動時,它必須要知道此筆 row 是被哪一個事務(transaction) 所異動,因此 Data Block 會記載著所有要來異動它的事物(transaction) ,這個記錄就稱作 interested transaction list (ITL) 。當一個 Data Block 接受到一個事務(transaction)來請求資料的異動時, Data Block 會於 Block Header 中分配一個約 23 bytes 的空間專門來記錄這個事務(transaction)的資訊,而這個空間就稱作 ITL Slot,當一個交易(transaction)獲得 ITL Slot 之後,它才有權利請求 row lock 來異動資料。

參數 initrans 表示 Block 起始配置的 ITL Slot 數量,而 maxtrans 表示最大可配置的 ITL Slot 數量,此兩個參數最小可設為 1 最大 255,預設 Table 的 initrans 為 1 、 Index 的 initrans 為 2 ,而自 Oracle 10g 之後, maxtrans 已自動配置為 255 了。當一個交易(transaction)佔用了一個 ITL Slot 且尚未 commit,此時又有另一個交易(transaction)進來,如果沒有可用的 ITL Slot ,它就必須等待其他的交易(transaction)完成 commit 後釋出 ITL Slot,或是等待 Block 配置一個新的 ITL Slot,這個時候就會產生 enq: TX - allocate ITL entry 這個等待事件。如果這個事件發生頻率很高且等待時間過長,就要考慮增加 initrans ,起始就分配多一點的 ITL Slot 來避免這個等待的發生。


Data Block 相關參數的設定是在建立 Table時所附加,例如建立一個 Table T1 ,設定 pctfree 20 、 pctused 40 且 initrans 4:

SQL> create table t1(a varchar2(20),b number) 

pctfree 20 pctused 40 initrans 4;


而建立 Index 時只能設定 initrans 屬性:


如果要修改屬性,可以使用 alter 命令來修改,例如將 T1 設定為 pctfree 10 、 pctused 20 且 initrans 2:


介紹完這些參數之後,最主要的還是要了解一個應用系統的特性,為 OLTP 系統或是 OLAP 系統 ? 資料頻不頻繁被異動 ? 資料成長速度快不快 ? 資料的屬性為何 ? 這樣才能在系統建置的初期來客制化這些參數,進而達到最佳化的結果。





2020年9月21日 星期一

5.4 管理 Tablespace

當我們在一個 Tablespace 裡面建立一個 Table 時,會在這個 Tablespace 上面建立一個 Data Segment 給這個 Table 所使用,自Oracle 11g 開始出現了參數 deferred_segment_creation ,預設為 TRUE ,意思是在 Table 建立的當下且沒有任何資料時先不要配置 Data Segment 給它,直到有資料塞進來時再配置 Data Segment ,設為 FALSE 表示在 Table 建立的當下就配置 Data Segment ,這也是過往 Oracle DB 的作法, 11g 開始出現這個參數的用意只是為了節省 Tablespace 的使用空間,因為一個沒有資料的空 Table 在不配置 Data Segment 的情況下不會占用到 Tablespace 的空間。而隨著 Table 資料的增長,起始配置給這個 Table 的 Data Segment 勢必會遇到用完的一天,當 Data Segment 裡面的剩餘空間不足以再容納 Table 新增資料時,就必須從 Tablespace 裡面再挖一段 Extent 進來擴充:

那麼 Data Segment 在擴充的當下要怎麼知道 Tablespace 還有沒有足夠的空間來新增 Extent ? 這個時候就要提到 Tablespace 裡面的 Extent Management 機制,分為 Dictionary Management 與 Local Management。


  • Dictionary Management:

Tablespace 裡面的 Free Extent 由系統的 Table SYS.UET$ 與 SYS.FET$ 來管理,已使用的 Extent 記錄在 SYS.UET$ 裡面,Free Extent 則是記錄在 SYS.FET$ ,當 Segment 需要新增 Extent 時便會從 SYS.FET$ 來得知有哪些 Free Extent 可以用, SYS.FET$ 裡面記錄的某一筆 Free Extent 被拿去使用時,便會從 SYS.FET$ 裡面刪除這筆資料,同時會在 SYS.UET$ 新增一筆資料表示這個 Extent 已被使用。這種做法的缺點是會增加系統額外的 I/O 操作,因為除了前台對 Table 資料異動的 I/O 之外,後端也要不斷的對 FET$ 與 UET$ 進行操作,容易影響到整體的效能。


  • Local Management:

對於 Free Extent 的管理不使用 UET$ 與 FET$ ,而是以 bitmap 的方式直接在 Tablespace 所屬的 Data File Header 上直接標記有哪些空的 Block 可以做使用, Segment 需要新增 Extent 的時候可以直接從 Data File Header 得知可用空間然後馬上進行擴充,不用再去查找 FET$ 也不用對 FET$ 與 UET$ 進行操作,在流程上省去了很多動作,此種管理方式對整體效能較好。


在建立 Tablespace 的當下使用 extent management 關鍵字就可以選擇要使用哪一種管理方式了:

SQL> create tablespace ts1 datafile '/u01/app/oradata/ts1_01.dbf' SIZE 50M extent management dictionary default storage (initial 50K next 50K minextents 2 maxextents 50);

(建立 Extent Management 為 Dictionary 的 Tablespace

起始 segment 50K 不夠的時候以 50k 為單位擴充,最大可擴充 50 個 Extent)


SQL> create tablespace ts1 datafile '/u01/app/oradata/ts1_01.dbf' SIZE 50M extent management local;

 (建立 Extent Management 為 Local 的 Tablespace)


在 Local Management 下系統會自行決定下個 Extent 要增長的大小,預設以 64K 為單位增長,隨著 Segment 的擴充也有可能增加 Extent 的單位至 1M、8M 或是64M 。我們可以透過設定 Uniform size 來固定每次增加 Extent 的大小,例如每次固定增加 256K:

SQL> create tablespace ts1 datafile '/u01/app/oradata/ts1_01.dbf' SIZE 50M extent management local uniform size 256K;


現在 Oracle 預設的 Extent Management 都是 Local Management ,也就是不指定 Extent Management 時都是用 Local Management ,因為 Local Management 的管理方式與效能較好, Dictionary Management 已經不再使用了。透過 dba_tablespaces 可以得知目前 Tablespace 是使用哪一種 Extent Management:

轉換 Tablespace 為 Dictionary 或是 Local Management 可以使用dbms_space_admin 來達成:

SQL> exec dbms_space_admin.Tablespace_Migrate_TO_Local('ts1'); 

(將 Tablespace ts1 由 Dictionary Management 轉換為 Local Management)


SQL> exec dbms_space_admin.Tablespace_Migrate_FROM_Local('ts2');

(將 Tablespace ts2 由 Local Management 轉換為 Dictionary Management)


不過現在預設都是使用 Local Management ,也不太會有轉換的需求了。


如果要刪除一個 Tablespace ,使用 drop tablespace 命令:

SQL> drop tablespace ts1; 

(刪除 tablespace ts1)


SQL> drop tablespace ts2 including contents and datafiles;

(刪除 tablespace ts1 並且連同相對應的 Data File 也一併刪除)


除此之外,我們還可以使用 alter tablespace 命令來更改 tablespace 的屬性,例如將 Tablespace 更改為唯讀模式 (read-only) 或是 nologging :

SQL> alter tablespace ts1 read only; 

(將 tablespace ts1 改為 read-only)


SQL> alter tablespace ts1 read write; 

(將 tablespace ts1 改為 read-write)


SQL> alter tablespace ts2 nologging; 

(將 tablespace ts2 改為 nologging)


SQL> alter tablespace ts2 logging; 

(將 tablespace ts2 改為 logging)


Tablespace 建立起來預設都是 logging 模式,也就是在此 Tablespace 做的任何異動都會記錄到 redo log 當中,建議不要將 Tablespace 更改為 nologging ,因為 nologging 模式下, Tablespace 的異動不會記錄在 redo log ,只要發生異常就無法還原。我們有時候會為了加速 insert 的效能短暫的將 Tablespace 設定為 nologging ,不過在做完這個任務後必須馬上將它改回 logging 並且執行備份,避免造成無法還原的情況。

 

對於 Tablespace 的監控,最常使用的就是 dba_tablespaces 、 dba_free_space 與 dba_data_files 來查看狀態了,而 DBA 則是需要每天觀察 Tablespace 的使用率,我們可以使用下列語法來查詢:

col ts_name format a12

col type format a12

select a.tablespace_name ts_name,c.contents type,

          round((a.mbytes - nvl(b.mbytes,0))/a.mbytes * 100,2) "USED(%)",

          round(nvl(b.mbytes,0)/a.mbytes * 100,2) "free(%)",

          round(nvl(b.mbytes,0),2) "free(MB)",a.mbytes "total(MB)"

 from (select tablespace_name,sum(bytes)/1024/1024 mbytes

          from dba_data_files

        group by tablespace_name) a,

       (select tablespace_name,sum(bytes)/1024/1024 mbytes 

          from dba_free_space

        group by tablespace_name) b,

          dba_tablespaces c

where a.tablespace_name=b.tablespace_name(+)

  and a.tablespace_name=c.tablespace_name(+)

union

select a.tablespace_name ts_name,b.contents type,

          round(nvl(b.mbytes,0)/a.mbytes * 100,2) "USED(%)",

          round((a.mbytes - nvl(b.mbytes,0))/a.mbytes * 100,2) "free(%)",

          round((a.mbytes - nvl(b.mbytes,0)),2) "free(MB)",a.mbytes "total(MB)"

  from (select tablespace_name,sum(bytes)/1024/1024 mbytes

            from dba_temp_files group by tablespace_name) a,

        (select ss.tablespace_name,ts.contents,

                  sum((ss.used_blocks*ts.block_size))/1024/1024 mbytes

            from gv$sort_segment ss, dba_tablespaces ts

          where ss.tablespace_name = ts.tablespace_name

            group by ss.tablespace_name,ts.contents) b

 where a.tablespace_name=b.tablespace_name

 order by ts_name;

3-33


上述範例使用的是 dba_data_files 中的 bytes 欄位來計算使用率,如果 Data File 有設定 autoextend 的話,可以將 bytes 欄位更改為 maxbytes 來計算會更加的精確。以下範例是以 maxbytes 計算之:

col ts_name format a12

col type format a12

select a.tablespace_name ts_name,c.contents type,

 round((a.rmbytes - nvl(b.mbytes,0))/decode(a.mbytes,0,32767,a.mbytes) * 100,2) "USED(%)",

 100 - round((a.rmbytes - nvl(b.mbytes,0))/decode(a.mbytes,0,32767,a.mbytes) * 100,2) "free(%)",

 round(nvl(b.mbytes,0),2) "free(MB)",a.rmbytes "total(MB)"

from

 (select tablespace_name,sum(maxbytes)/1024/1024 mbytes,sum(bytes)/1024/1024 rmbytes from dba_data_files

   group by tablespace_name) a,

 (select tablespace_name,sum(bytes)/1024/1024 mbytes from dba_free_space

   group by tablespace_name) b,

  dba_tablespaces c

where a.tablespace_name=b.tablespace_name(+)

  and a.tablespace_name=c.tablespace_name(+)

union

select a.tablespace_name ts_name,b.contents type,

 round((a.rmbytes - nvl(b.mbytes,0))/decode(a.mbytes,0,32767,a.mbytes) * 100,2) "USED(%)",

 100 - round((a.rmbytes - nvl(b.mbytes,0))/decode(a.mbytes,0,32767,a.mbytes) * 100,2) "free(%)",

 round((a.rmbytes - nvl(b.mbytes,0)),2) "free(MB)",a.rmbytes "total(MB)"

from

 (select tablespace_name,sum(maxbytes)/1024/1024 mbytes,sum(bytes)/1024/1024 rmbytes

   from dba_temp_files group by tablespace_name) a,

 (select ss.tablespace_name,ts.contents,sum((ss.used_blocks*ts.block_size))/1024/1024 mbytes

   from gv$sort_segment ss,dba_tablespaces ts

  where ss.tablespace_name=ts.tablespace_name

  group by ss.tablespace_name,ts.contents) b

where a.tablespace_name=b.tablespace_name

order by ts_name;


這邊要注意的是,如果 Data File 沒有設定為 autoextend 屬性,則 dba_data_files 裡面的 maxbytes 欄位會顯示為 0 :