近期來,FreeList的重要作用逐漸爲Oracle DBA所認識,網上也出現一些相關的討論。本文以FreeList爲線索對Oracle的存儲管理的原理進行較深入的探討,涉及Oracle段區塊管理的原理,FreeList算法等。而與FreeList密切相關的一個重用特性HWM,與sql性能密切相關,本文也作了原理分析介紹。在原理探討的基礎上,介紹了常用的存儲參數分析方法,並對所涉及的存儲優化、HWM的優化和Freelist競爭優化作了說明。
縮略語:
ASSM:auto segement space management
HWM:high water mark
DBA:data block address
OLTP:online transaction process
OPS:oracle parallel server
ASSM:auto segement space management
HWM:high water mark
DBA:data block address
OLTP:online transaction process
OPS:oracle parallel server
1.簡介
Oracle的空間管理和存儲參數管理是Oracle管理及優化的重要部分。FreeList作爲Oracle底層存儲參數中的核心參數,其行爲方式對Oracle的存儲管理及性能優化有重大影響,而現有的Oracle文檔對此方面的內容比較缺乏。雖然Oracle 9i已出現了ASSM,但是作爲深入調優對FreeList認識仍是必要的。
Oracle的空間管理和存儲參數管理是Oracle管理及優化的重要部分。FreeList作爲Oracle底層存儲參數中的核心參數,其行爲方式對Oracle的存儲管理及性能優化有重大影響,而現有的Oracle文檔對此方面的內容比較缺乏。雖然Oracle 9i已出現了ASSM,但是作爲深入調優對FreeList認識仍是必要的。
近期來,FreeList的重要作用逐漸爲Oracle DBA所認識,網上也出現一些相關的討論。本文以FreeList爲線索對Oracle的存儲管理的原理進行較深入的探討,涉及Oracle段區塊管理的原理,FreeList算法等。而與FreeList密切相關的一個重用特性HWM,與sql性能密切相關,本文也作了原理分析介紹。在原理探討的基礎上,介紹了常用的存儲參數分析方法,並對所涉及的存儲優化、HWM的優化和Freelist競爭優化作了說明。
這些原理分析和性能優化都建立在探討的基礎上,限於篇幅和本人經驗可能存在侷限、偏差或謬誤。
爲了準確文中部分結構和字段的說明直接用英文描述。限於篇幅本文不對同樣很重要的block結構作更深入的討論,對OPS性能有重要影響的free list group本文也未提及,因此本文在單一free list group下討論。對於block的深入討論、free list group的介紹與優化以及PCTUSED和PCTFREE等重要參數的優化請參見參考文獻和資料。
2.原理探討
FreeList作爲一個Oracle存儲管理的核心參數。其行爲方式由Oralce內部控制,我們一般不需要掌握和控制。但是我們可能會遇到這些問題,當插入一條記錄,會插入到那個塊中?是使用新塊,還是插入有數據的老塊?段是什麼時候擴展的,如何擴展的?表中只有一條記錄,但是作一次select時代價卻是上千個塊,爲什麼?如果我們從原理上清楚了Oracle的存儲管理方式,對相關這些問題的解決及性能優化就清晰自然了。
2.1 Oracle的邏輯儲存結構
Oralce的邏輯存儲結構按表空間,段,區,塊進行管理。塊是Oracle用來管理存儲空間的最基本單元,Oracle數據庫在進行輸入輸出操作時,都是以塊爲單位進行邏輯讀寫操作的。區由一系列連續的塊組成,Oralce在進行空間分配、回收和管理時是以區爲基本單位的。段由多個區組成,這些區可以是連續的也可以是不連續的,一般情況下一個對象擁有一個段。表空間中容納段和區。
在生成段的時候,會同時分配初始區(initial extents), 初始區的第一個塊就格式化爲segment header,並被用來記錄free list描述信息、extents信息,HWM信息等。
2.2 free list概念
free list是一種單向鏈表用於定位可以接收數據的塊,在字典管理方式的表空間中,Oracle使用free list來管理未分配的存儲塊。Oracle記錄了有空閒空間的塊用於insert或Update。空閒空間來源於兩種方式:1.段中所有超過HWM的塊,這些塊已經分配給段了,但是還未被使用。2.段中所有在HWM下的且鏈入了free list的塊,可以被重用。free list具有下列屬性
l flag指示free list 被使用(1)或未使用(0)
l free list 鏈的首塊的地址DBA(data block address)
l free list 鏈的尾塊的地址DBA
free list 的信息通常保留在segment header中,這裏給出segment header block dump片段加以說明:
nfl = 3, nfb = 1 typ = 1 nxf = 0
SEG LST:: flg: UNUSED lhd: 0x00000000 ltl: 0x00000000
SEG LST:: flg: USED lhd: 0x03c00233 ltl: 0x03c00233
SEG LST:: flg: USED lhd: 0x03c00234 ltl: 0x03c00234
SEG LST:: flg: UNUSED lhd: 0x00000000 ltl: 0x00000000
l free list 鏈的首塊的地址DBA(data block address)
l free list 鏈的尾塊的地址DBA
free list 的信息通常保留在segment header中,這裏給出segment header block dump片段加以說明:
nfl = 3, nfb = 1 typ = 1 nxf = 0
SEG LST:: flg: UNUSED lhd: 0x00000000 ltl: 0x00000000
SEG LST:: flg: USED lhd: 0x03c00233 ltl: 0x03c00233
SEG LST:: flg: USED lhd: 0x03c00234 ltl: 0x03c00234
SEG LST:: flg: UNUSED lhd: 0x00000000 ltl: 0x00000000
Segment Header:
==> nfl: number of free lists/block
==> nfb: number of free list blocks + segment header
==> typ: block type
==> nxf: number of transaction free lists
Segment List:
==> flg: flag USED or UNUSED the free list
==> lhd: head of free list
==> ltl: tail of free list
==> nfl: number of free lists/block
==> nfb: number of free list blocks + segment header
==> typ: block type
==> nxf: number of transaction free lists
Segment List:
==> flg: flag USED or UNUSED the free list
==> lhd: head of free list
==> ltl: tail of free list
在每一個塊中都有一個標記flg用來表明塊是否鏈入了 free list鏈中。如果這個標誌置上,該塊中後向指針指向free list鏈中下一個塊的DBA。如果當前塊是鏈的最末尾的塊,該後向指針值爲0。這裏給出位於free list上的block dump的片段
Block header dump: 0x03c00235
Object id on Block? Y
seg/obj: 0xe2d8 csc: 0x00.6264c61 itc: 1 flg: O typ: 1 - DATA
fsl: 1 fnx: 0x3c00234 ver: 0x01
Object id on Block? Y
seg/obj: 0xe2d8 csc: 0x00.6264c61 itc: 1 flg: O typ: 1 - DATA
fsl: 1 fnx: 0x3c00234 ver: 0x01
==> Seg/obj Object ID in dictionary
==> csc SCN of last block cleanout
==> itc Number of ITL slots
==> flg O = On freelist , - = Not on freelist
==> typ 1 = DATA 2 = INDEX
==> fsl ITL TX freelist slot
==> fnx DBA of NEXT block on freelist
==> csc SCN of last block cleanout
==> itc Number of ITL slots
==> flg O = On freelist , - = Not on freelist
==> typ 1 = DATA 2 = INDEX
==> fsl ITL TX freelist slot
==> fnx DBA of NEXT block on freelist
舉例來說如果有五個塊在free list中,分別爲A,B,C,D,E
就會形成segment header->A->B->C->D->E--|
同時segment header->E
就會形成segment header->A->B->C->D->E--|
同時segment header->E
2.3 free list類別
在段中存在3類free list, 即Master Freelists (MFL), Process Freelists (PrFL), 和 Transaction Freelists.
2.3.1 Master Free List(公用空閒空間池):
每一個段中有一個Master free list,在段創建的時候自動生成。對於每一個段來說都有這樣一個空閒空間池,對每個進程都是公用的,空閒空間就是位於master free list 的塊上。由於Master free list是公用的,因此當多個進程同時插入行到同一個段上,master free list競爭使用程度就會增加。
2.3.2 Process Free Lists
爲了減少Master Free list的競爭問題, 引入了另一種free list叫做Process free lists, 根據sql命令 CREATE/ALTER 中的參數FREELISTS 創建. 這樣多個free list 就可以分攤空閒空間的管理,以提高OLTP應用作高度併發插入和更新事務時空間分配管理的性能。通過指定CREATE TABLE / CLUSTER or INDEX的子句STORAGE的參數FREELISTS 來創建,例如: CREATE TABLE flg ( . . . .) . . . STORAGE ( ... FREELISTS 10 ...)。缺省的FREELISTS爲1,此時不會創建Process free lists。當FREELISTS>=2時,創建Process free lists。
進程在使用process free list是根據進程的Oracle PID (Process ID)來選擇的,公式如下:
select list entry = (PID % NFL) + 1
NFL : FREELISTS定義的Process free list個數
2.3.3 Transaction Free Lists
當Oracle需要時動態創建。一個Transaction Free List 是一種專門給某一個事務使用的free list. 每個段至少有16個transactions free lists, 並且這個值在需要時會增長,直到達到Segment Header塊的大小限制。一個事務只有下面情況下會需要分配一個Tx Free Lists entry: 塊中釋放空間時(DELETE or UPDATE) 並且還不存在Tx Free Lists entry時。
進程在使用process free list是根據進程的Oracle PID (Process ID)來選擇的,公式如下:
select list entry = (PID % NFL) + 1
NFL : FREELISTS定義的Process free list個數
2.3.3 Transaction Free Lists
當Oracle需要時動態創建。一個Transaction Free List 是一種專門給某一個事務使用的free list. 每個段至少有16個transactions free lists, 並且這個值在需要時會增長,直到達到Segment Header塊的大小限制。一個事務只有下面情況下會需要分配一個Tx Free Lists entry: 塊中釋放空間時(DELETE or UPDATE) 並且還不存在Tx Free Lists entry時。
2.4 Free list行爲
2.4.1 Freelist Link and Unlink 操作
Freelist 按後進先出隊列(LIFO) 方式管理。也就是說最後被link到freelist的塊擁有最先unlink的機會。當塊中空閒空間增加到大於PCTFREE時,塊放入freelist中。free list中的塊可用來作update 或insert。 當塊中沒有足夠的空間用於insert操作時並且使用空間大於PCTUSED,塊就會從free list中移出。
在塊在DELETE or UPDATE 操作之後,如果使用空間落到PCTUSED下,塊再次link到free list中。每次塊加入free list時,都是link到鏈表的頭部。
例如:考慮段中有120個塊編號由1到120。其中有6個塊在free list上並假設HWM是 80。(block實際使用DBA編號)
10->24->45->46->65->80-|
10->24->45->46->65->80-|
現在作INSERT 操作,需要400 bytes空間。假設塊10上空間不足,但塊24上空間可用。現在數據插入到塊 24 ,現在塊24的剩餘空間小於該表的PCTUSED。因此塊 24 從free list鏈表中移出。PCTFREE and PCTUSED參數的目的就是用來控制數據塊從free list的鏈表中移入/移出行爲的。現在free lists象這樣:
10->45->46->65->80-|
然後在同一事務中作DELETE同一個段的數據,使塊 54 和 67落到PCTUSED下。現在這些塊加入到free list鏈中。free list鏈現在象這樣:
67->54->10->45->46->65->80-|
10->45->46->65->80-|
然後在同一事務中作DELETE同一個段的數據,使塊 54 和 67落到PCTUSED下。現在這些塊加入到free list鏈中。free list鏈現在象這樣:
67->54->10->45->46->65->80-|
2.4.2 Transaction Free List 算法
掃描segment Header塊中所有的Tx free list,檢查是否還沒有Tx free list entry分配給transaction, 如何沒有,將尋找未使用的entry或已經提交了事務的空的Tx free list。如果上述搜索過程失敗, 新的entry會在segment Header塊中Tx free lists區域中開闢。如果沒有空間來生成, 事務就必須等待entry的釋放。
segment header中的最大free list個數:
Block Size Max # Freelists
----------- -----------------
2K 24
4K 50
8K 101
16k 204
事務T1釋放出來的空閒塊(DELETE or UPDATE)的使用 :
l 立即被T1所重用
l 當T1 commit後被其它需要空閒塊的事務重用,過程舉例如下:
Block Size Max # Freelists
----------- -----------------
2K 24
4K 50
8K 101
16k 204
事務T1釋放出來的空閒塊(DELETE or UPDATE)的使用 :
l 立即被T1所重用
l 當T1 commit後被其它需要空閒塊的事務重用,過程舉例如下:
2.5 HMW概念
HIGH WATER MARK代表一個表使用的最大的(top limit)塊 。2.1中已經提到HIGH WATER MARK 記錄在segment header中,並且在Oracle插入數據時一般增長5個blocks(並非總是5個塊,具體參見2.4.2中流程圖中HMW增長方式)。
segment header block中與HWM相關信息說明如下:
EXTENT CONTROL:
Extent Header:: spare1: 0 space2: 0 #extents: 13 #blocks: 1429
last map 0x00000000 #maps: 0 offset: 4128
Highwater:: 0x020004d0 ext#: 12 blk#: 275 ext size: 475
#blocks in seg. hdr’s freelists: 5
#blocks below: 1229
mapblk 0x00000000 offset: 12
Unlocked
==> spare1: this field is no longer used (old inc#, now always 0)
==> space2: this field is no longer used (old ts#, now always 0)
==> #extents: number of extents allocated to segment
==> #blocks: number of blocks allocated to segment
segment header block中與HWM相關信息說明如下:
EXTENT CONTROL:
Extent Header:: spare1: 0 space2: 0 #extents: 13 #blocks: 1429
last map 0x00000000 #maps: 0 offset: 4128
Highwater:: 0x020004d0 ext#: 12 blk#: 275 ext size: 475
#blocks in seg. hdr’s freelists: 5
#blocks below: 1229
mapblk 0x00000000 offset: 12
Unlocked
==> spare1: this field is no longer used (old inc#, now always 0)
==> space2: this field is no longer used (old ts#, now always 0)
==> #extents: number of extents allocated to segment
==> #blocks: number of blocks allocated to segment
==> last map: address of last extent map block
0 if extent map is entirely in the segment header
==> #maps: number of extent map block
==> offset: offset to end of extent map
0 if extent map is entirely in the segment header
==> #maps: number of extent map block
==> offset: offset to end of extent map
==> HWM dba: address of block at highwater mark
==> ext#: HWM extent number relative to segment
==> blk#: HWM block number within extent
==> ext size: HWM extent size (in blocks)
==> #blocks in seg. hdr’s freelists: number of blocks in seg. hdr’s free list
==> #blocks below: number of blocks below HWM
==> mapblk dba: dba of extent map block containing HWM extent
is 0 if HWM is in the segment header
==> offset: offset within extent map block
is the ext# if HWM is in segment header
==> Locked by: if locked by a transaction, the xid is displayed
==> ext#: HWM extent number relative to segment
==> blk#: HWM block number within extent
==> ext size: HWM extent size (in blocks)
==> #blocks in seg. hdr’s freelists: number of blocks in seg. hdr’s free list
==> #blocks below: number of blocks below HWM
==> mapblk dba: dba of extent map block containing HWM extent
is 0 if HWM is in the segment header
==> offset: offset within extent map block
is the ext# if HWM is in segment header
==> Locked by: if locked by a transaction, the xid is displayed
HWM可以說是已經使用過的存儲空間和未使用過的存儲空間之間的分界線。在表使用過程中,HWM一直向一個方向移動,插入記錄時HWM可能會向增加的方向移動,但是刪除記錄時HWM並不會向相反的方向移動。參見2.4.2。下圖顯示了某個數據段中HWM的位置情況。
HIGH WATER MARK之所以重要是因爲它對全表掃描性能的影響。當實施一個全表掃描時,Oracle會讀取所有HIGH WATER MARK下的塊即使它們是空塊。當HIGH WATER MARK 下有很多unused block時實施全表掃描會增加額外的不必要的I/O。它也會在全局共享區中填充很多很多空塊
3.分析方法
存儲參數基本上屬於oracle internal的東西,因此oralce並沒有提供很好的手段來分析。但是對於DBA來說,還是可以通過block dump和DBMS_SPACE等手段來獲取部分信息。
3.1 提取block和free list信息
創建dbms_space使用的存儲過程show_space
SQL>
create or replace procedure show_space
( p_segname in varchar2,
p_owner in varchar2 default user,
p_type in varchar2 default 'TABLE',
p_partition in varchar2 default NULL )
as
l_free_blks number;
l_total_blocks number;
l_total_bytes number;
l_unused_blocks number;
l_unused_bytes number;
l_LastUsedExtFileId number;
l_LastUsedExtBlockId number;
l_last_used_block number;
procedure p( p_label in varchar2, p_num in number )
is
begin
dbms_output.put_line( rpad(p_label,40,'.') || p_num );
end;
begin
dbms_space.free_blocks
( segment_owner => p_owner,
segment_name => p_segname,
segment_type => p_type,
partition_name => p_partition,
freelist_group_id => 0,
free_blks => l_free_blks );
dbms_space.unused_space
( segment_owner => p_owner,
segment_name => p_segname,
segment_type => p_type,
partition_name => p_partition,
total_blocks => l_total_blocks,
total_bytes => l_total_bytes,
unused_blocks => l_unused_blocks,
unused_bytes => l_unused_bytes,
last_used_extent_file_id => l_LastUsedExtFileId,
last_used_extent_block_id => l_LastUsedExtBlockId,
last_used_block => l_last_used_block );
p( 'Free Blocks', l_free_blks );
p( 'Total Blocks', l_total_blocks );
p( 'Total Bytes', l_total_bytes );
p( 'Unused Blocks', l_unused_blocks );
p( 'Unused Bytes', l_unused_bytes );
p( 'Last Used Ext FileId', l_LastUsedExtFileId );
p( 'Last Used Ext BlockId', l_LastUsedExtBlockId );
p( 'Last Used Block', l_last_used_block );
end;
過程已創建。
create or replace procedure show_space
( p_segname in varchar2,
p_owner in varchar2 default user,
p_type in varchar2 default 'TABLE',
p_partition in varchar2 default NULL )
as
l_free_blks number;
l_total_blocks number;
l_total_bytes number;
l_unused_blocks number;
l_unused_bytes number;
l_LastUsedExtFileId number;
l_LastUsedExtBlockId number;
l_last_used_block number;
procedure p( p_label in varchar2, p_num in number )
is
begin
dbms_output.put_line( rpad(p_label,40,'.') || p_num );
end;
begin
dbms_space.free_blocks
( segment_owner => p_owner,
segment_name => p_segname,
segment_type => p_type,
partition_name => p_partition,
freelist_group_id => 0,
free_blks => l_free_blks );
dbms_space.unused_space
( segment_owner => p_owner,
segment_name => p_segname,
segment_type => p_type,
partition_name => p_partition,
total_blocks => l_total_blocks,
total_bytes => l_total_bytes,
unused_blocks => l_unused_blocks,
unused_bytes => l_unused_bytes,
last_used_extent_file_id => l_LastUsedExtFileId,
last_used_extent_block_id => l_LastUsedExtBlockId,
last_used_block => l_last_used_block );
p( 'Free Blocks', l_free_blks );
p( 'Total Blocks', l_total_blocks );
p( 'Total Bytes', l_total_bytes );
p( 'Unused Blocks', l_unused_blocks );
p( 'Unused Bytes', l_unused_bytes );
p( 'Last Used Ext FileId', l_LastUsedExtFileId );
p( 'Last Used Ext BlockId', l_LastUsedExtBlockId );
p( 'Last Used Block', l_last_used_block );
end;
過程已創建。