Oracle的內存配置與oracle性能息息相關。而且關於內存的錯誤(如4030、4031錯誤)都是十分令人頭疼的問題。可以說,關於內存的配置,是最影響Oracle性能的配置。內存還直接影響到其他兩個重要資源的消耗:CPU和IO。
首先,看看Oracle內存存儲的主要內容是什么:
- 程序代碼(PLSQL、Java);
- 關於已經連接的會話的信息,包括當前所有活動和非活動會話;
- 程序運行時必須的相關信息,例如查詢計划;
- Oracle進程之間共享的信息和相互交流的信息,例如鎖;
- 那些被永久存儲在外圍存儲介質上,被cache在內存中的數據(如redo log條目,數據塊)。
此外,需要記住的一點是,Oracle的內存是與實例對應的。也就是說,一個實例就有一個獨立的內存結構。
先從Oracle內存的組成架構介紹。
1. Oracle的內存架構組成
Oracle的內存,從總體上講,可以分為兩大塊:共享部分(主要是SGA)和進程獨享部分(主要是PGA和UGA)。而這兩部分內存里面,根據功能不同,還分為不同內存池(Pool)和內存區(Area)。下面就是Oracle內存構成框架圖:
SGA
|
||||||
|
下面分別介紹這兩塊內存區。
1.1. SGA(System Global Area)
SGA(System Global Area 系統全局區域)是一組包含一個Oracle實例的數據和控制信息的共享內存結構。這句話可以說是SGA的定義。雖然簡單,但其中闡述了SGA幾個很重要的特性:1、SGA的構成——數據和控制信息,我們下面會詳細介紹;2、SGA是共享的,即當有多個用戶同時登錄了這個實例,SGA中的信息可以被它們同時訪問(當涉及到互斥的問題時,由latch和enquence控制);3、一個SGA只服務於一個實例,也就是說,當一台機器上有多個實例運行時,每個實例都有一個自己的SGA,盡管SGA來自於OS的共享內存區,但實例之間不能相互訪問對方的SGA區。
Oracle進程和一個SGA就構成了一個Oracle實例。當實例啟動時,Oracle會自動從系統中分配內存給SGA,而實例關閉時,操作系統會回收這些內存。下面就是當實例啟動后,顯示已經分配了SGA:
SQL> startup
ORACLE instance started.
Total System Global Area 289406976 bytes
Fixed Size 1248576 bytes
Variable Size 117441216 bytes
Database Buffers 163577856 bytes
Redo Buffers 7139328 bytes
Database mounted.
Database opened.
SQL>
SGA區是可讀寫的。所有登錄到實例的用戶都能讀取SGA中的信息,而在oracle做執行操作時,服務進程會將修改的信息寫入SGA區。
SGA主要包括了以下的數據結構:
- 數據緩沖(Buffer Cache)
- 重做日志緩沖(Redo Log Buffer)
- 共享池(Shared Pool)
- Java池(Java Pool)
- 大池(Large Pool)
- 流池(Streams Pool --- 10g以后才有)
- 數據字典緩存(Data Dictionary Cache)
- 其他信息(如數據庫和實例的狀態信息)
最后的兩種內存信息會被實例的后台進程所訪問,它們在實例啟動后就固定在SGA中了,而且不會改變,所以這部分又稱為固定SGA(Fixed SGA)。這部分區域的大小一般小於100K。
此外,用於並非進程控制的鎖(latch)的信息也包含在SGA區中。
Shared Pool、Java Pool、Large Pool和Streams Pool這幾塊內存區的大小是相應系統參數設置而改變的,所以有通稱為可變SGA(Variable SGA)。
1.1.1. SGA的重要參數和特性
在設置SGA時,有一些很重要的參數,它們設置正確與否,會直接影響到系統的整體性能。下面一一介紹他們:
· SGA_MAX_SIZE
SGA區包括了各種緩沖區和內存池,而大部分都可以通過特定的參數來指定他們的大小。但是,作為一個昂貴的資源,一個系統的物理內存大小是有限。盡管對於CPU的內存尋址來說,是無需關系實際的物理內存大小的(關於這一點,后面會做詳細的介紹),但是過多的使用虛擬內存導致page in/out,會大大影響系統的性能,甚至可能會導致系統crash。所以需要有一個參數來控制SGA使用虛擬內存的最大大小,這個參數就是SGA_MAX_SIZE。
當實例啟動后,各個內存區只分配實例所需要的最小大小,在隨后的運行過程中,再根據需要擴展他們的大小,而他們的總和大小受到了SGA_MAX_SIZE的限制。
當試圖增加一個內存的大小,並且如果這個值導致所有內存區大小總和大於SGA_MAX_SIZE時,oracle會提示錯誤,不允許修改。
當然,如果在設置參數時,指定區域為spfile時(包括修改SGA_MAX_SIZE本身),是不會受到這個限制的。這樣就可能出現這樣的情況,在spfile中,SGA各個內存區設置大小總和大於SGA_MAX_SIZE。這時,oracle會如下處理:當實例再次啟動時,如果發現SGA各個內存總和大於SGA_MAX_SIZE,它會將SGA_MAX_SIZE的值修改為SGA各個內存區總和的值。
SGA所分配的是虛擬內存,但是,在我們配置SGA時,一定要使整個SGA區都在物理內存中,否則,會導致SGA頻繁的頁入/頁出,會極大影響系統性能。
對於OLTP系統,我個人建議可以如下配置SGA_MAX_SIZE(一般有經驗的DBA都會有自己的默認配置大小,你也可以通過一段時間的觀察、調整自己的系統來得到適合本系統的參數配置):
系統內存 |
SGA_MAX_SIZE值 |
1G |
400-500M |
2G |
1G |
4G |
2500M |
8G |
5G |
SGA的實際大小可以通過以下公式估算:
SGA實際大小 = DB_CACHE_SIZE + DB_KEEP_CACHE_SIZE + DB_RECYCLE_CACHE_SIZE + DB_nk_CACHE_SIZE + SHARED_POOL_SIZE + LARGE_POOL_SIZE + JAVA_POOL_SIZE + STREAMS_POOL_SIZE(10g中的新內存池) + LOG_BUFFERS+11K(Redo Log Buffer的保護頁) + 1MB + 16M(SGA內部內存消耗,適合於9i及之前版本)
公式種涉及到的參數在下面的內容種會一一介紹。
· PRE_PAGE_SGA
我們前面提到,oracle實例啟動時,會只載入各個內存區最小的大小。而其他SGA內存只作為虛擬內存分配,只有當進程touch到相應的頁時,才會置換到物理內存中。但我們也許希望實例一啟動后,所有SGA都分配到物理內存。這時就可以通過設置PRE_PAGE_SGA參數來達到目的了。
這個參數的默認值為FALSE,即不將全部SGA置入物理內存中。當設置為TRUE時,實例啟動會將全部SGA置入物理內存中。它可以使實例啟動達到它的最大性能狀態,但是,啟動時間也會更長(因為為了使所有SGA都置入物理內存中,oracle進程需要touch所有的SGA頁)。
我們可以通過TopShow工具(本站原創工具,可在http://www.HelloDBA.com/Download/TopShow.html中下載)來觀察windows(Unix下的內存監控比較復雜,這里暫不舉例)下參數修改前后的對比。
PRE_PAGE_SGA為FALSE:
SQL> show parameter sga
NAME TYPE VALUE
------------------------------------ ----------- --------------------------
lock_sga boolean FALSE
pre_page_sga boolean FALSE
sga_max_size big integer 276M
sga_target big integer 276M
SQL> startup force
ORACLE instance started.
Total System Global Area 289406976 bytes
Fixed Size 1248576 bytes
Variable Size 117441216 bytes
Database Buffers 163577856 bytes
Redo Buffers 7139328 bytes
Database mounted.
Database opened.
SQL>
啟動后,Oracle的內存情況
可以看到,實例啟動后,oracle占用的物理內存只有168M,遠小於SGA的最大值288M(實際上,這部分物理內存中還有一部分進程的PGA和Oracle Service占用的內存),而虛擬內存則為340M。
將PRE_PAGE_SGA修改為TRUE,重啟實例:
SQL> alter system set pre_page_sga=true scope=spfile;
System altered.
SQL> startup force
ORACLE instance started.
Total System Global Area 289406976 bytes
Fixed Size 1248576 bytes
Variable Size 117441216 bytes
Database Buffers 163577856 bytes
Redo Buffers 7139328 bytes
Database mounted.
Database opened.
再觀察啟動后Oracle的內存分配情況:
這時看到,實例啟動后物理內存達到了最大343M,於虛擬內存相當。這時,oracle實例已經將所有SGA分配到物理內存。
當參數設置為TRUE時,不僅在實例啟動時,需要touch所有的SGA頁,並且由於每個oracle進程都會訪問SGA區,所以每當一個新進程啟動時(在Dedicated Server方式中,每個會話都會啟動一個Oracle進程),都會touch一遍該進程需要訪問的所有頁。因此,每個進程的啟動時間頁增長了。所以,這個參數的設置需要根據系統的應用情況來設定。
在這種情況下,進程啟動時間的長短就由系統內存的頁的大小來決定了。例如,SGA大小為100M,當頁的大小為4K時,進程啟動時需要訪問100000/4=25000個頁,而如果頁大小為4M時,進程只需要訪問100/4=25個頁。頁的大小是由操作系統指定的,並且是無法修改的。
但是,要記住一點:PRE_PAGA_SGA只是在啟動時將物理內存分配給SGA,但並不能保證系統在以后的運行過程不會將SGA中的某些頁置換到虛擬內存中,也就是說,盡管設置了這個參數,還是可能出現Page In/Out。如果需要保障SGA不被換出,就需要由另外一個參數LOCK_SGA來控制了。
· LOCK_SGA
上面提到,為了保證SGA都被鎖定在物理內存中,而不必頁入/頁出,可以通過參數LOCK_SGA來控制。這個參數默認值為FALSE,當指定為TRUE時,可以將全部SGA都鎖定在物理內存中。當然,有些系統不支持內存鎖定,這個參數也就無效了。
· SGA_TARGET
這里要介紹的時Oracle10g中引入的一個非常重要的參數。在10g之前,SGA的各個內存區的大小都需要通過各自的參數指定,並且都無法超過參數指定大小的值,盡管他們之和可能並沒有達到SGA的最大限制。此外,一旦分配后,各個區的內存只能給本區使用,相互之間是不能共享的。拿SGA中兩個最重要的內存區Buffer Cache和Shared Pool來說,它們兩個對實例的性能影響最大,但是就有這樣的矛盾存在:在內存資源有限的情況下,某些時候數據被cache的需求非常大,為了提高buffer hit,就需要增加Buffer Cache,但由於SGA有限,只能從其他區“搶”過來——如縮小Shared Pool,增加Buffer Cache;而有時又有大塊的PLSQL代碼被解析駐入內存中,導致Shared Pool不足,甚至出現4031錯誤,又需要擴大Shared Pool,這時可能又需要人為干預,從Buffer Cache中將內存奪回來。
有了這個新的特性后,SGA中的這種內存矛盾就迎刃而解了。這一特性被稱為自動共享內存管理(Automatic Shared Memory Management ASMM)。而控制這一特性的,也就僅僅是這一個參數SGA_TARGE。設置這個參數后,你就不需要為每個內存區來指定大小了。SGA_TARGET指定了SGA可以使用的最大內存大小,而SGA中各個內存的大小由Oracle自行控制,不需要人為指定。Oracle可以隨時調節各個區域的大小,使之達到系統性能最佳狀態的個最合理大小,並且控制他們之和在SGA_TARGET指定的值之內。一旦給SGA_TARGET指定值后(默認為0,即沒有啟動ASMM),就自動啟動了ASMM特性。
設置了SGA_TARGET后,以下的SGA內存區就可以由ASMM來自動調整:
- 共享池(Shared Pool)
- Java池(Java Pool)
- 大池(Large Pool)
- 數據緩存區(Buffer Cache)
- 流池(Streams Pool)
對於SGA_TARGET的限制,它的大小是不能超過SGA_MAX_SIZE的大小的。
SQL> show parameter sga
NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
lock_sga boolean FALSE
pre_page_sga boolean FALSE
sga_max_size big integer 276M
sga_target big integer 276M
SQL>
SQL>
SQL>
SQL> alter system set sga_target=280M;
alter system set sga_target=280M
*
ERROR at line 1:
ORA-02097: parameter cannot be modified because specified value is invalid
ORA-00823: Specified value of sga_target greater than sga_max_size
另外,當指定SGA_TARGET小於SGA_MAX_SIZE,實例重啟后,SGA_MAX_SIZE就自動變為和SGA_TARGET一樣的值了。
SQL> show parameter sga
NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
lock_sga boolean FALSE
pre_page_sga boolean FALSE
sga_max_size big integer 276M
sga_target big integer 276M
SQL> alter system set sga_target=252M;
System altered.
SQL> show parameter sga
NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
lock_sga boolean FALSE
pre_page_sga boolean FALSE
sga_max_size big integer 276M
sga_target big integer 252M
SQL> startup force
ORACLE instance started.
Total System Global Area 264241152 bytes
Fixed Size 1248428 bytes
Variable Size 117441364 bytes
Database Buffers 138412032 bytes
Redo Buffers 7139328 bytes
Database mounted.
Database opened.
SQL> show parameter sga
NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
lock_sga boolean FALSE
pre_page_sga boolean FALSE
sga_max_size big integer 252M
sga_target big integer 252M
SQL>
對於SGA_TARGET,還有重要一點就是,它的值可以動態修改(在SGA_MAX_SIZE范圍內)。在10g之前,如果需要修改SGA的大小(即修改SGA_MAX_SIZE的值)需要重啟實例才能生效。當然,在10g中,修改SGA_MAX_SIZE的值還是需要重啟的。但是有了SGA_TARGET后,可以將SGA_MAX_SIZE設置偏大,再根據實際需要調整SGA_TARGET的值(我個人不推薦頻繁修改SGA的大小,SGA_TARGET在實例啟動時設置好,以后不要再修改)。
SGA_TARGET帶來一個重要的好處就是,能使SGA的利用率達到最佳,從而節省內存成本。因為ASMM啟動后,Oracle會自動根據需要調整各個區域的大小,大大減少了某些區域內存緊張,而某些區域又有內存空閑的矛盾情況出現。這也同時大大降低了出現4031錯誤的幾率。
· use_indirect_data_buffers
這個參數使32位平台使用擴展緩沖緩存基址,以支持支持4GB多物理內存。設置此參數,可以使SGA突破在32位系統中的2G最大限制。64位平台中,這個參數被忽略。
二.
1.1.2. 關於SGA的重要視圖
要了解和觀察SGA的使用情況,並且根據統計數據來處理問題和調整性能,主要有以下的幾個系統視圖。
· v$sga
這個視圖包括了SGA的的總體情況,只包含兩個字段:name(SGA內存區名字)和value(內存區的值,單位為字節)。它的結果和show sga的結果一致,顯示了SGA各個區的大小:
SQL> select * from v$sga;
NAME VALUE
-------------------- ----------
Fixed Size 1248428
Variable Size 117441364
Database Buffers 138412032
Redo Buffers 7139328
4 rows selected.
SQL> show sga
Total System Global Area 264241152 bytes
Fixed Size 1248428 bytes
Variable Size 117441364 bytes
Database Buffers 138412032 bytes
Redo Buffers 7139328 bytes
SQL>
· v$sgastat
這個視圖比較重要。它記錄了關於sga的統計信息。包含三個字段:Name(SGA內存區的名字);Bytes(內存區的大小,單位為字節);Pool(這段內存所屬的內存池)。
這個視圖尤其重要的是,它詳細記錄了個各個池(Pool)內存分配情況,對於定位4031錯誤有重要參考價值。
以下語句可以查詢Shared Pool空閑率:
SQL> select to_number(v$parameter.value) value, v$sgastat.BYTES,
2 (v$sgastat.bytes/v$parameter.value)*100 "percent free"
3 from v$sgastat, v$parameter
4 where v$sgastat.name= 'free memory'
5 and v$parameter.name = 'shared_pool_size'
6 and v$sgastat.pool='shared pool'
7 ;
VALUE BYTES percent free
---------- ---------- ------------
503316480 141096368 28.033329645
SQL>
· v$sga_dynamic_components
這個視圖記錄了SGA各個動態內存區的情況,它的統計信息是基於已經完成了的,針對SGA動態內存區大小調整的操作,字段組成如下:
字段 |
數據類型 |
描述 |
|
|
內存區名稱 |
|
|
當前大小 |
|
|
自從實例啟動后的最小值 |
|
|
自從實例啟動后的最大值 |
|
|
自從實例啟動后的調整次數 |
|
|
最后一次完成的調整動作,值包括:
|
|
|
最后一次完成的調整動作的模式,包括:
|
|
|
最后一次完成的調整動作的開始時間 |
|
|
GRANULE大小(關於granule后面詳細介紹) |
· V$SGA_DYNAMIC_FREE_MEMORY
這個視圖只有一個字段,一條記錄:當前SGA可用於動態調整SGA內存區的空閑區域大小。它的值相當於(SGA_MAX_SIZE – SGA各個區域設置大小的總和)。當設置了SGA_TARGET后,它的值一定為0(為什么就不需要我再講了吧^_^)。
下面的例子可以很清楚的看到這個視圖的作用:
SQL> select * from v$sga_dynamic_free_memory;
CURRENT_SIZE
--------------
0
SQL> show parameter shared_pool
NAME TYPE VALUE
----------------------------------- ----------- ----------
shared_pool_size big integer 50331648
SQL> alter system set shared_pool_size=38M;
system altered.
SQL> show parameter shared_pool
NAME TYPE VALUE
----------------------------------- ----------- ----------
shared_pool_size big integer 41943040
SQL> select * from v$sga_dynamic_free_memory;
CURRENT_SIZE
--------------
8388608
1.1.3. 數據庫緩沖區(Database Buffers)
Buffer Cache是SGA區中專門用於存放從數據文件中讀取的的數據塊拷貝的區域。Oracle進程如果發現需要訪問的數據塊已經在buffer cache中,就直接讀寫內存中的相應區域,而無需讀取數據文件,從而大大提高性能(要知道,內存的讀取效率是磁盤讀取效率的14000倍)。Buffer cache對於所有oracle進程都是共享的,即能被所有oracle進程訪問。
和Shared Pool一樣,buffer cache被分為多個集合,這樣能夠大大降低多CPU系統中的爭用問題。
1.1.3.1. Buffer cache的管理
Oracle對於buffer cache的管理,是通過兩個重要的鏈表實現的:寫鏈表和最近最少使用鏈表(the Least Recently Used LRU)。寫鏈表所指向的是所有臟數據塊緩存(即被進程修改過,但還沒有被回寫到數據文件中去的數據塊,此時緩沖中的數據和數據文件中的數據不一致)。而LRU鏈表指向的是所有空閑的緩存、pin住的緩存以及還沒有來的及移入寫鏈表的臟緩存。空閑緩存中沒有任何有用的數據,隨時可以使用。而pin住的緩存是當前正在被訪問的緩存。LRU鏈表的兩端就分別叫做最近使用端(the Most Recently Used MRU)和最近最少使用端(LRU)。
· Buffer cache的數據塊訪問
當一個Oracle進程訪問一個緩存是,這個進程會將這塊緩存移到LRU鏈表中的MRU。而當越來越多的緩沖塊被移到MRU端,那些已經過時的臟緩沖(即數據改動已經被寫入數據文件中,此時緩沖中的數據和數據文件中的數據已經一致)則被移到LRU鏈表中LRU端。
當一個Oracle用戶進程第一次訪問一個數據塊時,它會先查找buffer cache中是否存在這個數據塊的拷貝。如果發現這個數據塊已經存在於buffer cache(即命中cache hit),它就直接讀從內存中取該數據塊。如果在buffer cache中沒有發現該數據塊(即未命中cache miss),它就需要先從數據文件中讀取該數據塊到buffer cache中,然后才訪問該數據塊。命中次數與進程讀取次數之比就是我們一個衡量數據庫性能的重要指標:buffer hit ratio(buffer命中率),可以通過以下語句獲得自實例啟動至今的buffer命中率:
SQL> select 1-(sum(decode(name, 'physical reads', value, 0))/
2 (sum(decode(name, 'db block gets', value, 0))+
3 (sum(decode(name, 'consistent gets', value, 0))))) "Buffer Hit Ratio"
4 from v$sysstat;
Buffer Hit Ratio
----------------
.926185625
1 row selected.
SQL>
根據經驗,一個良好性能的系統,這一值一般保持在95%左右。
上面提到,如果未命中(missed),則需要先將數據塊讀取到緩存中去。這時,oracle進程需要從空閑列表種找到一個適合大小的空閑緩存。如果空閑列表中沒有適合大小的空閑buffer,它就會從LRU端開始查找LRU鏈表,直到找到一個可重用的緩存塊或者達到最大查找塊數限制。在查找過程中,如果進程找到一個臟緩存塊,它將這個緩存塊移到寫鏈表中去,然后繼續查找。當它找到一個空閑塊后,就從磁盤中讀取數據塊到緩存塊中,並將這個緩存塊移到LRU鏈表的MRU端。
當有新的對象需要請求分配buffer時,會通過內存管理模塊請求分配空閑的或者可重用的buffer。“free buffer requested”就是產生這種請求的次數;
當請求分配buffer時,已經沒有適合大小的空閑buffer時,需要從LRU鏈表上獲取到可重用的buffer。但是,LRU鏈表上的buffer並非都是立即可重用的,還會存在一些塊正在被讀寫或者已經被別的用戶所等待。根據LRU算法,查找可重用的buffer是從鏈表的LRU端開始查找的,如果這一段的前面存在這種不能理解被重用的buffer,則需要跳過去,查找鏈表中的下一個buffer。“free buffer inspected”就是被跳過去的buffer的數目。
如果Oracle用戶進程達到查找塊數限制后還沒有找到空閑緩存,它就停止查找LRU鏈表,並且通過信號同志DBW0進程將臟緩存寫入磁盤去。
下面就是oracle用戶進程訪問一個數據塊的偽代碼:
user_process_access_block(block)
{
if (search_lru(block))
{
g_cache_hit++;
return read_block_from_buffer_cache(block);
}
else
{
g_cache_missed++;
search_count = 1;
searched = FALSE;
set_lru_latch_context();
buffer_block = get_lru_from_lru();
do
{
if (block == buffer_block)
{
set_buffer_block(buffer_block, read_block_from_datafile(block);
move_buffer_block_to_mru(buffer_block);
searched = TRUE;
}
search_count++;
buffer_block = get_next_from_lru(buffer_block);
}while(!searched && search_count < BUFFER_SEARCH_THRESHOLD)
free_lru_latch_context();
if (!searched)
{
buffer_block = signal_dbw0_write_dirty_buffer();
set_buffer_block(buffer_block, read_block_from_datafile(block);
move_buffer_block_to_mru(buffer_block);
}
return buffer_block;
}
}
· 全表掃描
當發生全表掃描(Full Table Scan)時,用戶進程讀取表的數據塊,並將他們放在LRU鏈表的LRU端(和上面不同,不是放在MRU端)。這樣做的目的是為了使全表掃描的數據盡快被移出。因為全表掃描一般發生的頻率較低,並且全表掃描的數據塊大部分在以后都不會被經常使用到。
而如果你希望全表掃描的數據能被cache住,使之在掃描時放在MRU端,可以通過在創建或修改表(或簇)時,指定CACHE參數。
· Flush Buffer
回顧一下前面一個用戶進程訪問一個數據塊的過程,如果訪問的數據塊不在buffer cache中,就需要掃描LRU鏈表,當達到掃描塊數限制后還沒有找到空閑buffer,就需要通知DBW0將臟緩存回寫到磁盤。分析一下偽代碼,在這種情況下,用戶進程訪問一個數據塊的過程是最長的,也就是效率最低的。如果一個系統中存在大量的臟緩沖,那么就可能導致用戶進程訪問數據性能下降。
我們可以通過人工干預將所有臟緩沖回寫到磁盤去,這就是flush buffer。
在9i,可以用以下語句:
alter system set events = 'immediate trace name flush_cache'; --9i
在10g,可以用以下方式(9i的方式在10g仍然有效):
alter system flush buffer_cache; -- 10g
另外,9i的設置事件的方式可以是針對系統全部的,也可以是對會話的(即將該會話造成的臟緩沖回寫)。
1.1.3.2. Buffer Cache的重要參數配置
Oracle提供了一些參數用於控制Buffer Cache的大小等特性。下面介紹一下這些參數。
· Buffer Cache的大小配置
由於Buffer Cache中存放的是從數據文件中來的數據塊的拷貝,因此,它的大小的計算也是以塊的尺寸為基數的。而數據塊的大小是由參數db_block_size指定的。9i以后,塊的大小默認是8K,它的值一般設置為和操作系統的塊尺寸相同或者它的倍數。
而參數db_block_buffers則指定了Buffer Cache中緩存塊數。因此,buffer cache的大小就等於db_block_buffers * db_block_size。
在9i以后,Oracle引入了一個新參數:db_cache_size。這個參數可以直接指定Buffer Cache的大小,而不需要通過上面的方式計算出。它的默認值48M,這個數對於一個系統來說一般是不夠用的。
注意:db_cache_size和db_block_buffers是不能同時設置的,否則實例啟動時會報錯。
SQL> alter system set db_block_buffers=16384 scope=spfile;
system altered.
SQL> alter system set db_cache_size=128M scope=spfile;
system altered.
SQL> startup force
ORA-00381: cannot use both new and old parameters for buffer cache size specification
9i以后,推薦使用db_cache_size來指定buffer cache的大小。
在OLTP系統中,對於DB_CACHE_SIZE的設置,我的推薦配置是:
DB_CACHE_SIZE = SGA_MAX_SIZE/2 ~ SGA_MAX_SIZE*2/3
最后,DB_CACHE_SIZE是可以聯機修改的,即實例無需重啟,除非增大Buffer Cache導致SGA實際大小大於SGA_MAX_SIZE。
· 多種塊尺寸系統中的Buffer Cache的配置
從9i開始,Oracle支持創建不同塊尺寸的表空間,並且可以為不同塊尺寸的數據塊指定不同大小的buffer cache。
9i以后,除了SYSTEM表空間和TEMPORARY表空間必須使用標准塊尺寸外,所有其他表空間都可以最多指定四種不同的塊尺寸。而標准塊尺寸還是由上面的所說的參數db_block_size來指定。而db_cache_size則是標致塊尺寸的buffer cache的大小。
非標准塊尺寸的塊大小可以在創建表空間(CREATE TABLESPACE)是通過BLOCKSIZE參數指定。而不同塊尺寸的buffer cache的大小就由相應參數DB_nK_CACHE_SZIE來指定,其中n可以是2,4,8,16或者32。例如,你創建了一個塊大小為16K的非標准塊尺寸的表空間,你就可以通過設置DB_16K_CACHE_SIZE為來指定緩存這個表空間數據塊的buffer cache的大小。
任何一個尺寸的Buffer Cache都是不可以緩存其他尺寸的數據塊的。因此,如果你打算使用多種塊尺寸用於你的數據庫的存儲,你必須最少設置DB_CACHE_SIZE和DB_nK_CACHE_SIZE中的一個參數(10g后,指定了SGA_TARGET就可以不需要指定Buffer Cache的大小)。並且,你需要給你要用到的非標准塊尺寸的數據塊指定相應的Buffer Cache大小。這些參數使你可以為系統指定多達4種不同塊尺寸的Buffer Cache。
另外,請注意一點,DB_nK_CACHE_SIZE 參數不能設定標准塊尺寸的緩沖區大小。舉例來說,如果 DB_BLOCK_SIZE 設定為 4K,就不能再設定 DB_4K_CACHE_SIZE 參數。
· 多緩沖池
你可以配置不同的buffer cache,可以達到不同的cache數據的目的。比如,可以設置一部分buffer cache緩存過的數據在使用后后馬上釋放,使后來的數據可以立即使用緩沖池;還可以設置數據進入緩沖池后就被keep住不再釋放。部分數據庫對象(表、簇、索引以及分區)可以控制他們的數據緩存的行為,而這些不同的緩存行為就使用不同緩沖池。
o 保持緩沖池(Keep Buffer Pool)用於緩存那些永久駐入內存的數據塊。它的大小由參數DB_KEEP_CACHE_SZIE控制;
o 回收緩沖池(Recycle Buffer Pool)會立即清除那些不在使用的數據緩存塊。它的大小由參數DB_RECYLE_CACHE_SIZE指定;
o 默認的標准緩存池,也就是上面所說的DB_CACHE_SIZE指定。
這三個參數相互之間是獨立的。並且他們都只適用於標准塊尺寸的數據塊。與8i兼容參數DB_BLOCK_BUFFERS相應的,DB_KEEP_CACHE_SIZE對應有BUFFER_POOL_KEEP、DB_RECYLE_CACHE_SIZE對應有BUFFER_POOL_RECYCLE。同樣,這些參數之間是互斥的,即DB_KEEP_CACHE_SIZE和BUFFER_POOL_KEEP之間只能設置一個。
· 緩沖池建議器
從9i開始,Oracle提供了一些自動優化工具,用於調整系統配置,提高系統性能。建議器就是其中一種。建議器的作用就是在系統運行過程中,通過監視相關統計數據,給相關配置在不同情況下的性能效果,提供給DBA做決策,以選取最佳的配置。
9i中,Buffer Cache就有了相應的建議器。參數db_cache_advice用於該建議器的開關,默認值為FALSE(即關)。當設置它為TRUE后,在系統運行一段時間后,就可以查詢視圖v$db_cache_advice來決定如何使之DB_CACHE_SIZE了。關於這個建議器和視圖,我們會在下面的內容中介紹。
· 其他相關參數
DB_BLOCK_LRU_LATCHES
LRU鏈表作為一個內存對象,對它的訪問是需要進行鎖(latch)控制的,以防止多個用戶進程同時使用一個空閑緩存塊。DB_BLOCK_LRU_LATCHES設置了LUR latch的數量范圍。Oracle通過一系列的內部檢測來決定是否使用這個參數值。如果這個參數沒有設置,Oracle會自動為它計算出一個值。一般來說,oracle計算出來的值是比較合理,無需再去修改。
9i以后這個參數是隱含參數。對於隱含參數,我建議在沒有得到Oracle支持的情況下不要做修改,否則,如果修改了,Oracle是可以拒絕為你做支持的。
DB_WRITER_PROCESSES
在前面分析Oracle讀取Buffer Cache時,提到一個Oracle重要的后台進程DBW0,這個(或這些)進程負責將臟緩存塊寫回到數據文件種去,稱為數據庫書寫器進程(Database Writer Process)。DB_WRITER_PROCESSES參數配置寫進程的個數,各個進程以DBWn區分,其中n>=0,是進程序號。一般情況下,DB_WRITER_PROCESSES = MAX(1, TRUNC(CPU數/8))。也就是說,CPU數小於8時,DB_WRITER_PROCESSES為1,即只有一個寫進程DBW0。這對於一般的系統來說也是足夠用。當你的系統的修改數據的任務很重,並且已經影響到性能時,可以調整這個參數。這個參數不要超過CPU數,否則多出的進程也不會起作用,另外,它的最大值不能超過20。
DBWn進程除了上面提到的在用戶進程讀取buffer cache時會被觸發,還能被Checkpoint觸發(Checkpoint是實例從redo log中做恢復的起始點)。
1.1.3.3. Buffer Cache的重要視圖
關於Buffer Cache,oracle提供一些重要視圖,用於查詢關於Buffer Cache的重要信息,為調整Buffer Cache、提高性能提供參考。下面一一介紹它們
· v$db_cache_advice
上面我們提到了Oracle的建議器,其中有一個針對Buffer Cache的建議器。在我們設置了參數db_cache_advice為TRUE后,經過一段時間的系統運行,Oracle收集到相關統計數據,並根據一定的數學模型,預測出DB_CACHE_SIZE在不同大小情況的性能數據。我們就可以由視圖V$DB_CACHE_ADVICE查出這些數據,並根據這些數據調整DB_CACHE_SZIE,使系統性能最優。
下面是關於這個視圖的結構描述:
字段 |
數據類型 |
描述 |
ID |
NUMBER |
緩沖池標識號(從1到8,1-6對應於DB_nK_CACHE_SIZE,DB_CACHE_SIZE與系統標准塊尺寸的序號相關,如DB_BLOCK_SIZE為8K,則DB_CACHE_SIZE的標識號為3(2,4,8…)。7是DB_KEEP_CACHE_SIZE,8是DB_RECYCLE_CACHE_SIZE) |
NAME |
VARCHAR2(20) |
緩沖池名稱 |
BLOCK_SIZE |
NUMBER |
緩沖池塊尺寸(字節為單位) |
ADVICE_STATUS |
VARCHAR2(3) |
建議器狀態:ON表示建議器在運行;OFF表示建議器已經關閉。當建議器關閉了,視圖中的數據是上一次打開所統計得出的。 |
SIZE_FOR_ESTIMATE |
NUMBER |
預測性能數據的Cache大小(M為單位) |
SIZE_FACTOR |
NUMBER |
預測的Cache大小因子(即與當前大小的比例) |
BUFFERS_FOR_ESTIMATE |
NUMBER |
預測性能數據的Cache大小(緩沖塊數) |
ESTD_PHYSICAL_READ_FACTOR |
NUMBER |
這一緩沖大小時,物理讀因子,它是如果緩沖大小為SIZE_FOR_ESTIMATE時,建議器預測物理讀數與當前實際物理讀數的比率值。如果當前物理讀數為0,這個值為空。 |
ESTD_PHYSICAL_READS |
NUMBER |
如果緩沖大小為SIZE_FOR_ESTIMATE時,建議器預測物理讀數。 |
下面是從這個視圖中查詢出來的數據:
SQL> select size_for_estimate, estd_physical_read_factor, estd_physical_reads
2 from v$db_cache_advice
3 where name = 'DEFAULT';
SIZE_FOR_ESTIMATE ESTD_PHYSICAL_READ_FACTOR ESTD_PHYSICAL_READS
----------------- ------------------------- -------------------
16 2.0176 6514226
32 1.7403 5619048
48 1.5232 4917909
64 1.3528 4367839
80 1.2698 4099816
96 1.1933 3852847
112 1.1443 3694709
128 1.1007 3553685
144 1.0694 3452805
160 1.0416 3362964
176 1.0175 3285085
192 1 3228693
208 0.9802 3164754
224 0.9632 3109920
240 0.9395 3033427
256 0.8383 2706631
272 0.7363 2377209
288 0.682 2202116
304 0.6714 2167888
320 0.6516 2103876
20 rows selected
當前我們的DB_CACHE_SIZE為192M,可以看到,它的物理讀因子為1,物理讀數為3228693。那么如何根據這些數據調整DB_CACHE_SIZE呢?給出一個方法,找到變化率較平緩的點作為采用值。因為建議器做預測是,DB_CACHE_SIZE的預測值的增長步長是相同的,是16M。我們按照這一步長增加DB_CACHE_SIZE,如果每次增加物理讀降低都很明顯,就可以繼續增加,直到物理讀降低不明顯,說明繼續增加DB_CACHE_SIZE沒有太大作用。當然,性能和可用資源是天平的兩端,你需要根據自己系統的實際情況調整。
上面的例子中,我們可以考慮將DB_CACHE_SIZE調整到288M。因為在288M之前,物理讀因子變化都比較大,而從288M到304M以后,這個因子變化趨緩。用一個二維圖可以更容易看出這個變化來:
這一視圖作為調整DB_CACHE_SIZE以提高性能有很大參考價值。但衡量Buffer Cache是否合適的重要指標還是我們前面提到的緩存命中率(Buffer Hit),而影響緩存命中率往往還有其他因素,如性能極差的SQL語句。
· V$BUFFER_POOL
這一視圖顯示了當前實例中所有緩沖池的信息。它的結構如下:
字段 |
數據類型 |
描述 |
ID |
NUMBER |
緩沖池ID,和上面視圖描述相同。 |
NAME |
VARCHAR2(20) |
緩沖池名稱 |
BLOCK_SIZE |
NUMBER |
緩沖池塊尺寸(字節為單位) |
RESIZE_STATE |
VARCHAR2(10) |
緩沖池當前狀態。 STATIC:沒有被正在調整大小 ALLOCATING:正在分配內存給緩沖池(不能被用戶取消) ACTIVATING:正在創建新的緩存塊(不能被用戶取消) SHRINKING:正在刪除緩存塊(能被用戶取消) |
CURRENT_SIZE |
NUMBER |
緩沖池大小(M為單位) |
BUFFERS |
NUMBER |
當前緩存塊數 |
TARGET_SIZE |
NUMBER |
如果正在調整緩沖池大小(即狀態不為STATIC),這記錄了調整后的大小(M為單位)。如果狀態為STATIC,這個值和當前大小值相同。 |
TARGET_BUFFERS |
NUMBER |
如果正在調整緩沖池大小(即狀態不為STATIC),這記錄了調整后的緩存塊數。否則,這個值和當前緩存塊數相同。 |
PREV_SIZE |
NUMBER |
前一次調整的緩沖池大小。如果從來沒有調整過,則為0。 |
PREV_BUFFERS |
NUMBER |
前一次調整的緩存塊數。如果從來沒有調整過,則為0。 |
LO_BNUM |
NUMBER |
9i后已經廢棄字段 |
HI_BNUM |
NUMBER |
9i后已經廢棄字段 |
LO_SETID |
NUMBER |
9i后已經廢棄字段 |
HI_SETID |
NUMBER |
9i后已經廢棄字段 |
SET_COUNT |
NUMBER |
9i后已經廢棄字段 |
· v$buffer_pool_statistics
V$BUFFER_POOL_STATISTICS視圖記錄了所有緩沖池的統計數據。它的結構如下:
字段 |
數據類型 |
描述 |
ID |
NUMBER |
緩沖池ID,和上面視圖描述相同。 |
NAME |
VARCHAR2(20) |
緩沖池名稱 |
SET_MSIZE |
NUMBER |
緩沖池中緩存塊的最大數 |
CNUM_REPL |
NUMBER |
在置換列表中的緩存塊數 |
CNUM_WRITE |
NUMBER |
在寫列表中的緩存塊數 |
CNUM_SET |
NUMBER |
當前的緩存塊數 |
BUF_GOT |
NUMBER |
讀取過的緩存塊數 |
SUM_WRITE |
NUMBER |
被寫過的緩存塊數 |
SUM_SCAN |
NUMBER |
被掃描過的緩存塊數 |
FREE_BUFFER_WAIT |
NUMBER |
等待空閑塊統計數 |
WRITE_COMPLETE_WAIT |
NUMBER |
等待完成寫統計數 |
BUFFER_BUSY_WAIT |
NUMBER |
忙(正在被使用)等待統計數 |
FREE_BUFFER_INSPECTED |
NUMBER |
確認了的空閑緩存塊數(即可用的) |
DIRTY_BUFFERS_INSPECTED |
NUMBER |
確認了的臟緩存塊數 |
DB_BLOCK_CHANGE |
NUMBER |
被修改過的數據塊數 |
DB_BLOCK_GETS |
NUMBER |
讀取過的數據塊數 |
CONSISTENT_GETS |
NUMBER |
一致性讀統計數 |
PHYSICAL_READS |
NUMBER |
物理讀統計數 |
PHYSICAL_WRITES |
NUMBER |
物理寫統計數 |
查看當前的Buffer Cache命中率:
SQL> select 1-(physical_reads)/(consistent_gets+db_block_gets)
2 from v$buffer_pool_statistics;
1-(PHYSICAL_READS)/(CONSISTENT
------------------------------
0.967658520581074
SQL>
· v$bh
這一視圖在深入定位緩沖區問題時很有用。它記錄了緩沖區中所有數據塊對象。粒度非常細。這個視圖最初的目的是用於OPS(Oracle Parallel Server Oracle平行服務器,9i后稱為RAC)的,是用於保證RAC中各個節點的數據一致性的。但是,我們可以通過它來查詢Buffer Cache的使用情況,找出大量消耗Buffer Cache的對象。下面的語句就可以完成這一工作:
SQL> column c0 heading 'Owner' format a15
SQL> column c1 heading 'Object|Name' format a30
SQL> column c2 heading 'Number|of|Buffers' format 999,999
SQL> column c3 heading 'Percentage|ofData|Buffer' format 999,999,999
SQL> select
2 owner c0,
3 object_name c1,
4 count(1) c2,
5 (count(1)/(select count(*) from v$bh)) *100 c3
6 from
7 dba_objects o,
8 v$bh bh
9 where
10 o.object_id = bh.objd
11 and
12 o.owner not in ('SYS','SYSTEM')
13 group by
14 owner,
15 object_name
16 order by
17 count(1) desc
18 ;
C0 C1 C2 C3
--------------- ------------------------------ ---------- ----------
PLSQLDEV STANDARD_CITY 17290 72.5860621
DBOWNER MSG_LOG 2 0.00839630
DBOWNER COUNTRY_PK 1 0.00419815
DBOWNER PARAMETER 1 0.00419815
DBOWNER PARAMETER_PK 1 0.00419815
DBOWNER MSG_LOG_IDX1 1 0.00419815
6 rows selected
SQL>
更重要的是,這個視圖記錄的粒度非常細,一條記錄對應了一個數據塊。這對於我們做內存問題分析或分析Oracle行為時很有幫助。
下面是這個視圖的結構:
字段 |
數據類型 |
說明 |
FILE# |
NUMBER |
緩存塊對應的數據塊所在的數據文件號。可以通過視圖DBA_DATA_FILES或V$DBFILES查詢 |
BLOCK# |
NUMBER |
緩存塊對應的數據塊編號 |
CLASS# |
NUMBER |
分類編號 |
STATUS |
VARCHAR2(1) |
緩存塊的狀態 FREE:空閑,沒有被使用 XCUR:排斥(正在被使用) SCUR:可被共享 CR:一致性讀 READ:正在從磁盤讀入 MREC:處於從存儲介質恢復狀態 IREC:處於實例恢復狀態 |
XNC |
NUMBER |
緩存塊上由於和其他實例爭用導致的PCM(Parallel Cache Management並行緩存管理)x to null鎖的數量。這一字段已經被廢棄。 |
LOCK_ELEMENT_ADDR |
RAW(4 | 8) |
緩存塊上PCM鎖的地址。如果多個緩存塊的PCM鎖地址相同,說明他們被同一鎖鎖住。 |
LOCK_ELEMENT_NAME |
NUMBER |
緩存塊上PCM鎖的地址。如果多個緩存塊的PCM鎖地址相同,說明他們被同一鎖鎖住。 |
LOCK_ELEMENT_CLASS |
NUMBER |
緩存塊上PCM鎖的地址。如果多個緩存塊的PCM鎖地址相同,說明他們被同一鎖鎖住。 |
FORCED_READS |
NUMBER |
由於其他實例的PCM鎖鎖住了該緩存塊,導致當前實例嘗試重新請求讀該緩沖塊的次數。 |
FORCED_WRITES |
NUMBER |
由於其他實例的PCM鎖鎖住了該緩存塊,導致當前實例嘗試重新請求寫該緩沖塊的次數。 |
DIRTY |
VARCHAR2(1) |
臟標志:Y – 塊被修改過,是臟塊;N – 不是臟塊 |
TEMP |
VARCHAR2(1) |
是否為臨時塊:Y – 是;N – 否。 |
PING |
VARCHAR2(1) |
是否被ping住:Y – 是;N – 否。 |
STALE |
VARCHAR2(1) |
是否是陳舊塊:Y – 是;N – 否。 |
DIRECT |
VARCHAR2(1) |
是否為直接讀寫塊:Y – 是;N – 否。 |
NEW |
VARCHAR2(1) |
字段被廢棄,始終為N |
OBJD |
NUMBER |
數據塊所屬對象的對象標號,可以查詢dba_objects |
TS# |
NUMBER |
數據塊所在的表空間號,可以查詢v$tablespaces |
1.1.4. 共享池(Shared pool)
SGA中的共享池由庫緩存(Library Cache)、字典緩存(Dictionary Cache)、用於並行執行消息的緩沖以及控制結構組成。
Shared Pool的大小由參數SHARED_POOL_SIZE決定。在32位系統中,這個參數的默認值是8M,而64位系統中的默認值位64M。最大為4G。
對於Shared Pool的內存管理,是通過修正過的LRU算法表來實現的。
下面分別介紹Shared Pool的幾個組成部分。
1.1.4.1. 庫緩存(Library Cache)
Library Cache中包括共享SQL區(Shared SQL Areas)、PL/SQL存儲過程和包以及控制結構(如鎖、庫緩存句柄)。
任何用戶都可以訪問共享SQL區(可以通過v$sqlarea訪問,隨后會介紹這個重要視圖)。因此庫緩存存在於SGA的共享池中。
· 共享SQL區和私有SQL區
Oracle會為每一條SQL語句運行(每運行一條語句Oracle都會打開一個游標)提供一個共享SQL區(Shared SQL Areas)和私有SQL區(Private SQL Areas屬於PGA)。當發現兩個(或多個)用戶都在運行同一SQL語句時,Oracle會重新組織SQL區,使這些用戶能重用共享SQL區。但他們還會在私有SQL區中保存一份這條SQL語句的拷貝。
一個共享SQL區中保存了一條語句的解析樹和查詢計划。在多用戶系統中,Oracle通過為SQL語句使用同一共享SQL區多次運行來節省內存。
當一條新的SQL語句被解析時,Oracle從共享池中分配一塊內存來存儲共享SQL區。這塊內存的大小與這條語句的復雜性相關。如果Shared Pool不夠空間分配給共享SQL區,Oracle將釋放從LRU鏈表中查找到最近最少使用的內存塊,直到有足夠空間給新的語句的共享SQL區。如果Oracle釋放的是一個共享SQL區的內存,那么相應的語句在下次執行時需要再次解析並重新分配共享SQL區。而從解析語句到分配共享SQL區是一個比較消耗CPU的工程。這就是為什么我們提倡使用綁定變量的原因了。在沒有使用綁定變量時,語句中的變量的數值不同,oracle就視為一條新的語句(9i后可以通過cursor_sharing來控制),重復上面的解析、內存分配的動作,將大大消耗系統資源,降低系統性能。
· PL/SQL程序單元
Oracle對於PL/SQL程序單元(存儲過程、函數、包、匿名PL/SQL塊和觸發器)的處理過程和對單個的SQL語句的處理過程相似。它會分配一個共享區來存儲被解析、編譯過的程序單元。同時分配一個私有區域來存放運行程序單元的會話所指定的程序單元的參數值(包括本地變量、全局變量和包變量——這也叫做包的實例化)和用於執行程序所需的內存。如果多個用戶運行同一個程序單元,則他們共享同一個共享區域,並且各自保持一份私有區域,用於用戶會話中指定的變量值。
而一個PL/SQL程序單元中的每條單個SQL語句的處理過程則和上面描述的SQL語句的處理過程相同。要注意一點,盡管這些語句是從PL/SQL程序單元中來的,但是Oracle還是會為這些語句分配一塊共享SQL區,同時為每個用戶分配一個相應的私有SQL區。
1.1.4.2. 字典緩存(Dictionary Cache)
數據字典是有關於數據庫的參考信息、數據庫的結構信息和數據庫中的用戶信息的一組表和視圖的集合,如我們常用到的V$視圖、DBA_視圖都屬於數據字典。在SQL語句解析的過程中,Oracle可以非常迅速的訪問(如果需要的話)這些數據字典,在SQL Trace中,這種對數據字典的訪問就被統計為回調(recursive calls)。看下面例子:
第一調用語句,需要做硬解析:
SQL> select * from T_COMPANY;
9999 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 3356521258
-------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10000 | 156K| 9 (0)| 00:00:01 |
| 1 | TABLE ACCESS FULL| T_COMPANY | 10000 | 156K| 9 (0)| 00:00:01 |
-------------------------------------------------------------------------------
Statistics
----------------------------------------------------------
355 recursive calls
0 db block gets
764 consistent gets
39 physical reads
116 redo size
305479 bytes sent via SQL*Net to client
7711 bytes received via SQL*Net from client
668 SQL*Net roundtrips to/from client
5 sorts (memory)
0 sorts (disk)
9999 rows processed
可以看到,Recursive Calls高達355。第二次調用,無需解析,直接使用共享SQL區中緩存:
SQL> /
9999 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 3356521258
-------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 10000 | 156K| 9 (0)| 00:00:01 |
| 1 | TABLE ACCESS FULL| T_COMPANY | 10000 | 156K| 9 (0)| 00:00:01 |
-------------------------------------------------------------------------------
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
705 consistent gets
0 physical reads
0 redo size
305479 bytes sent via SQL*Net to client
7711 bytes received via SQL*Net from client
668 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
9999 rows processed
由於沒做解析,這時recursive calls為0。
當然,recursive calls並不僅僅發生在解析的時候。由於數據字典記錄了所有對象的結構、數據信息,因此在對象結構、數據發生變化時都會訪問數據字典:
SQL> delete from t_company where rownum=1;
1 row deleted.
...
Statistics
----------------------------------------------------------
360 recursive calls
...
SQL> /
1 row deleted.
...
Statistics
----------------------------------------------------------
4 recursive calls
...
SQL> /
...
Statistics
----------------------------------------------------------
4 recursive calls
...
可以看到,上面的delete語句在第一次執行時,包括因解析和數據改動導致對數據字典的訪問,因此recursive calls較高,為360。在隨后的執行中,因為沒有做解析,所以recursive calls大大減少,只有4,而這4個recursive calls是因為數據改變而需要對數據字典的訪問。
因為Oracle對數據字典訪問如此頻繁,因此內存中有兩處地方被專門用於存放數據字典。一個地方就是數據字典緩存(Data Dictionary Cache)。數據字典緩存也被稱為行緩存(Row Cache),因為它是以記錄行為單元存儲數據的,而不像Buffer Cache是以數據塊為單元存儲數據。內存中另外一個存儲數據字典的地方是庫緩存。所有Oracle的用戶都可以訪問這兩個地方以獲取數據字典信息。
1.1.4.3. 共享池的內存管理
通常來說,共享池是根據修正過的LRU算法來是否其中的對象(共享SQL區和數據自動記錄行)的,否則這些對象就一直保持在共享池中。如果共享池需要為一個新對象分配內存,並且共享池中沒有足夠內存時,內存中那些不經常使用的對象就被釋放掉。一個被許多會話使用過的共享池對象,即使最初創建它的進程已經結束,只要它是有用的,都會被修正過的LRU算法一直保持在共享池中。這樣就使一個多用戶的Oracle系統對SQL語句的處理和內存消耗最小。
注意,即使一個共享SQL區與一個打開的游標相關,但如果它長時間沒有被使用,它還是可能會被從共享池中釋放出來。而此時如果打開的游標還需要運行它的相關語句,Oracle就會重新解析語句,並分配新的共享SQL區。
當一條SQL語句被提交給Oracle執行,Oracle會自動執行以下的內存分配步驟:
1.Oracle檢查共享池,看是否已經存在關於這條語句的共享SQL區。如果存在,這個共享SQL區就被用於執行這條語句。而如果不存在,Oracle就從共享池中分配一塊新的共享SQL區給這條語句。同時,無論共享SQL區存在與否,Oracle都會為用戶分配一塊私有SQL區以保存這條語句相關信息(如變量值)。
2.Oracle為會話分配一個私有SQL區。私有SQL區的所在與會話的連接方式相關。
下面是Oracle執行一條語句時共享池內存分配過程的偽代碼:
execute_sql(statement)
{
if ((shared_sql_area = find_shared_sql_area(statement)) == NULL)
{
if (!allocate_from_shared_pool(&new_buffer))
{
if (new_buffer = find_age_area_by_lru(size_of(statement)) == NULL)
{
raise(4031);
return 0;
}
}
shared_sql_area = set_shared_sql_area(new_buffer);
parse_sql(statement, shared_sql_area);
}
private_sql_area = allocate_private_sql_area(statement);
run_sql(statement, shared_sql_area, private_sql_area);
return 1;
}
在以下情況下,Oracle也會將共享SQL區從共享池中釋放出來:
· 當使用ANALYZE語句更新或刪除表、簇或索引的統計信息時,所有與被分析對象相關的共享SQL區都被從共享池中釋放掉。當下一次被釋放掉的語句被執行時,又重新在一個新的共享SQL區中根據被更新過的統計信息重新解析。
· 當對象結構被修改過后,與該對象相關的所有共SQL區都被標識為無效(invalid)。在下一次運行語句時再重新解析語句。
· 如果數據庫的全局數據庫名(Global Database Name)被修改了,共享池中的所有信息都會被清空掉。
· DBA通過手工方式清空共享池:
ALTER SYSTEM FLUSH SHARED_POOL;
Shared Pool能被分成幾個區域,分別被不同的latch(latch數最大為7,可以通過隱含參數_kghdsidx_count設置)保護。
表x$kghlu可以查看shared pool中的LRU列表。當滿足以下條件之一時,shared pool會分為多個區,分別有不同的LRU鏈表管理:
· 在10g之前版本,如果shared pool大於128M、CPU數量大於4;
· Oracle數據庫版本為10g
這時,在x$kghlu中就會對應不同記錄。
1.1.4.4. 保留共享池
前面提到,如果Oracle解析一個 PL/SQL程序單元,也需要從共享池中分配內存給這些程序單元對象。由於這些對象本一般比較大(如包),所以分配的內存空間也相對較大。系統經過長時間運行后,共享池可能存在大量內存碎片,導致無法滿足對於大塊內存段的分配。
為了使有足夠空間緩存大程序塊,Oracle專門從共享池內置出一塊區域來來分配內存保持這些大塊。這個保留共享池的默認大小是共享池的5%。它的大小也可以通過參數SHARED_POOL_RESERVED_SIZE來調整。保留區是從共享池中分配,不是直接從SGA中分配的,它是共享池的保留部分,用於存儲大塊段。
Shared Pool中內存大於5000字節的大段就會被存放在共享池的保留部分。而這個大小限制是通過隱含參數_SHARED_POOL_RESERVED_MIN_ALLOC來設定的(如前面所說,隱含參數不要去修改它)。除了在實例啟動過程中,所有小於這個數的內存段永遠都不會放到保留部分中,而大於這個值的大內存段也永遠不會存放到非保留區中,即使共享池的空間不夠用的情況下也是如此。
保留區的空閑內存也不會被包含在普通共享池的空閑列表中。它會維護一個單獨的空閑列表。保留池也不會在它的LRU列表中存放可重建(Recreatable 關於內存段的各種狀態我們在后面的內容中再介紹)段。當釋放普通共享池空閑列表上的內存時是不會清除這些大段的,同樣,在釋放保留池的空閑列表上的大內存段時也不會清除普通共享池中內存。
通過視圖V$SHARED_POOL_RESERVED可以查到保留池的統計信息。其中字段REQUEST_MISSES記錄了沒有立即從空閑列表中得到可用的大內存段請求次數。這個值要為0。因為保留區必須要有足夠個空閑內存來適應那些短期的內存請求,而無需將那些需要長期cache住的沒被pin住的可重建的段清除。否則就需要考慮增大SHARED_POOL_RESERVED_SIZE了。
你可以通過觀察視圖V$SHARED_POOL_RESERVED的MAX_USED_SPACE字段來判斷保留池的大小是否合適。大多數情況下,你會觀察到保留池是很少被使用的,也就是說5%的保留池空間可能有些浪費。但這需要經過長期觀察來決定是否需要調整保留池大小。
保留區使用shared pool的LRU鏈表來管理內存塊,但是在做掃描時,相互是不受影響的。例如,內存管理器掃描shared pool的LRU鏈表,清出空間以分配給一個小於5000字節的內存請求,是不會清出保留區的內存塊的,相反亦然。
1.1.4.5. 將重要、常用對象保持(Keep)在共享池中
前面提到,根據LRU算法,一些一段時間沒有使用到的內存塊會被情況釋放。這就可能導致一些重要的對象(如一個含有大量通用算法函數的包、被cache的序列)被從內存中清除掉。這些對象可能只是間歇被使用,但是因為他們的處理過程復雜(不僅包本身重新分配內存、解析,還要檢查里面的所有語句),要在內存中重建他們的代價非常大。
我們可以通過調用存儲過程DBMS_SHARED_POOL.KEEP將這些對象保持在共享池中來降低這種風險。這個存儲過程立即將對象及其從事對象載入library cache中,並將他們都標記為保持(Keeping)狀態。對於這種對象,我們建議在實例啟動時就Keep住,以減少內存碎片的幾率。
有一種觀點認為那些大對象(如包)是沒有必要被Keep住的,因為他們會被保持在共享池的保留區(如前所述,這個區通常使用率很低),所以一般不可能被清出。這個觀點是錯誤滴!因為大多數大對象實際上是被分為多個小的內存段被載入共享池的,因此根本不會因為對象的大小而受到特別的保護。
另外,也不要通過頻繁調用某些對象以防止他們被從共享池中清出。如果共享池大小設置合理,在系統運行的高峰時期,LRU鏈表會相對較短,那些沒有被pin住的對象會很快被清出,除非他們被keep住了。
1.1.4.6. 關於Shared Pool的重要參數
這里再介紹與共享池相關的一些重要參數。
· SHARED_POOL_SIZE
這個參數我們前面已經提到,它指定了Shared Pool的大小。在32位系統中,這個參數的默認值是8M,而64位系統中的默認值位64M。
但是,在SGA中還存在一塊叫內部SGA消耗(Internal SGA Overhead)的內存被放置在共享池中。在9i及之前版本,共享池的統計大小(通過v$sgastat視圖統計)為SHARED_POOL_SIZE + 內部SGA消耗大小。而10g以后,SHARED_POOL_SIZE就已經包含了這部分內存大小。因此在10g中,共享池的實際使用大小就是SHARED_POOL_SIZE - 內部SGA消耗大小,這在配置共享池大小時需要考慮進去,否則,扶過SHARED_POOL_SIZE設置過小,在實例啟動時就會報ORA-00371錯誤。
看9i中的結果:
SQL> show parameter shared_pool_size
NAME TYPE VALUE
----------------------------- ----------- --------------
shared_pool_size big integer 41943040
SQL> select sum(bytes) from v$sgastat where pool = 'shared pool';
SUM(BYTES)
----------
58720256
SQL>
· SHARED_POOL_RESERVED_SIZE
這個參數前面已經提到,指定了共享池中緩存大內存對象的保留區的大小。這里不再贅述。
· _SHARED_POOL_RESERVED_MIN_ALLOC
這個參數前面也已經介紹,設置了進入保留區的對象大小的閥值。
1.1.4.7. 共享池的重要視圖
最后,我們再介紹關於共享池的一些重要視圖
· v$shared_pool_advice
這個視圖與Oracle的另外一個優化建議器——共享池建議器——相關。我們可以根據這個視圖里面oracle所做的預測數據來調整共享池大小。它的預測范圍是從當前值的10%到200%之間。視圖的結構如下
字段 |
數據類型 |
描述 |
SHARED_POOL_SIZE_FOR_ESTIMATE |
NUMBER |
估算的共享池大小(M為單位) |
SHARED_POOL_SIZE_FACTOR |
NUMBER |
估算的共享池大小與當前大小比 |
ESTD_LC_SIZE |
NUMBER |
估算共享池中用於庫緩存的大小(M為單位) |
ESTD_LC_MEMORY_OBJECTS |
NUMBER |
估算共享池中庫緩存的內存對象數 |
ESTD_LC_TIME_SAVED |
NUMBER |
估算將可以節省的解析時間。這些節省的時間來自於請求處理一個對象時,重新將它載入共享池的時間消耗和直接從庫緩存中讀取的時間消耗的差值。 |
ESTD_LC_TIME_SAVED_FACTOR |
NUMBER |
估算的節省的解析時間與當前節省解析時間的比。 |
ESTD_LC_MEMORY_OBJECT_HITS |
NUMBER |
估算的可以直接從共享池中命中庫緩存的內存對象的命中次數。 |
關於如何根據建議器采用合理的共享池大小的方法,和前面提到的緩沖區建議器的使用方法類似,不再贅述。
· V$SHARED_POOL_RESERVED
前面提到了這個視圖。這個視圖存放了共享池保留區的統計信息。可以根據這些信息來調整保留區。視圖結構如下:
Column |
Datatype |
Description |
以下字段只有當參數SHARED_POOL_RESERVED_SIZE設置了才有效。 |
||
FREE_SPACE |
NUMBER |
保留區的空閑空間數。 |
AVG_FREE_SIZE |
NUMBER |
保留區的空閑空間平均數。 |
FREE_COUNT |
NUMBER |
保留區的空閑內存塊數 |
MAX_FREE_SIZE |
NUMBER |
最大的保留區空閑空間數。 |
USED_SPACE |
NUMBER |
保留區使用空間數。 |
AVG_USED_SIZE |
NUMBER |
保留區使用空間平均數。 |
USED_COUNT |
NUMBER |
保留區使用內存塊數。 |
MAX_USED_SIZE |
NUMBER |
最大保留區使用空間數 |
REQUESTS |
NUMBER |
請求再保留區查找空閑內存塊的次數。 |
REQUEST_MISSES |
NUMBER |
無法滿足查找保留區空閑內存塊請求,需要從LRU列表中清出對象的次數。 |
LAST_MISS_SIZE |
NUMBER |
請求的內存大小,這次請求是最后一次需要從LRU列表清出對象來滿足的請求。 |
MAX_MISS_SIZE |
NUMBER |
所有需要從LRU列表清出對象來滿足的請求中的內存最大大小 |
以下字段無論參數SHARED_POOL_RESERVED_SIZE是否設置了都有效。 |
||
REQUEST_FAILURES |
NUMBER |
沒有內存能滿足的請求次數(導致4031錯誤的請求) |
LAST_FAILURE_SIZE |
NUMBER |
沒有內存能滿足的請求所需的內存大小(導致4031錯誤的請求) |
ABORTED_REQUEST_THRESHOLD |
NUMBER |
不清出對象的情況下,導致4031錯誤的最小請求大小。 |
ABORTED_REQUESTS |
NUMBER |
不清出對象的情況下,導致4031錯誤的請求次數。。 |
LAST_ABORTED_SIZE |
NUMBER |
不清出對象的情況下,最后一次導致4031錯誤的請求大小。 |
我們可以根據后面4個字段值來決定如何設置保留區的大小以避免4031錯誤的發生。
· v$db_object_cache
這一視圖顯示了所有被緩存在library cache中的對象,包括表、索引、簇、同義詞、PL/SQL存儲過程和包以及觸發器。
字段 |
數據類型 |
說明 |
OWNER |
VARCHAR2(64) |
對象所有者 |
NAME |
VARCHAR2(1000) |
對象名稱 |
DB_LINK |
VARCHAR2(64) |
如果對象存在db link的話,db link的名稱 |
NAMESPACE |
VARCHAR2(28) |
庫緩存的對象命名空間,包括: TABLE/PROCEDURE, BODY, TRIGGER, INDEX, CLUSTER, OBJECT, CURSOR, INVALID NAMESPACE, JAVA SHARED DATA, PUB_SUB, RSRC CONSUMER GROUP |
TYPE |
VARCHAR2(28) |
對象類型,包括:INDEX, TABLE, CLUSTER, VIEW, SET, SYNONYM, SEQUENCE, PROCEDURE, FUNCTION, PACKAGE, PACKAGE BODY, TRIGGER, CLASS, OBJECT, USER, DBLINK, CURSOR, JAVA CLASS, JAVA SHARED DATA, NON-EXISTENT, NOT LOADED, PUB_SUB, REPLICATION OBJECT GROUP, TYPE |
SHARABLE_MEM |
NUMBER |
對象消耗的共享池中的共享內存 |
LOADS |
NUMBER |
對象被載入次數。即使對象被置為無效了,這個數字還是會增長。 |
EXECUTIONS |
NUMBER |
對象執行次數,但本視圖中沒有被使用。可以參考視圖v$sqlarea中執行次數。 |
LOCKS |
NUMBER |
當前鎖住這個對象的用戶數(如正在調用、執行對象)。 |
PINS |
NUMBER |
當前pin住這個對象的用戶數(如正在編譯、解析對象)。 |
KEPT |
VARCHAR2(3) |
對象是否被保持,即調用了DBMS_SHARED_POOL.KEEP來永久將對象pin在內存中。 (YES | NO) |
CHILD_LATCH |
NUMBER |
正在保護該對象的子latch的數量。 |
· v$sql、v$sqlarea 、v$sqltext
這三個視圖都可以用於查詢共享池中已經解析過的SQL語句及其相關信息。
V$SQL中列出了共享SQL區中所有語句的信息,它不包含GROUP BY字句,並且為每一條SQL語句中單獨存放一條記錄;
V$SQLAREA中一條記錄顯示了一條共享SQL區中的統計信息。它提供了有在內存中、解析過的和准備運行的SQL語句的統計信息;
V$SQLTEXT包含了庫緩存中所有共享游標對應的SQL語句。它將SQL語句分片顯示。
下面介紹一下我常用的V$SQLAREA的結構:
字段 |
數據類型 |
說明 |
SQL_TEXT |
VARCHAR2(1000) |
游標中SQL語句的前1000個字符。 |
SHARABLE_MEM |
NUMBER |
被游標占用的共享內存大小。如果存在多個子游標,則包含所有子游標占用的共享內存大小。 |
PERSISTENT_MEM |
NUMBER |
用於一個打開這條語句的游標的生命過程中的固定內存大小。如果存在多個子游標,則包含所有子游標生命過程中的固定內存大小。 |
RUNTIME_MEM |
NUMBER |
一個打開這條語句的游標的執行過程中的固定內存大小。如果存在多個子游標,則包含所有子游標執行過程中的固定內存大小。 |
SORTS |
NUMBER |
所有子游標執行語句所導致的排序次數。 |
VERSION_COUNT |
NUMBER |
緩存中關聯這條語句的子游標數。 |
LOADED_VERSIONS |
NUMBER |
緩存中載入了這條語句上下文堆(KGL heap 6)的子游標數。 |
OPEN_VERSIONS |
NUMBER |
打開語句的子游標數。 |
USERS_OPENING |
NUMBER |
打開這些子游標的用戶數。 |
FETCHES |
NUMBER |
SQL語句的fetch數。 |
EXECUTIONS |
NUMBER |
所有子游標的執行這條語句次數。 |
USERS_EXECUTING |
NUMBER |
通過子游標執行這條語句的用戶數。 |
LOADS |
NUMBER |
語句被載入和重載入的次數 |
FIRST_LOAD_TIME |
VARCHAR2(19) |
語句被第一次載入的時間戳。 |
INVALIDATIONS |
NUMBER |
所以子游標的非法次數。 |
PARSE_CALLS |
NUMBER |
所有子游標對這條語句的解析調用次數。 |
DISK_READS |
NUMBER |
所有子游標運行這條語句導致的讀磁盤次數。 |
BUFFER_GETS |
NUMBER |
所有子游標運行這條語句導致的讀內存次數。 |
ROWS_PROCESSED |
NUMBER |
這條語句處理的總記錄行數。 |
COMMAND_TYPE |
NUMBER |
Oracle命令類型代號。 |
OPTIMIZER_MODE |
VARCHAR2(10) |
執行這條的優化器模型。 |
PARSING_USER_ID |
NUMBER |
第一次解析這條語句的用戶的ID。 |
PARSING_SCHEMA_ID |
NUMBER |
第一次解析這條語句所用的schema的ID。 |
KEPT_VERSIONS |
NUMBER |
所有被DBMS_SHARED_POOL包標識為保持(Keep)狀態的子游標數。 |
ADDRESS |
RAW(4 | 8) |
指向語句的地址 |
HASH_VALUE |
NUMBER |
這條語句在library cache中hash值。 |
MODULE |
VARCHAR2(64) |
在第一次解析這條語句是通過調用DBMS_APPLICATION_INFO.SET_MODULE設置的模塊名稱。 |
MODULE_HASH |
NUMBER |
模塊的Hash值 |
ACTION |
VARCHAR2(64) |
在第一次解析這條語句是通過調用DBMS_APPLICATION_INFO.SET_ACTION設置的動作名稱。 |
ACTION_HASH |
NUMBER |
動作的Hash值 |
SERIALIZABLE_ABORTS |
NUMBER |
所有子游標的事務無法序列化的次數,這會導致ORA-08177錯誤。 |
IS_OBSOLETE |
VARCHAR2(1) |
游標是否被廢除(Y或N)。當子游標數太多了時可能會發生。 |
CHILD_LATCH |
NUMBER |
為了包含此游標的子latch數。 |
查看當前會話所執行的語句以及會話相關信息:
SQL> select a.sid||'.'||a.SERIAL#, a.username, a.TERMINAL, a.program, s.sql_text
2 from v$session a, v$sqlarea s
3 where a.sql_address = s.address(+)
4 and a.sql_hash_value = s.hash_value(+)
5 order by a.username, a.sid;
... ...
SQL>
· v$sql_plan
視圖V$SQL_PLAN包含了library cache中所有游標的執行計划。通過結合v$sqlarea可以查出library cache中所有語句的查詢計划。先從v$sqlarea中得到語句的地址,然后在由v$sql_plan查出它的查詢計划:
SQL> select lpad(' ', 2*(level-1))||operation "Operation",
2 options "Options",
3 decode(to_char(id), '0', 'Cost='||nvl(to_char(position), 'n/a'), object_name) "Object Name",
4 substr(optimizer, 1, 6) "Optimizer"
5 from v$sql_plan a
6 start with address = 'C0000000FCCDEDA0'
7 and id = 0
8 connect by prior id = a.parent_id
9 and prior a.address = a.address
10 and prior a.hash_value = a.hash_value;
Operation Options Object Name Optimizer
------------------- -------------------- -------------------- ---------
SELECT STATEMENT Cost=0 CHOOSE
NESTED LOOPS
INDEX RANGE SCAN CSS_BL_CNTR_IDX1 ANALYZ
INDEX RANGE SCAN CSS_BKG_BL_ASSN_UQ1 ANALYZ
SQL>
· v$librarycache
這個視圖包含了關於library cache的性能統計信息,對於共享池的性能調優很有幫助。它是按照命名空間分組統計的,結構如下:
字段 |
數據類型 |
說明 |
NAMESPACE |
VARCHAR2(15) |
library cache的命名空間 |
GETS |
NUMBER |
請求GET該命名空間中對象的次數。 |
GETHITS |
NUMBER |
請求GET並在內存中找到了對象句柄的次數(鎖定命中)。 |
GETHITRATIO |
NUMBER |
請求GET的命中率。 |
PINS |
NUMBER |
請求pin住該命名中對象的次數。 |
PINHITS |
NUMBER |
庫對象的所有元數據在內存中被找到的次數(pin命中)。 |
PINHITRATIO |
NUMBER |
Pin命中率。 |
RELOADS |
NUMBER |
Pin請求需要從磁盤中載入對象的次數。 |
INVALIDATIONS |
NUMBER |
命名空間中的非法對象(由於依賴的對象被修改所導致)數。 |
DLM_LOCK_REQUESTS |
NUMBER |
GET請求導致的實例鎖的數量。 |
DLM_PIN_REQUESTS |
NUMBER |
PIN請求導致的實例鎖的數量. |
DLM_PIN_RELEASES |
NUMBER |
請求釋放PIN鎖的次數。 |
DLM_INVALIDATION_REQUESTS |
NUMBER |
GET請求非法實例鎖的次數。 |
DLM_INVALIDATIONS |
NUMBER |
從其他實例那的得到的非法pin數。 |
其中PIN的命中率(或未命中率)是我們系統調優的一個重要依據:
SQL> select sum(pins) "hits",
2 sum(reloads) "misses",
3 sum(pins)/(sum(pins)+sum(reloads)) "Hits Ratio"
4 from v$librarycache;
hits misses Hits Ratio
---------- ---------- ----------
84962803 288 0.99999661
SQL>
SQL> select sum(pins) "hits",
2 sum(reloads) "misses",
3 ((sum(reloads)/sum(pins))*100) "Reload%"
4 from v$librarycache;
hits misses Reload%
---------- ---------- ----------
84963808 288 0.00033896
SQL>
當命中率小於99%或未命中率大於1%時,說明系統中硬解析過多,要做系統優化(增加Shared Pool、使用綁定變量、修改cursor_sharing等措施,性能優化不是本文重點,不再贅述)。
· v$library_cache_memory
這個視圖顯示了各個命名空間中的庫緩存內存對象的內存分配情況。一個內存對象是為了高效管理而組織在一起的一組內部內存。一個庫對象可能包含多個內存對象。
字段 |
數據類型 |
說明 |
LC_NAMESPACE |
VARCHAR2(15) |
Library cache命名空間 |
LC_INUSE_MEMORY_OBJECTS |
NUMBER |
屬於命名空間並正被在共享池使用的內存對象數。 |
LC_INUSE_MEMORY_SIZE |
NUMBER |
正在使用的內存對象的大小總(M未單位)。 |
LC_FREEABLE_MEMORY_OBJECTS |
NUMBER |
共享池中空閑的內存對象數。 |
LC_FREEABLE_MEMORY_SIZE |
NUMBER |
空閑內存對象的大小總和(M為單位)。 |
· v$sgastat
這個視圖前面介紹過,是關於SGA使用情況的統計。其中,關於Shared Pool有詳細的統計數據。
1.1.5. 重做日志緩存(Redo Log Buffer)
Redo Log Buffer是SGA中一段保存數據庫修改信息的緩存。這些信息被存儲在重做條目(Redo Entry)中.重做條目中包含了由於INSERT、UPDATE、DELETE、CREATE、ALTER或DROP所做的修改操作而需要對數據庫重新組織或重做的必須信息。在必要時,重做條目還可以用於數據庫恢復。
重做條目是Oracle數據庫進程從用戶內存中拷貝到Redo Log Buffer中去的。重做條目在內存中是連續相連的。后台進程LGWR負責將Redo Log Buffer中的信息寫入到磁盤上活動的重做日志文件(Redo Log File)或文件組中去的。
參數LOG_BUFFER決定了Redo Log Buffer的大小。它的默認值是512K(一般這個大小都是足夠的),最大可以到4G。當系統中存在很多的大事務或者事務數量非常多時,可能會導致日志文件IO增加,降低性能。這時就可以考慮增加LOG_BUFFER。
但是,Redo Log Buffer的實際大小並不是LOB_BUFFER的設定大小。為了保護Redo Log Buffer,oracle為它增加了保護頁(一般為11K):
SQL> select * from v$sgastat where name = 'log_buffer';
POOL NAME BYTES
------------ -------------------------- ----------
log_buffer 7139328
1 row selected.
SQL> show parameter log_buffer
NAME TYPE VALUE
------------------------------------ ----------- -------------------
log_buffer integer 7028736
SQL>
1.1.6. 大池(large pool)
大池是SGA中的一塊可選內存池,根據需要時配置。在以下情況下需要配置大池:
o 用於共享服務(Shared Server MTS方式中)的會話內存和Oracle分布式事務處理的Oracle XA接口
o 使用並行查詢(Parallel Query Option PQO)時
o IO服務進程
o Oracle備份和恢復操作(啟用了RMAN時)
通過從大池中分配會話內存給共享服務、Oracle XA或並行查詢,oracle可以使用共享池主要來緩存共享SQL,以防止由於共享SQL緩存收縮導致的性能消耗。此外,為Oracle備份和恢復操作、IO服務進程和並行查詢分配的內存一般都是幾百K,這么大的內存段從大池比從共享池更容易分配得到(所以叫“大”池嘛^_^)。
參數LARGE_POOL_SIZE設置大池的大小。大池是屬於SGA的可變區(Variable Area)的,它不屬於共享池。對於大池的訪問,是受到large memory latch保護的。大池中只有兩種內存段:空閑(free)和可空閑(freeable)內存段(關於不同類型內存段我們在后面介紹)。它沒有可重建(recreatable)內存段,因此也不用LRU鏈表來管理(這和其他內存區的管理不同)。大池最大大小為4G。
為了防止大池中產生碎片,隱含參數_LARGE_POOL_MIN_ALLOC設置了大池中內存段的最小大小,默認值是16K(同樣,不建議修改隱含參數)。
此外,large pool是沒有LRU鏈表的。
1.1.7. Java池(Java Pool)
Java池也是SGA中的一塊可選內存區,它也屬於SGA中的可變區。
Java池的內存是用於存儲所有會話中特定Java代碼和JVM中數據。Java池的使用方式依賴與Oracle服務的運行模式。
Java池的大小由參數JAVA_POOL_SIZE設置。Java Pool最大可到1G。
在Oracle 10g以后,提供了一個新的建議器——Java池建議器——來輔助DBA調整Java池大小。建議器的統計數據可以通過視圖V$JAVA_POOL_ADVICE來查詢。如何借助建議器調整Java池的方法和使用Buffer Cache建議器類似,可以參考Buffer Cache中關於建議器部分。
1.1.8. 流池(Streams Pool)
流池是Oracle 10g中新增加的。是為了增加對流(流復制是Oracle 9iR2中引入的一個非常吸引人的特性,支持異構數據庫之間的復制。10g中得到了完善)的支持。
流池也是可選內存區,屬於SGA中的可變區。它的大小可以通過參數STREAMS_POOL_SIZE來指定。如果沒有被指定,oracle會在第一次使用流時自動創建。如果設置了SGA_TARGET參數,Oracle會從SGA中分配內存給流池;如果沒有指定SGA_TARGET,則從buffer cache中轉換一部分內存過來給流池。轉換的大小是共享池大小的10%。
Oracle同樣為流池提供了一個建議器——流池建議器。建議器的統計數據可以通過視圖V$STREAMS_POOL_ADVICE查詢。使用方法參看Buffer Cache中關於優化器部分。
1.2. PGA (The Process Global Area)
PGA(Program Global Area程序全局區)是一塊包含一個服務進程的數據和控制信息的內存區域。它是Oracle在一個服務進程啟動是創建的,是非共享的。一個Oracle進程擁有一個PGA內存區。一個PGA也只能被擁有它的那個服務進程所訪問,只有這個進程中的Oracle代碼才能讀寫它。因此,PGA中的結構是不需要Latch保護的。
我們可以設置所有服務進程的PGA內存總數受到實例分配的總體PGA(Aggregated PGA)限制。
在專有服務器(Dedicated Server)模式下,Oracle會為每個會話啟動一個Oracle進程;而在多線程服務(Multi-Thread Server MTS)模式下,由多個會話共享通一個Oracle服務進程。
PGA中包含了關於進程使用到的操作系統資源的信息,以及一些關於進程狀態的信息。而關於進程使用的Oracle共享資源的信息則是在SGA中。這樣做可以使在進程以外中止時,能夠及時釋放和清除這些資源。
1.2.1. PGA的組成
PGA由兩組區域組成:固定PGA和可變PGA(或者叫PGA堆,PGA Heap【堆——Heap就是一個受管理的內存區】)。固定PGA和固定SGA類似,它的大小時固定的,包含了大量原子變量、小的數據結構和指向可變PGA的指針。
可變PGA時一個內存堆。它的內存段可以通過視圖X$KSMPP(另外一個視圖X$KSMSP可以查到可變SGA的內存段信息,他們的結構相同)查到。PGA堆包含用於存放X$表的的內存(依賴與參數設置,包括DB_FILES、CONTROL_FILES)。
總的來說,PGA的可變區中主要分為以下三部分內容:
o 私有SQL區;
o 游標和SQL區
o 會話內存
1.2.1.1. 私有SQL區(Private SQL Area)
前面已經說過,私有SQL區包含了綁定變量值和運行時期內存結構信息等數據。每一個運行SQL語句的會話都有一個塊私有SQL區。所有提交了相同SQL語句的用戶都有各自的私有SQL區,並且他們共享一個共享SQL區。因此,一個共享SQL區可能和多個私有共享區相關聯。
一個游標的私有SQL區又分為兩個生命周期不同的區:
o 永久區。包含綁定變量信息。當游標關閉時被釋放。
o 運行區。當執行結束時釋放。
創建運行區是一次執行請求的第一步。對於INSERT、UPDATE和DELETE語句,Oracle在語句運行結束時釋放運行區。對於查詢操作,Oracle只有在所有記錄被fetch到或者查詢被取消時釋放運行區。
1.2.1.2. 游標和SQL區(Cursors and SQL Areas)
一個Oracle預編譯程序或OCI程序的應用開發人員能夠很明確的打開一個游標,或者控制一塊特定的私有SQL區,將他們作為程序運行的命名資源。另外,oracle隱含的為一些SQL語句產生的遞歸調用(前面有介紹,讀取數據字典信息)也使用共享SQL區。
私有SQL區是由用戶進程管理的。如何分配和釋放私有SQL區極大的依賴與你所使用的應用工具。而用戶進程可以分配的私有SQL區的數量是由參數OPEN_CURSORS控制的,它的默認值是50。
在游標關閉前或者語句句柄被釋放前,私有SQL區將一直存在(但其中的運行區是在語句執行結束時被釋放,只有永久區一直存在)下去。應用開發人員可以通過將所有打開的不再使用的游標都關閉來釋放永久區,以減少用戶程序所占用的內存。
1.2.1.3. 會話內存(Session Memory)
會話內存是一段用於保存會話變量(如登錄信息)和其他預會話相關信息的內存。對於共享服務器模式下,會話內存是共享的,而不是私有的。
對於復雜的查詢(如決策支持系統中的查詢),運行區的很大一部分被那些內存需求很大的操作分配給SQL工作區(SQL Work Area)。這些操作包括:
o 基於排序的操作(ORDER BY、GROUP BY、ROLLUP、窗口函數);
o Hash Join
o Bitmap merge
o Bitmap create
例如,一個排序操作使用工作區(這時也可叫排序區Sort Area)來將一部分數據行在內存排序;而一個Hash Join操作則使用工作區(這時也可以叫做Hash區 Hash Area)來建立Hash表。如果這兩種操作所處理的數據量比工作區大,那就會將輸入的數據分成一些更小的數據片,使一些數據片能夠在內存中處理,而其他的就在臨時表空間的磁盤上稍后處理。盡管工作區太小時,Bitmap操作不會將數據放到磁盤上處理,但是他們的復雜性是和工作區大小成反比的。因此,總的來說,工作區越大,這些操作就運行越快。
工作區的大小是可以調整的。一般來說,大的工作區能讓一些特定的操作性能更佳,但也會消耗更多的內存。工作區的大小足夠適應輸入的數據和相關的SQL操作所需的輔助的內存就是最優的。如果不滿足,因為需要將一部分數據放到臨時表空間磁盤上處理,操作的響應時間會增長。
1.2.2. PGA內存自動管理
SQL工作區可以是自動的、全局的管理。DBA只要設置參數PGA_AGGREGATE_TARGET給一個實例的PGA內存指定總的大小。設置這個參數后,Oracle將它作為一個總的全局限制值,盡量使所有Oracle服務進程的PGA內存總數不超過這個值。
在這個參數出現之前,DBA要調整參數SORT_AREA_SIZE、 HASH_AREA_SIZE,、BITMAP_MERGE_AREA_SIZE 和CREATE_BITMAP_AREA_SIZE(關於這些參數,我們會在后面介紹),使性能和PGA內存消耗最佳。對這些參數的調整是非常麻煩的,因為即要考慮所有相關的操作,使工作區適合它們輸入數據大小,又要使PGA內存不消耗過大導致系統整體性能下降。
9i以后,通過設置了參數PGA_AGGREGATE_TARGET,使所有會話的工作區的大小都是自動分配。同時,所有*_AREA_SIZE參數都會失效。在任何時候,實例中可用於工作區的PGA內存總數都是基於參數PGA_AGGREGATE_TARGET的。工作區內存總數等於參數PGA_AGGREGATE_TARGET的值減去系統其他組件(如分配給會話的PGA內存)的內存消耗。分配給Oracle進程的PGA內存大小是根據它們對內存的需求情況來的。
參數WORKAREA_SIZE_POLICY決定是否使用PGA_AGGREGATE_TARGET來管理PGA內存。它有兩個值:AUTO和MANUAL。默認是AUTO,即使用PGA_AGGREGATE_TARGET來管理PGA內存。其實,從參數WORKAREA_SIZE_POLICY的名字上可以看出,Oracle的PGA內存自動管理只會調整工作區部分,而非工作區部分(固定PGA區)則不會受影響。
還有注意一點就是:10g之前,PGA_AGGREGATE_TARGET只在專用服務模式下生效。而10g以后,PGA內存自動管理在專有服務模式(Dedicated Server)和MTS下都有效。另外,9i在OpenVMS系統上還不支持PGA內存自動管理,但10g支持。
設置了PGA_AGGREGATE_TARGET以后,每個進程PGA內存的大小也是受限制的:
o 串行操作時,每個進程可用的PGA內存為MIN(PGA_AGGREGATE_TARGET * 5%, _pga_max_size/2),其中隱含參數_pga_max_size的默認值是200M,同樣不建議修改它。
o 並行操作時,並行語句可用的PGA內存為PGA_AGGREGATE_TARGET * 30% / DOP (Degree Of Parallelism 並行度)。
1.2.3. 專有服務(Dedicated Server)和共享服務(Shared Server)
對PGA內存的管理和分配,很大程度上依賴與服務模式。下面這張表顯示了在不同模式下,PGA內存不同部分的分配的異同:
內存區 |
專有服務 |
共享服務 |
會話內存 | 私有的 | 共享的 |
永久區所在區域 | PGA | SGA |
SELECT語句的運行區所在區域 | PGA | PGA |
DML/DDL語句的運行區所在區域 | PGA | PGA |
1.2.4. 重要參數
PGA的管理和分配是由多個系統參數控制的,下面介紹一下這些參數:
1.2.4.1. PGA_AGGREGATE_TARGET
這個參數前面介紹了。它控制了所有進程PGA內存的總的大小。
在專有服務模式下,推薦使用PGA_AGGREGATE_TARGET。
PGA_AGGREGATE_TARGET的取值范圍是10M~(4096G - 1 )bytes。
對於PGA_AGGREGATE_TARGET大小的設置,Oracle提供了一個以下建議方案(參見Metalink Note: 223730.1):
o 對於OLTP系統,PGA_AGGREGATE_TARGET物理內存大小 * 80%) * 20% = (
o 對於DSS系統,PGA_AGGREGATE_TARGET物理內存大小 * 80%) * 50% = (
例如,你的系統是一個OLTP系統,物理內存為8G,那么推薦PGA_AGGREGATE_TARGET設置為 (8 * 80%) * 20% = 1.28G。
1.2.4.2. WORKAREA_SIZE_POLICY
參數WORKAREA_SIZE_POLICY決定是否使用PGA_AGGREGATE_TARGET來管理PGA內存。它有兩個值:AUTO和MANUAL。默認是AUTO,即使用PGA_AGGREGATE_TARGET來管理PGA內存。
1.2.4.3. sort_area_size
Oracle在做排序操作(ORDER BY、GROUP BY、ROLLUP、窗口函數)時,需要從工作區中分配一定內存區域對數據記錄做內存排序。在排序完成后,數據返回之前,Oracle會釋放這部分內存,。SORT_AREA_SIZE指定了這部分內存的大小。設置了PGA_AGGREGATE_TARGET后,該參數無效。
除非在共享服務模式下,一般不推薦設置這個參數,而推薦使用PGA_AGGREGATE_TARGET進行PGA內存自動管理。如果需要設置此參數,可以考慮設置在1M~3M。
Oracle也許會為一個查詢分配多個排序區。通常情況下,一條語句只有1、2個排序操作,但是對於復雜語句,可能存在多個排序操作,每個排序操作都有自己的排序區。因此,語句的復雜性也影響到每個進程PGA內存的大小。
1.2.4.4. sort_area_retained_size
這個參數與SORT_AREA_SIZE配合使用。它指定了在排序操作完成后,繼續保留用戶全局區(User Global Area UGA,關於UGA與PGA、SGA關系在UGA部分介紹)內存的最大大小,以維護內存中的排序,直到所有數據行被返回后才釋放(上面提到,SORT_AREA_SIZE的內存在排序完成、數據行返回之前被釋放)回UGA(注意:是釋放回UGA,而不會被操作系統回收)。
SORT_AREA_RETAINED_SIZE在共享服務中是從SGA中分配的(因為此時UGA從SGA中分配),在專有服務模式中是從PGA中分配的。而SORT_AREA_SIZE無論在那種模式下都從PGA中分配。
同樣,設置了PGA_AGGREGATE_TARGET后,該參數無效。
1.2.4.5. hash_area_size
HASH_AREA_SIZE設置了在做Hash Join時,hash內存表可占用的內存空間。同樣,設置了PGA_AGGREGATE_TARGET后,該參數無效。它的默認值大小是sort_area_size的1.5倍。
此外,由於Hash Join只有在優化器為CBO(Cost-Base Optimizer)模式下才有效,因此這個參數也只有CBO模式下才有意義。
1.2.4.6. hash_join_enable
這個參數決定是否啟用Hash Join。默認為TRUE。
由於Hash Join只有在優化器為CBO(Cost-Base Optimizer)模式下才有效,因此這個參數也只有CBO模式下才有意義。
10g中,這個參數是隱含參數。
1.2.4.7. bitmap_merge_area_size
在使用位圖索引(Bitmap Index)時,oracle為索引位圖段建立一張位圖。在進行位圖索引掃描時,需要將掃描到的位圖索引排序后與位圖合並(Merge),Oracle會在PGA中開辟一片區域用於排序和合並。參數BITMAP_MERGE_AREA_SIZE指定了這篇區域的大小。默認值是1M。
同樣,設置了PGA_AGGREGATE_TARGET后,該參數無效。
1.2.4.8. create_bitmap_area_size
在字段的集的勢(Cardinality 參照記錄行數,字段的不同值的一個因子。記錄數越多,不同值越少,則集的勢越小)很小,並且表的數據變化不大時,可以考慮為字段建立位圖索引以提高對該字段的檢索效率。這個參數指定可在創建位圖索引時的內存空間占用大小。它的默認大小是8M。
同樣,設置了PGA_AGGREGATE_TARGET后,該參數無效。
1.2.4.9. open_cursors
這個參數設置一個會話可以同時打開的游標數。由於每打開一個游標,都需要一部分PGA內存分配出來作為私有SQL區。因此這個參數也影響了每個進程的PGA內存的占用大小。
1.2.4.10. _pga_max_size
這是一個隱含參數。它規定了一個PGA的最大大小。可參見1.2.2。
1.2.5. 重要視圖
1.2.5.1. V$PGASTA
V$PGASTAT提供了PGA內存使用情況的統計信息和當自動PGA內存管理啟動時的統計信息。視圖里面的累加數據是自從實例啟動后開始累加的。
字段 |
數據類型 |
說明 |
NAME |
VARCHAR2(64) |
統計的名稱,包括: aggregate PGA target parameter – 當前參數PGA_AGGREGATE_TARGET的值。如果參數沒有設置,則值為0並且PGA內存自動管理被關閉。 aggregate PGA auto target – 在自動管理模式下,可用於工作區的總的PGA內存數。這個數值是動態的,和PGA_AGGREGATE_TARGET 的值以及當前工作區的負載有關,Oracle會動態調整它。 這個值相對與PGA_AGGREGATE_TARGET來說很小。其他很大一部分的PGA內存都被用於系統的其他組件(如PLSQL和Java的內存)。DBA必須保證在自動模式下有足夠的PGA內存用於工作區。 global memory bound – 自動模式下可用的工作區的最大大小。Oracle根據當前工作區的負載動態調整這個值。當系統中活動的工作區數量增加時,global memory bound一般會下降。如果global bound 降到低於1M,則要考慮增加PGA_AGGREGATE_TARGET了。 total PGA allocated – 當前實例分配的總的PGA內存大小。Oracle會試圖保持這個值在PGA_AGGREGATE_TARGET以內。然而,當工作區負載增加得非常快或者PGA_AGGREGATE_TARGET被設置得很小時,這個值也許會在一段時間內超過PGA_AGGREGATE_TARGET。 total PGA used – 當前被工作區消耗得PGA內存。這個數值也可以用於計算有多少PGA內存被其他組件(如PLSQL或Java)所消耗。 total PGA used for auto workareas – 自動模式下,當前多少PGA內存被工作區所消耗。這個數值也可以用於計算有多少PGA內存被其他組件(如PLSQL或Java)所消耗。 total PGA used for manual workareas –手動模式下,當前多少PGA內存被工作區所消耗。這個數值也可以用於計算有多少PGA內存被其他組件(如PLSQL或Java)所消耗。 over allocation count – 這個數值是自從實例啟動后累加的。當PGA_AGGREGATE_TARGET設置非常小或工作區負載增長很快時,會超額分配PGA內存(分配的值大於PGA_AGGREGATE_TARGET)。這種情況發生時,Oracle不能限制PGA內存小於PGA_AGGREGATE_TARGET,只能分配實際需要的PGA內存。此時,建議通過建議器視圖V$PGA_TARGET_ADVICE來增加PGA_AGGREGATE_TARGET的大小。 bytes processed – 自從實例啟動后,被內存SQL操作處理的字節數。 extra bytes read/written – 自從實例啟動后,需要額外輸入數據所處理的字節數。當工作區無法在最佳狀態下運行時,就需要進行這個額外處理。 cache hit percentage – Oracle計算出來的一個與PGA內存組件性能相關的數據,是自從實例啟動后累加的。如果這個值是100%,則表示實例啟動后,所有系統使用到的工作區都分配了最佳的PGA內存。 當工作區無法在最佳狀態下運行,就需要進行額外的數據輸入處理,這將會降低cache hit percentage。 |
VALUE |
NUMBER |
統計數據 |
UNITS |
VARCHAR2(12) |
數據的單位 (microseconds, bytes, or percent) |
1.2.5.2. V$PGA_TARGET_ADVICE
這個視圖是可以顯示PGA優化建議器的估算預測結果,它顯示了在各種PGA_AGGREGATE_TARGET值時,V$PGASTAT可能會顯示的PGA性能統計數據。選取所用來預測的PGA_AGGREGATE_TARGET值是當前PGA_AGGREGATE_TARGET左右的的值。而估算出的統計值是根據實例啟動后的負載模擬出來的。
只有當建議器打開(隱含參數_smm_advice_enabled為TRUE),並且參數STATISTICS_LEVEL值不是BASIC時,視圖中才會有內容。實例重啟后,所有預測數據都會被重寫。
字段 |
數據類型 |
說明 |
PGA_TARGET_FOR_ESTIMATE |
NUMBER |
用於預測的PGA_AGGREGATE_TARGET值。 |
PGA_TARGET_FACTOR |
NUMBER |
預測的PGA_AGGREGATE_TARGET與當前PGA_AGGREGATE_TARGET的比。 |
ADVICE_STATUS |
VARCHAR2(3) |
建議器狀態(ON或OFF) |
BYTES_PROCESSED |
NUMBER |
預測的被所有工作區處理的字節數。 |
ESTD_EXTRA_BYTES_RW |
NUMBER |
當PGA_AGGREGATE_TARGET設置為預測值時,需要額外讀寫的字節數。 |
ESTD_PGA_CACHE_HIT_PERCENTAGE |
NUMBER |
當PGA_AGGREGATE_TARGET設置為預測值時,緩存命中率。這個值等於BYTES_PROCESSED / (BYTES_PROCESSED + ESTD_EXTRA_BYTES_RW) |
ESTD_OVERALLOC_COUNT |
NUMBER |
當PGA_AGGREGATE_TARGET設置為預測值時,需要超額分配的PGA內存。如果非0則說明PGA_AGGREGATE_TARGET設置得太小。 |
1.2.5.3. V$SYSSTAT 、V$SESSTAT
這兩個視圖顯示了系統(會話)的統計數據。他們的統計項目基本相同,但不同之處在於一個是系統級的、一個是會話級的。
通過這兩個視圖我們可以查出像sort這樣操作對工作區的使用情況:
SQL> select * from V$SYSSTAT
2 where name like '%sort%';
STATISTIC# NAME CLASS VALUE
---------- ---------------------------------------------------------------- ---------- ----------
245 sorts (memory) 64 2876455
246 sorts (disk) 64 483
247 sorts (rows) 64 116554720
SQL>
1.2.5.4. V$SQL_WORKAREA
這個視圖顯示了被SQL游標使用的工作區的信息。存儲在Shared Pool中的每條SQL語句都有一個或多個子游標,它們能被V$SQL顯示。而V$SQL_WORKAREA顯示需要被這些游標所使用的工作區信息。可以將它與V$SQL進行join查詢。
通過這個視圖可以解決以下一些問題:
1、請求最多的工作區;
2、在自動模式下,占用內存最多的工作區。
字段 |
數據類型 |
說明 |
ADDRESS |
RAW(4 | 8) |
游標句柄的地址。 |
HASH_VALUE |
NUMBER |
游標句柄的Hash值。這個字段和ADDRESS字段join V$SQLAREA可以定位出相關語句。 |
CHILD_NUMBER |
NUMBER |
使用此工作區的子游標數。 |
WORKAREA_ADDRESS |
RAW(4 | 8) |
工作區句柄的地址。唯一定位了一條記錄 |
OPERATION_TYPE |
VARCHAR2(20) |
工作區的操作類型(SORT, HASH JOIN, GROUP BY, BUFFERING, BITMAP MERGE, or BITMAP CREATE) |
OPERATION_ID |
NUMBER |
唯一定位查詢計划中的一個操作的值,可以和視圖V$SQL_PLAN join。 |
POLICY |
VARCHAR2(10) |
工作區的模式(MANUAL或AUTO) |
ESTIMATED_OPTIMAL_SIZE |
NUMBER |
估計需要此工作區來執行內存中操作的所需的大小。 |
ESTIMATED_ONEPASS_SIZE |
NUMBER |
估計需要此工作區來一次執行內存中操作的所需的大小。 |
LAST_MEMORY_USED |
NUMBER |
最后一次執行游標所使用的工作區大小。 |
LAST_EXECUTION |
VARCHAR2(10) |
最后一次執行游標,工作區請求內存的方式,OPTIMAL, ONE PASS, ONE PASS 或MULTI-PASS。 |
LAST_DEGREE |
NUMBER |
最后一次執行並行操作的並行度(Degree of parallelism DOP)。 |
TOTAL_EXECUTIONS |
NUMBER |
此工作區激活的次數。 |
OPTIMAL_EXECUTIONS |
NUMBER |
此工作區運行於optimal模式的次數 |
ONEPASS_EXECUTIONS |
NUMBER |
此工作區運行於one-pass 模式的次數 |
MULTIPASSES_EXECUTIONS |
NUMBER |
此工作區運行於one-pass內存請求情況下的次數 |
ACTIVE_TIME |
NUMBER |
此工作區激活的評價時間數。 |
MAX_TEMPSEG_SIZE |
NUMBER |
實例化此工作區所創建的臨時段的最大大小。 |
LAST_TEMPSEG_SIZE |
NUMBER |
最后一次實例化此工作區所創建的臨時段的大小。 |
1.2.5.5. V$SQL_WORKAREA_ACTIVE
這個視圖包含了系統當前分配的工作區的瞬間信息。可以通過字段WORKAREA_ADDRESS join V$SQL_WORKAREA來查詢工作區信息。如果工作區溢出到磁盤,則這個視圖就包含了這個工作區所溢出的臨時段的信息。通過與視圖V$TEMPSEG_USAGE join,可以得到更多的臨時段信息。
這個視圖可以解決以下問題:
1、當前系統分配最大的工作區;
2、超額分配內存的百分比(EXPECTED_SIZE < ACTUAL_MEM_USED),和未超額分配內存的百分比(EXPECTED_SIZE > ACTUAL_MEM_USED);
3、哪個活動的工作區使用了超出內存管理預期的內存大小;
4、那個活動的工作區溢出到磁盤了。
字段 |
數據類型 |
說明 |
WORKAREA_ADDRESS |
RAW(4 | 8) |
工作區句柄的地址。唯一定位了一條記錄。 |
OPERATION_TYPE |
VARCHAR2(20) |
使用此工作區的操作 (SORT, HASH JOIN, GROUP BY, BUFFERING, BITMAP MERGE, 或 BITMAP CREATE) |
OPERATION_ID |
NUMBER |
唯一定位查詢計划中的一個操作的值,可以和視圖V$SQL_PLAN join。 |
POLICY |
VARCHAR2(6) |
工作區的模式(MANUAL或AUTO) |
SID |
NUMBER |
會話ID |
QCINST_ID |
NUMBER |
查詢協調(查詢協調在並行查詢中出現)者實例ID。 |
QCSID |
NUMBER |
查詢協調者的會話ID。 |
ACTIVE_TIME |
NUMBER |
此工作區激活的平均時間(厘秒為單位) |
WORK_AREA_SIZE |
NUMBER |
被當前操作使用的最大工作區大小。 |
EXPECTED_SIZE |
NUMBER |
工作區的預期大小。預期大小是內存管理器設置的。當WORK_AREA_SIZE 大於EXPECTED_SIZE 時,內存會超額分配。 |
ACTUAL_MEM_USED |
NUMBER |
當前分配個工作區的PGA內存大小(KB)。這個值在0和 WORK_AREA_SIZE之間。 |
MAX_MEM_USED |
NUMBER |
這個工作區使用的最大大小(KB)。 |
NUMBER_PASSES |
NUMBER |
這個工作區的通道數(如果在OPTIMAL模式下為0) |
TEMPSEG_SIZE |
NUMBER |
用於此工作區的臨時段大小。 |
TABLESPACE |
VARCHAR2(31) |
創建臨時段給這個工作區的表空間名字。 |
SEGRFNO# |
NUMBER |
創建臨時段的表空間的文件ID。 |
SEGBLK# |
NUMBER |
給工作區創建臨時段的block數。 |
1.2.5.6. V$PROCESS
這個視圖顯示了所有Oracle進程的信息。其中以下幾個字段則說明了進程PGA內存的使用情況。
PGA_USED_MEM:進程使用的PGA內存
PGA_ALLOCATED_MEM:分配給進程的PGA內存
PGA_MAX_MEM:進程使用的最大的PGA內存。
1.3. UGA (The User Global Area)
PGA是一段包含一個Oracle服務或后台進程的數據和控制信息的內存。PGA的大小依賴與系統的配置。在專用服務(Dedicated Server)模式下,一個服務進程與一個用戶進程相關,PGA就包括了堆空間和UGA。而UGA(User Global Area 用戶全局區)由用戶會話數據、游標狀態和索引區組成。在共享服務(MTS)模式下,一個共享服務進程被多個用戶進程共享,此時UGA是Shared Pool或Large Pool的一部分(依賴與配置)。
許多DBA都不理解PGA和UGA之間的區別。其實這種區別可以簡單的理解為進程和會話直接的區別。在專用服務模式下,進程和會話是一對一的;而在MTS模式下,進程和會話是一對多的關系。PGA是服務於進程的,它包含的是進程的信息;而UGA是服務於會話的,它包含的是會話的信息。因此,MTS模式下,PGA和UGA之間的關系也是一對多的。
UGA中包含了一個會話的信息,包括:
o 打開游標的永久區和運行區;
o 包的狀態信息,特別是包的變量;
o Java會話的信息;
o 激活的角色;
o 激活的跟蹤事件(ALTER SESSION SET EVENT …);
o 起作用的NLS參數(SELECT * FROM NLS_SESSION_PARAMETERS;);
o 所有打開的db link;
o 會話對於信任的Oracle的托管訪問標記(mandatory access control (MAC)
和PGA一樣,UGA也由兩組區組成,固定UGA和可變UGA(或者說UGA堆)。固定UGA包含了大概70個原子變量、小的數據結構以及指向UGA堆的指針。
UGA heap中的段可以通過表X$KSMUP查到(它的結構和X$KSMSP相同)。UGA堆包含了存儲一些固定表(X$表)的永久內存(依賴與特定參數的設置,如OPEN_CURSORS,OPEN_LINKS和MAX_ENABLED_ROLES)。除此以外,大部分的UGA用於私有SQL區。UGA內存的所在依賴於會話的設置。在專用服務模式下,會話和進程是一對一的關系,UGA位於PGA中。固定UGA是PGA中的一段內存段,而UGA堆是PGA的子堆。在MTS模式下,固定UGA是shared pool中的一段內存段,而UGA堆是Large Pool的子堆,如果從large pool分配失敗,則從shared pool中分配。
MTS模式下,可以通過Profile中的PRIVATE_SGA項(通過dba_profiles查看)來控制每個UGA占用的SGA的總的大小,但是不建議這樣做。
Oracle 9.2以后,有一個新的隱含參數:_use_realfree_heap。當設置這個參數為true時,Oracle會為CGA、UGA單獨分配堆,而不從PGA中分配。它的默認值為false,而當設置了pga_aggregate_target后,它的值自動被改為true。
1.4. CGA (The Call Global Area)
與其他的全局區不同,CGA(Call Global Area 調用全局區)的存在是瞬間的。它只存在於一個調用過程中。對於實例的一些低層次的調用需要CGA,包括:
o 解析一條SQL語句;
o 執行一條SQL語句;
o 取一條SELECT語句的輸出值。
如果語句產生了遞歸調用,則需要為每個遞歸調用分配一個CGA。如上所述,遞歸調用是在語句解析、優化器產生語句查詢計划、DML操作時需要查詢或修改數據字典信息的調用。
無論UGA存在於PGA還是SGA,CGA都是PGA的subheap。因為無論那種模式,會話在做調用時總需要一個進行進行處理。這一點很重要,特別是在MTS模式下時,如果發現一次調用很久沒有響應,則可能需要增加PGA的大小。
當然,調用並不是只通過CGA中的數據結構來工作。實際上,調用所需要的大部分的重要數據結構都來自於UGA。例如私有SQL取和排序區都存放在UGA中,因為調用結束后,它們是被保留的。CGA中只包含了那些調用結束后可以被釋放的數據。例如,CGA中包含了直接IO緩存、關於遞歸調用的信息、用於表達式評估(產生查詢計划時)的的堆空間和其他一些臨時數據。
Java調用內存也分配在CGA中。它被分為三部分空間:堆空間、新空間和老空間。在調用期間(調用長短依賴於使用期長短和大小),在新空間和老空間中的內存段不再使用的內存段將被垃圾收集器回收。
1.5. 軟件代碼區(Software Code Area)
軟件代碼區是一部分用於存放那些正在運行和可以被運行的代碼(Oracle自身的代碼)的內存區。Oracle代碼一般存儲在一個不同於用戶程序存儲區的軟件代碼區,而用戶程序存儲區是排他的、受保護的區域。
軟件區的大小一般是固定的,只有Oracle軟件升級或重裝后才會改變。在不同操作系統下,這部分區域所要求的大小也不同。
軟件區是只讀的,可以被安裝成共享的或非共享的。可能的情況下,Oracle代碼是共享的,這樣所有Oracle用戶都可以直接訪問這些代碼,而不需要各自保存一份拷貝在自己的內存中。這樣可以節省大量內存並提高整體性能。
而用戶程序也可以是共享的或非共享的。一些Oracle工具(如SQL Plus)能被安裝成共享的,但有些不能。如果一台機器運行多個實例,這些實例可以使用同一個Oracle代碼區。
另外要注意的是:並不是所有操作系統都能將軟件區安裝成共享的,如Windows。
2. Oracle的內存管理
在這部分章節中,我們將從更底層的角度來了解Oracle的內存管理。當然,涉及到內存管理,就不可避免的要了解OS對內存的管理,在這以章節中,將談到不少關於OS內存的管理知識。如果概念記得不清除了,可以找一本《操作系統》的書看看先。
2.1. Oracle內存管理基礎
要了解內存管理,首先需要知道虛擬內存。而要了解虛擬內存,就先要知道CPU尋址。我們知道,目前主流的CPU都是32位或者64位的。那么這個位數指的是什么呢?就是指CPU的最大尋址能力。在CPU中,所有一切都是二進制表示的,那么一個32位CPU的尋址范圍就是2^32=4G。但有可能一台機器的實際物理內存沒有這么大,比如說只有2G。而程序員在編程的時候根本不用考慮實際物理內存多大,還是按照4G的內存來分配。這時,OS就提出了一個虛擬內存的概念,如果所尋址的數據實際上不在物理內存中,那就從“虛擬內存”中來獲取。這個虛擬內存可以是一個專門文件格式的磁盤分區(比如UNIX下的swap分區),也可以是硬盤上的某個足夠大的文件(比如win下的那個i386文件,好像是這個名字)。物理內存中長期不用的數據,也可以轉移到虛擬內存中。這樣的交換由OS來控制,用戶看起來就好像物理內存大了一樣。
虛擬內存尋址的一個好處就是可以時進程使用很大的虛擬內存地址,而無需考慮實際的物理內存的大小。這使得進程內存可以基於使用需要,從邏輯上分為幾個不同段。這些段可能映射到不連續的虛擬內存地址上,以使內存能夠夠增加。
為了共享RAM,需要有一個叫做交換磁盤(swap disk)的特殊磁盤空間。交換磁盤的重要目的是保存程序的通過LRU算法換出的內存,這樣可以使很多程序能夠共享有限的RAM。一旦非活動程序的RAM頁被寫入交換磁盤(Page Out),操作系統可以使空出來的內存用於其他活動的程序。如果非活動程序稍后又繼續執行,被page out的RAM頁又重新從交換磁盤中載入RAM(Page in)。這種重新載入RAM頁的動作就叫交換,而交換是非常消耗時間的,並且會降低相關程序的性能。
盡管交換磁盤確保並發的RAM使用量能大於實際的RAM總理,但是為了確保最佳性能,交換空間絕不要被活動程序所使用。這是因為從交換磁盤上讀取page out的RAM頁要比直接從RAM中讀取內存頁要慢14000倍。磁盤訪問都是以毫秒記的,而RAM訪問是以10億份之一秒記的。
2.1.1. Oracle的內存段類型
段(Segement)在OS上是對不同內存的使用目的和存放位置不同的區分。和一般的程序一樣,Oracle使用以下幾種段類型:
o 程序文本(Pragram Text)
文本段包括了程序本身的可執行的機器代碼(除動態鏈接庫以外)。文本段一般標識為只讀,因此它能被多個進程共享來跑同一個程序。
o 初始化全局數據(Initialized Global Data)
這一段包括了被編譯器初始化的全局數據,比如用於跟蹤數據的字符串。初始化數據能被修改,因此它不能被運行同一程序的多個進程共享。Oracle很少使用這個段。
o 未初始化全局數據(Uninitialized Global Data)
未初始化全局數據一般稱為BSS(Block Started by Symbol 以符號開始的塊)段。這一段包括了靜態分配的全局數據,這些數據在進程運行時被進程初始化。Oracle也很少使用這個段。
o 數據堆(Data Heap)
數據堆被用於進程在運行時,通過使用系統調用malloc()或sbrk()動態分配內存。Oracle將數據heap用於PGA。
o 執行堆棧(Execution Stack)
無論什么時候一個函數被調用,它的參數和返回上下文被push到一個執行堆棧中。返回上下文實際上是一組CPU注冊值,這些注冊值描述了進程在調用函數時那一刻的狀態。當調用結束后,堆棧被POP而上下文被保留,以使執行能從函數調用時的結構狀態立即執行下去。堆棧同時還保留了代碼塊的本地變量。堆棧大小依賴於函數嵌套或遞歸調用的深度、參數和本地變量所需的內存大小。
o 共享庫(Shared Libraries)
共享庫是一個與位置無關的可執行代碼集,這個集合實現了許多程序——特別是系統調用功能——所需要的功能。共享庫段也是只讀的,它被所有的進程(包括Oracle進程)共享。共享庫無需保存一份在內存中。當調用了共享庫中的一個函數后,進程需要打開共享庫文件,然后通過系統調用mmap()將它映射到它的地址空間去。
使用共享庫的另外一種方法是在程序文本段本身將需要的系統調用include進去。在那些不支持共享庫的操作系統中或用上面方式有問題時就需要這樣做。在大多數操作系統中,Oracle使用共享庫作為來實現系統調用而不是實現Oracle代碼本身。然而,Java類庫都是編譯好的,並且作為共享庫動態鏈接的。
o 共享內存段(Shared Memory Segment)
共享內存允許關聯的進程共同讀寫內存中的同樣數據。每個需要在共享內存段中尋址的進程都需要先將這段內存附到它自己的虛擬內存地址中去(一般通過shmat()系統調用實現)。Oracle SGA就是使用的共享內存段。
2.1.2. Oracle的內存管理模塊
Oracle中有兩個內存管理模塊。一個是內核服務內存管理模塊(Kernel Service Memory KSM);一個是內核通用堆管理模塊(Kernel Generic Heap KGH)
在X$表中,有兩種用於這兩個模塊的表,它們就是以KSM和KGH開頭的表。這兩個模塊相互非常緊密。內存管理模塊是負責與操作系統進行接口以獲取用於Oracle的內存,同時還負責靜態內存的分配。這個模塊中比較重要的X$表是X$ksmfs,它記錄了固定 sga、buffer cache、log buffer在內核服務內存中的分配。而堆管理模塊則負責動態內存的管理。這也就是為什么SGA和PGA中堆又叫可變內存區了。Shared Pool、Library cache和PGA的堆都是由這個模塊管理的。
一個堆(Heap)包括一個堆描述符和一個或多個內存擴展段(extent)。一個堆還可以包含子堆(Subheap)。這種情況下,堆描述符和子堆的擴展段可以被視為其父堆的大塊(chunk).堆描述符的大小依賴於堆的類型和堆的空閑列表和LRU列表所包含的列表(Header)頭的多少。一個擴展段又一個包含指向前一個和后一個擴展段指針(Pointer)的小的頭部,擴展段的其他內存就是堆可用於動態分配的內存。
除了還包含一個保留列表的這一特性外,Shared Pool中的子堆具有與Shared Pool本身相同的結構。內存是以Chunk為單位分配的。空閑的chunk按照大小來組織在相應的空閑列表(Free List)中。而未pin住的、可重建(unpinned recreatable)的chuck被維護在兩個分別用於周期性chunk和短期chunk的LRU鏈表中。子堆還有一個包含少許空閑內存的主永久內存chunk。子堆也許還包含子堆,一共可以嵌套到四層。
子堆的概念非常重要,因為大多數被緩存在shared pool中的對象實際上都被保存在子堆中,而不是保存在最上一層的堆中。在一個子堆中尋找空閑空間就相當於在shared pool中尋找空閑空間。它們的不同處就在於子堆可以通過分配新的擴展段(extent)來增長,而shared pool具有固定的擴展段數(10g引入SGA自動管理特性后,shared pool的擴展段數也是可變的)。為子堆分配新的擴展段受到擴展段大小的限制,因此會存在這樣的可能,因為沒有任何一個父堆可以分配一個所需最小擴展段大小的chunk導致在子堆中查找一個小的chunk失敗(拋4031錯誤)。
為了減少內存錯誤,在10g中,引入了多個隱含參數對擴展段進行控制,
_bct_buffer_allocation_min_extents - 每次buffer cache分配時的最小擴展段數(1)。
_compilation_call_heap_extent_size 編譯調用時分配堆的擴展段的大小(16384)10gR2引入。
_kgl_fixed_extents library cache內存分配時是否使用固定的擴展段大小(TRUE),10gR2引入。
_mem_std_extent_size 固定擴展段大小的堆的標准擴展段大小(4096),10gR2引入。
_minimum_extents_to_shrink 當內存收縮時,收縮的最少擴展段數(1)
_total_large_extent_memory 分配大擴展段的內存數(0),10gR1引入,10gR2中已經廢除。
_pga_large_extent_size(1048576)和_uga_cga_large_extent_size(262144)控制了當使用系統函數mmap()初始化時,PGA、UGA和CGA擴張段的最大大小。
2.1.3. 內存分配顆粒Granule
在Oracle的內存分配和管理中,有一個重要的單位:Granule(顆粒)。granule是連續虛擬內存分配的單位。Granule的大小依賴於SGA的總的大小(即SGA_MAX_SIZE大小)。當SGA小於128M時,granule為4M,SGA大於128M時,granule為16M。
Buffer Cache、Shared Pool、Large Pool和Java Pool(在10g中,還包括Streams Pool、KEEP buffer cache、RECYCLE buffer cache、nK Buffer Cache、ASM Buffer Cache)的增長和收縮都是以granule為單位的。SGA的各個組件的大小增長、收縮情況可以通過視圖v$sga_dynamic_components(這個視圖在1.1.2中有介紹)來觀察。
在實例啟動時, Oracle先分配granule條目(Entry),使所有granule能支持到SGA_MAX_SIZE的空間大小。如果沒有設置PRE_PAGE_SGA和LOCK_SGA,在實例啟動時,每個組件請求它所需要的最少granule。
因此最小SGA(占用物理內存)是3個granule,包括:
o 固定SGA(包括redo buffer)一個granule
o Buffer Cache一個granule
o Shared Pool一個granule
我們可以通過“ALTER SYSTEM”命令來修改分配給各個組件的granule數。當DBA想要給組件增加granule時,需要考慮實例中是否還有足夠的granule來分配給新增加的組件大小(即增大后,各個組件之和小於SGA_MAX_SIZE)。有一點要注意,ALERT SYSTEM指定的是新的組件大小,是內存的大小,不是granule數。而Oracle在分配內存時,會以granule為單位,如果新分配的大小不是granule大小的倍數,則會使用最接近且大於分配數的granule的倍數值。例如,Unix中,當granule為16M,新分配組件大小為120M(不是16的倍數),Oracle則會實際分配128M(16×8);32位windows下,SGA小於1G時granule是4M,大於1G時granule是8M。
執行ALERT SYSTEM擴展組件大小后,前台進程(即執行ALTER SYSTEM命令)的進程會將SGA中的可用的granule(按照擴展新增大小計算)先保留下來。當這些granule被保留后,前台進程將后續處理交給后台進程。后台進程負責將這些保留的granule加到指定的組件的granule列表中去。這就完成了一次對SGA組件的擴展。
Granule的默認大小是根據以上規則規定的,但也可以通過隱含參數_ksm_granule_size來修改(強烈不建議修改)。另外,隱含參數_ksmg_granule_locking_status可以設置內存分配是否強制按照granule為單位進行分配。
2.1.4. SGA內存
當一個Oracle實例啟動后,主要的SGA區的大小一開始基於初始化參數計算得出。這些大小可以通過show sga顯示(實例啟動后也會顯示出這些信息)。但是,在共享內存段分配之前,每個區(Area)的大小都只有大概一個內存頁大小。當需要時,這些區被分為一些子區(sub-area),因此沒有一個子區會大於操作系統所限制的共享內存段(UNIX下受SHMMAX限制)的大小。對於可變區,有一個操作系統規定的最小子區大小,因此可變區的大小是最小子區大小的倍數。
如果可能,Oracle會為整個SGA分配一個單獨的共享內存段。然而,如果SGA大於操作系統限制的單個共享內存段的大小時,Oracle會使用最佳的算法來將所有子區組織在多個共享段中,並且不超過SGA最大大小的限制。
嚴重的頁入/頁出會導致很嚴重的系統性能問題。然而,大內存消耗導致的間斷的頁入/頁出是沒有影響的。大多數系統都有大量的非活動內存被page out而沒有什么性能影響。但少量的頁出也是有問題的,因為這會導致SGA中那些中度活性的頁會經常page out。大多數操作系統提供了一個讓Oracle鎖住SGA(設置lock_sga參數為TRUE)到物理內存中的機制以防止page out。在某些操作系統中,oracle需要有特定的系統權限來使用這一特性。
當共享池分配了一個chunk,代碼會返回給執行分配的函數一個注釋。這些注釋可以通過表X$KSMSP的字段KSMCHCOM查到。它們同時描述了這塊內存分配的目的。如以下語句:
select ksmchcom, ksmchcls, ksmchsiz from x$ksmsp;
2.1.5. 進程內存
進程包括程序代碼(文本)、本地數據域(進程堆棧、進程堆【主要是PGA】和進程BSS【未初始化的全局數據】)和SGA。程序代碼的大小由基本內核、內核、聯機的網絡情況以及所使用的操作系統決定的。SGA大小是由Oracle初始化參數決定的。而這兩部分是共享。隨着用戶的增加,它們與單個Oracle服務進程的關系越來越小。它們是可以通過修改Oracle配置參數來改變的。
本地數據域中的堆棧是根據需要來增大、縮小的。然而,堆就不會釋放內存了。堆的主要組成部分是PGA。而影響PGA的主要因素是sort_area_size(如果沒有配置PGA_AGGREGATE_TARGET的話)。因此,非自動PGA內存管理模式下,可以通過控制sort_area_size來控制PGA的大小。
因此,總的來說,可以有以下方法來限制進程內存大小:
1、降低相關的內核參數;
2、通過Oracle參數來降低SGA;
3、通過減小sort_area_size來降低PGA。
在UNIX平台中,一般通過操作系統命令如“ps”或“top”來定位一個經常的內存大小。這些工具可以預警那些大量占用內存的進程。但是,這些工具統計出來的Oracle進程的內存情況往往是是實際PGA內存是有出入的。
這是為什么呢?有兩種原因會導致錯誤的進程內存報告錯誤:將那些非進程私有的(如共享內存)計算進去了;操作系統沒有回收空閑內存。下面詳細解釋它們。
· 統計了非私有內存
一個內存中的進程包含以下幾個部分:
o 共享內存(SGA)
o 共享庫(包括公用和私有的)
o 私有數據(指數據段【DATA】或堆)
o 可執行部分(指文本【TEXT】段)
而SGA和TEXT部分是被所有Oracle進程共享的。它們只會被映射到內存中一次,而不會未每個進程做映射。因此,這些內存不是一個新的Oracle進程導致的內存增加部分。
· 操作系統沒有回收空閑內存
通常,一部分內存被一個進程釋放了后,並沒有立即返回到操作系統的空閑池中,而是繼續保持與進程的關聯,直到操作系統內存不足時,才會回收這些空閑頁。所以操作系統工具報告的進程內存大小可能會被實際大。
從Oracle的角度來看,一個服務進程的私有內存包括多個Oracle“堆(heap)”。在Oracle的術語中,堆就是一個受管理的內存區。而對於操作系統來說,這僅僅時分配給一個應用程序的另外一塊內存而已。PGA和UGA中都關聯到堆。
Oracle當前或者曾經在這些堆中擁有的內存總數可以通過以下語句統計出來:
SQL> select statistic#, name, value
2 from v$sysstat
3 where name like '%ga memory%';
STATISTIC# NAME VALUE
---------- -------------------------- ------------------------------
20 session uga memory 8650004156
21 session uga memory max 778811244
25 session pga memory 50609488
26 session pga memory max 58007200
查詢所有會話的堆大小可以用以下語句實現:
select value, n.name|| '('||s.statistic#||')' , sid
from v$sesstat s , v$statname n
where s.statistic# = n.statistic#
and n.name like '%ga memory%'
order by value;
但是,查詢出來大的PGA或UGA並不一定說明有問題。它們的大小受到以下參數影響:
o SORT_AREA_SIZE
o SORT_AREA_RETAINED_SIZE
o HASH_AREA_SIZE
另外,過多的使用PL/SQL結構體(如PL/SQL TABLE、ARRAY)也會導致會話內存增大。
2.2. Oracle的內存的分配、回收
Oracle中的共享內存區的分配都是以chunk為最小單位的。Chunk不是一個固定值,它是一個擴展段(extent)中一塊連續的內存。而Oracle的內存(其他存儲,如磁盤也是)的增長是以擴展段為基礎的。
2.2.1. 空閑列表和LRU鏈表
空閑的chunk按照大小來組織在相應的空閑列表(Free List)中。而未pin住的、可重建(unpinned recreatable)的chuck被維護在兩個分別用於周期性chunk和短期chunk的LRU鏈表中。子堆還有一個包含少許空閑內存的主永久內存chunk。
2.2.2. 空閑內存分配和回收
空閑內存都是由空閑列表(free list)統一管理、分配的。每個空閑的chunk(大塊)都會屬於也只屬於一個空閑列表。空閑列表上的chunk的大小范圍是由bucket來划分的。Bucket直譯為“桶”,在西方,往往用桶來盛裝一定品質范圍的物品,以便查找。比如,在采礦時,用不同的桶來裝不同純度的礦石,在桶上標明礦石的純度范圍,以便在提煉時可以采用不同工藝。在這里,我們也可以把bucket視為一種索引,使Oracle在查找空閑塊時,先定位所需的空閑塊在哪個bucket的范圍內,然后在相應的空閑列表中查找。
一次內存的分配過程如下:當一個進程需要一個內存的大塊(chunk)時,它會先掃描目標空閑列表(每個空閑列表對應有一個bucket,bucket是這個空閑列表的中chunk的大小范圍)以查找最適合大小的chunk。如果找不到一個大小正好合適的chunk,則繼續掃描空閑列表中更大的chunk。如果找到的可用chunk比所需的大小大24字節或者更多,則這個chunk就會被分裂,剩余的空閑chunk又被加到空閑列表的合適位置。如果空閑列表中沒有一個chunk能滿足要求的大小,則會從非空的相鄰bucket的空閑列表中取最小的chunk。如果所有空閑列表都是空的,就需要掃描LRU鏈表釋放最近最少使用的內存。當chunk空閑時,如果相鄰的chunk也是空閑的,它們可能會結合(coalesce)起來。
當所有空閑列表都沒有合適的空閑chunk可用時,就開始掃描LRU鏈表,將最近最少使用的chunk釋放掉(如有空閑的相鄰chunk,則結合),放到相應的空閑列表種去,直到找到合適的chunk或者達到最大查找數限制。如果在釋放了LRU鏈表中最近最少使用的chunk后沒沒有找到合適空閑chunk,系統就拋4031錯誤。
如果找到了可用空閑chunk,就將它從空閑列表中移出,放到LRU鏈表中去。
下面介紹一下Shared Pool的分配和回收。
2.2.3. Shared Pool的分配和回收
在Shared Pool中,空閑列表掃描、管理和chunk分配的操作都是受到shared pool latch保護的。顯然,如果shared pool含有大量的非常小的空閑chunk,則掃描空閑列表時間將很長,而shared pool latch則會要保持很久。這就是導致shared pool latch爭用的主要原因了。有些DBA通過增加shared pool來減少shared pool latch爭用,這種方法是沒有用的,可能反倒會加劇爭用(作為短期解決方法,可以flush shared pool;而要真正解決問題,可以采取在實例啟動時keep住那些可能會斷斷續續使用的對象【這種對象最容易導致shared pool碎片】)。
只有執行了ALTER SYSTEM FLUSH SHARED_POOL才會使shared pool的空閑chunk全結合起來。因此,即使shared pool空閑內存之和足夠大,也可能出現內存請求失敗(空閑內存都是一些很小的碎片chunk)。
實際上,oracle實例啟動時,會保留大概一半的Shared Pool,當有內存壓力時逐漸釋放它們。Oracle通過這種方法限制碎片的產生。Oracle的少量空襲內存(spare free memory)和X$表即其他持久內存結構一起,隱含在shared pool的主要持久內存chunk中。這些內存不在shared pool的空閑列表中,因此能夠立即被分配。但是,卻包含在視圖V$SGASTAT的free memory的統計數據中。
SQL> select * from v$sgastat
2 where pool = 'shared pool'
3 and name = 'free memory';
POOL NAME BYTES
------------ -------------------------- ----------
shared pool free memory 18334468
SQL>
而spare free memory可以用以下方式查出:
select avg(v.value) shared_pool_size, greatest(avg(s.ksmsslen) - sum(p.ksmchsiz), 0) spare_free, to_char( 100 * greatest(avg(s.ksmsslen) - sum(p.ksmchsiz), 0) / avg(v.value), '99999' ) || '%' wastage from sys.x$ksmss s, sys.x$ksmsp p, sys.v$parameter v where s.inst_id = userenv('Instance') and p.inst_id = userenv('Instance') and p.ksmchcom = 'free memory' and s.ksmssnam = 'free memory' and v.name = 'shared_pool_size';
注意:如果10g中用了SGA內存自動管理。以上語句可能無法查出。
當需要時,少量的空閑內存chunk會被釋放到shared pool中。除非所有這些少量空閑內存倍耗盡了,否則不會報4031錯誤。如果實例在負載高峰運行了一段時期之后還有大量的少量空閑內存,這就說明shared pool太大了。
而未pin住的、可重建(unpinned recreatable)的chuck被維護在兩個分別用於周期性chunk和短期chunk的LRU鏈表中。這兩個LRU鏈表的長度可以通過表X$KGHLU查到,同時還能查到被flush的chunk數、由於pin和unpin而加到和從LRU鏈表中移出的chunk數。X$KGHLU還能顯示LRU鏈表沒有被成功flush的次數,以及最近一次這樣的請求失敗的請求大小。
SQL> column
kghlurcr heading "RECURRENT|CHUNKS"
SQL> column kghlutrn heading "TRANSIENT|CHUNKS"
SQL> column kghlufsh heading "FLUSHED|CHUNKS"
SQL> column kghluops heading "PINS AND|RELEASES"
SQL> column kghlunfu heading "ORA-4031|ERRORS"
SQL> column kghlunfs heading "LAST ERROR|SIZE"
SQL> select
2 kghlurcr "RECURRENT_CHUNKS",
3 kghlutrn "TRANSIENT_CHUNKS",
4 kghlufsh "FLUSHED_CHUNKS",
5 kghluops "PINS AND_RELEASES",
6 kghlunfu "ORA-4031_ERRORS",
7 kghlunfs "LAST ERROR_SIZE"
8 from
9 sys.x$kghlu
10 where
11 inst_id = userenv('Instance');
RECURRENT_CHUNKS TRANSIENT_CHUNKS FLUSHED_CHUNKS PINS AND_RELEASES ORA-4031_ERRORS LAST ERROR_SIZE
---------------- ---------------- -------------- ----------------- --------------- ---------------
327 368 0 7965 0 0
865 963 2960 102138 0 0
1473 5657 96 20546 1 540
0 0 0 0 0 0
如果短期鏈表的長度大於周期鏈表的長度3倍以上,說明Shared Pool太大,如果chunk flash對LRU 操作的比例大於1/20,則說明Shared Pool可能太小。
2.3. Oracle在UNIX下的內存管理
在UNIX下,Oracle是以多進程的方式運行的。除了幾個后台進程外,Oracle為每個連接起一個進程(進程名稱中,LOCAL = NO)。而UNIX下的內存分為兩類,一種叫共享內存,即可以被多個進程共享;還有一種就是私有進程,只能被所分配的進程訪問。更加Oracle內存區的不同特性,SGA內存可以被所有會話進程共享,因此是從共享內存中分配的;而PGA是進程私有的,因而是從私有內存中分配的。
2.3.1. 共享內存和信號量
在Unix下,Oracle的SGA是保存在共享內存中的(因為共享內存是可以被多個進程共享的,而SGA正是需要能被所有Oracle進程所共享)。因此,系統中必須要有足夠的共享內存用於分配SGA。而信號量則是在共享內存被多個進程共享時,防止發生內存沖突的一種鎖機制。
UNIX下,對於內存的管理配置,是由許多內核參數控制,在安裝使用Oracle時,這些參數一定要配置正確,否則可能導致嚴重的性能問題,甚至Oracle無法安裝、啟動。涉及共享內存段和信號量的參數:
參數名稱 |
建議大小(各個平台的Oracle建議值可以去metalink上找) |
參數描述 |
SHMMAX |
可以得到的物理內存 (0.5*物理內存) |
定義了單個共享內存段能分配的最大數. SHMMAX 必須足夠大,以在一個共享內存段中能足夠分配Oracle的SGA空間.這個參數設置過低會導致創建多個共享內存段,這會導致Oracle性能降低. |
SHMMIN |
定義了單個共享內存段能分配的最小數. |
|
SHMMNI |
512 |
定義了整個系統共享內存段的最大數。 |
NPROC |
4096 |
系統的進程總數 |
SHMSEG |
32 |
定義了一個進程能獲取的共享內存段的最大數. |
SEMMNS |
(NPROC * 2) * 2 |
設置系統中的信號數. SEMMNS的默認值是128 |
SEMMNI |
(NPROC * 2) |
定義了系統中信號集的最大數 |
SEMMSL |
與Oracle中參數processes大小相同 |
一個信號集中的信號最大數 |
SEMMAP |
((NPROC * 2) + 2) |
定義了信號映射入口最大數 |
SEMMNU |
(NPROC - 4) |
定義了信號回滾結構數 |
SEMVMX |
32768 |
定義了信號的最大值 |
信號量(Semaphore):對於每個相關的process都給予一個信號來表示其目前的狀態。主要的目地在於確保process間能同步,避免process存取shared data時產生碰撞( collisions)的情況。可以把信號量視為操作系統級的用於管理共享內存的鑰匙,每個進程需要有一把,一個信號量集就是一組鑰匙,這組鑰匙可以打開共同一個共享內存段上的所。當一個進程需要從共享內存段中獲取共享數據時,使用它自己的鑰匙打開鎖,進入后,再反鎖以防止其他進程進來。使用完畢后,將鎖釋放,這樣其他進程就可以存取共享數據了。
共享內存(Shared Memory)是指同一塊記內存段被一個以上的進程所共享。這是我們所知速度最快的進程間通訊方式。使用共享內存在使用多CPU的機器上,會使機器發揮較佳的效能。
可以通過以下命令檢查你當前的共享內存和信號量的設置:
$ sysdef | more
當Oracle異常中止,如果懷疑共享內存沒有被釋放,可以用以下命令查看:
$ipcs -mop
IPC status from /dev/kmem as of Thu Jul 6 14:41:43 2006
T ID KEY MODE OWNER GROUP NATTCH CPID LPID
Shared Memory:
m 0 0x411c29d6 --rw-rw-rw- root root 0 899 899
m 1 0x4e0c0002 --rw-rw-rw- root root 2 899 901
m 2 0x4120007a --rw-rw-rw- root root 2 899 901
m 458755 0x0c6629c9 --rw-r----- root sys 2 9113 17065
m 4 0x06347849 --rw-rw-rw- root root 1 1661 9150
m 65541 0xffffffff --rw-r--r-- root root 0 1659 1659
m 524294 0x5e100011 --rw------- root root 1 1811 1811
m 851975 0x5fe48aa4 --rw-r----- oracle oinstall 66 2017 25076
然后它ID號清除共享內存段:
$ipcrm –m 851975
對於信號量,可以用以下命令查看:
$ ipcs -sop
IPC status from /dev/kmem as of Thu Jul 6 14:44:16 2006
T ID KEY MODE OWNER GROUP
Semaphores:
s 0 0x4f1c0139 --ra------- root root
... ...
s 14 0x6c200ad8 --ra-ra-ra- root root
s 15 0x6d200ad8 --ra-ra-ra- root root
s 16 0x6f200ad8 --ra-ra-ra- root root
s 17 0xffffffff --ra-r--r-- root root
s 18 0x410c05c7 --ra-ra-ra- root root
s 19 0x00446f6e --ra-r--r-- root root
s 20 0x00446f6d --ra-r--r-- root root
s 21 0x00000001 --ra-ra-ra- root root
s 45078 0x67e72b58 --ra-r----- oracle oinstall
當Oracle異常中止,可以根據信號量ID,用以下命令清除信號量:
$ipcrm -s 45078
一個共享內存段可以可以分別由以下兩個屬性來定位:
o Key:一個32位的整數
o 共享內存ID:系統分配的一個內存
$ ipcs -mop
IPC status from /dev/kmem as of Thu Jul 6 10:32:12 2006
T ID KEY MODE OWNER GROUP NATTCH CPID LPID
Shared Memory:
m 0 0x411c29d6 --rw-rw-rw- root root 0 899 899
m 1 0x4e0c0002 --rw-rw-rw- root root 2 899 901
m 2 0x4120007a --rw-rw-rw- root root 2 899 901
m 458755 0x0c6629c9 --rw-r----- root sys 2 9113 17065
m 4 0x06347849 --rw-rw-rw- root root 1 1661 9150
m 65541 0xffffffff --rw-r--r-- root root 0 1659 1659
m 524294 0x5e100011 --rw------- root root 1 1811 1811
m 851975 0x5fe48aa4 --rw-r----- oracle oinstall 66 2017 25076
2.3.2. 私有內存
對於PGA,由於是分配的私有內存,不存在爭用問題,因而OS也沒有相應的信號量來控制(同理,PGA中也沒有latch)。在10g之前,PGA的私有內存是通過函數malloc()和sbrk()進行分配擴展的,10g之后,私有內存是通過函mmap()進行初始化的。
另外,還有一點要注意的是,和SGA不同,PGA內存的大小不是固定的,是可以擴展的(前者的大小是固定的,無法增長的)。因而進程的私有內存是會增長的。因此,一個規划好的系統發生內存不足的情況通常是由於進程的私有內存或進程數量突然增長造成的(PGA_AGGREGATE_TARGET參數能盡量控制但不保證PGA內存總量被控制在一定數值范圍內)。
隱含參數_pga_large_extent_size(1048576)和_uga_cga_large_extent_size(262144)就控制了當初始化時,PGA、UGA和CGA擴張段的最大大小。
2.3.3. SWAP的保留區
swap(交換)區,是UNIX中用來分配虛擬內存的一塊特殊的磁盤分區。UNIX啟動每一個進程,都需要在swap區預留一塊和內存一樣大小的區域,以防內存不夠時作數據交換。當預留的swap區用完時,系統就不能再啟動新的進程。比如,系統物理內存是4G,而設置的交換區只有1G,那么可以計算得出大概3G的內存會浪費(Buffer Cache除外,可能有2G浪費)。
在HP-UX中,參數swapmen_on可以讓系統創建一個pseudo-swap(偽交換區),大小為系統物理內存的3/4,但是這個偽交換區並不占用任何內存和硬盤資源。只是說,讓系統認為,交換區的大小是1+4*3/4=4G,而不是1G,就是說可以啟動更多的進程,避免內存的浪費。
一般系統物理內存不大的時候,設置交換區是物理內存的2-4倍,swapmen_on設置為1或0都沒什么影響,但是當系統內存很大如8G時,因為swap一般不設為16G-32G,這時開啟swapmen_on就很必要了。
hp建議,即使設置了swapmen_on,也將你的swap為物理內存的1-1.5倍。
swap大小設置不當,也同樣會造成系統的性能問題。因為,swap中首先會為各個進程留出一個保留區,這部分區去掉后,swap的可用大小就比較小了(這就是為什么用swapinfo可能看到Total PCT USED為100%而dev PCT USED為0%)。當swap可用區不足,而由內存需要被page out到swap區中,就需要先將swap區中一些頁被page in到物理內存中去,因而導致發生交換,產生性能問題。
swap的使用情況可以通過swapinfo查看:
> swapinfo -mt
Mb Mb Mb PCT START/ Mb
TYPE AVAIL USED FREE USED LIMIT RESERVE PRI NAME
dev 4096 0 4096 0% 0 - 1 /dev/vg00/lvol2
dev 8000 0 8000 0% 0 - 1 /dev/vg00/swap2
reserve - 12026 -12026
memory 20468 13387 7081 65%
total 32564 25413 7151 78% - 0 -
2.4. Oracle在windows下的內存管理
2.4.1. Windows內存系統概述
Windows NT使用一個以頁為基礎的虛擬內存系統,該系統使用32位線性地址。在內部,系統管理被稱為頁的4096字節段中的所有內存。每頁的物理內存都被備份。 對於臨時的內存頁使用頁文件(pagefile),而對於只讀的內存頁,則使用磁盤文件。在同一時刻,最多可以有16個不同的頁文件。代碼、資源和其它只讀數據都是通過它們創建的文件直接備份。
Windows NT為系統中的每一個應用程序(進程)提供一個獨立的、2 GB的用戶地址空間。對於應用程序來說,好象是有2 GB的可用內存,而不用考慮實際可用的物理內存的量。如果某個應用程序要求的內存比可用的內存更多時,Windows NT是這樣滿足這種要求的,它從這個和/或其他的進程把非關鍵內存分頁(paging)到一個頁文件,並且釋放這些物理內存頁。結果,在Windows NT中,全局堆不再存在。相反,每一個進程都有其自己的32位地址空間,在其中,該進程的所有內存被分配, 包括代碼、資源、數據、DLL(動態鏈接庫),和動態內存。實際上,系統仍然要受到可用的硬件資源的限制,但是實現了與系統中應用程序無關的、對於可用資源的管理。
Windows NT在內存和地址空間之間作出了區分。每個進程分配到2 GB的用戶地址空間,而不管對於該進程的實際可用物理內存有多少。而且,所有進程都使用相同范圍的線性32位地址,范圍從0000000016-7FFFFFFF16,而不考慮可用內存的地址。Windows NT負責在適當的時間把內存頁映射(paging)到磁盤以及從磁盤頁映射回內存,使得每個進程都確保能夠尋址到它所需要的內存。盡管有可能出現兩個進程試圖同時訪問同一虛擬地址上的內存,但是,實際上Windows NT虛擬內存管理程序是在不同的物理位置描述這兩個內存的位置。而且這兩個地址都不見得與原始的虛擬地址一致。這就是虛擬內存。
Win32環境下,32位的地址空間轉化為4GB的虛擬內存。默認情況下,將一半(2GB)分配給用戶進程(因而一個進程的最大可用虛擬內存為2G,oracle進程同樣受此限制),另一半(2GB)分配給操作系統。
因為虛擬內存的存在,一個應用程序能夠管理它自己的地址空間,而不必考慮在系統中對於其它進程的影響。在Windows NT中的內存管理程序負責查看在任何給定的時間里,所有的應用程序是否有足夠的物理內存進行有效的操作。Windows NT操作系統下的應用程序不必考慮和其它應用程序共享系統內存這個問題。並且,即使在應用程序自己的地址空間內,它們仍能夠與其它的應用程序共享內存。
區分內存和地址空間的一個好處是,為應用程序提供了將非常大的文件加載到內存的能力。不必將一個大的文件讀進內存中,Windows NT為應用程序保留該文件所需的地址范圍提供了支持。然后,在需要的時候,該文件部分就可以被瀏覽了(物理性地讀進內存)。通過虛擬內存的支持,對於大段的動態內存的分配同樣可以做到這一點。
在任意給定的時間,進程中每個地址都可以被當作是空閑的(free)、保留的(reserved)或已提交的(committed)。進程開始時,所有地址的都是空閑的,意味着它們都是自由空間並且可以被提交到內存,或者為將來使用而保留起來。在任何空閑的地址能夠被使用前,它必須首先被分配為保留的或已提交的。試圖訪問一個保留的或已提交的地址都將產生一個訪問沖突異常(access violation exception)。
一個進程中的所有2 GB的地址要么為了使用而是空閑的、要么為了將來的使用而是保留的、要么已提交到特定的內存(在使用的)。
一旦地址被以保留的或者已提交的形式分配,VirtualFree是唯一可以釋放它們的方法棗那就是,將它們返回到自由的地址。VirtualFree還可以用來對已提交的頁解除提交,同時,返回這些地址到保留狀態。當解除地址的提交時,所有與該地址相關的物理內存和頁文件空間都被釋放。
在Windows NT中的進程有一個被稱為工作組(working set)的最小頁,是為了進程能夠順利地運行,在運行時在內存中必須被提供。Windows NT在啟動時為一個進程分配了默認數量的頁數,並且逐漸地調整該數,使得系統中所有激活的進程的性能達到一種平衡的最優。當一個進程正在運行時(實際上是,是一個進程的線程正在運行時),Windows NT“盡量”確保該進程的工作組頁總是駐留在物理內存中。工作集即在物理內存中保持的虛擬頁面的子集,分進程工作集和系統工作集。
2.4.2. Windows下Oracle的內存配置
在windows下,Oracle實例作為一個單獨的進程運行,這個進程是一個標准的Win32應用程序,它能夠申請最大為2G的虛擬內存地址空間,所有用戶連接后后台線程的分配內存(包括像buffer cache那些全局內存)必須小於2G(64位平台中無此限制,32位平台中可以通過設置參數use_indirect_data_buffers來突破這一限制,不詳述)。
Oracle可以運行於windows NT下的任何版本,但是Oracle一般不推薦將Oracle數據庫運行在PDC(主域控服務器)或BDC(備域控服務器)下。這是因為域控服務器會需要大量的文件緩存(這會消耗大量內存,影響到Oracle)和網絡資源。
而文件緩存帶來的另外一個問題就是,這個機器到底是作為一個專用的數據庫服務器還是一個混合服務器。因為Oracle數據庫不會用到文件緩存(log buffer就相當於Oracle自己的文件緩存),它通過直接寫磁盤來避免文件緩沖。
在專用數據庫服務器系統上,用戶一定要確保沒有使用到頁文件(pagefile即虛擬內存文件)。否則,可以通過修改Oracle參數或者增大物理內存來避免。如果大量額頁被持續的移入、移出到虛擬內存中,會嚴重影響到性能。
如果是專用服務器系統,有以下建議:
o 如果分配給Oracle的總內存能保證不會超過物理內存,則虛擬內存頁可以被設置位物理內存的50%,並且可以增長到物理內存的100%大小(在my computer => properties => Advanced => Performance => Settings => Advanced => Virtual Memory => Change中設置,設置Initial size為物理內存的50%,Maximum Size和物理內存大小相同);
o 對於主要是運行純Oracle數據庫的系統(但不是專用),一般推薦虛擬內存頁大小在1倍到1.5倍於物理內存大小之間;
o 對於物理內存大於2G的機器,要求虛擬內存頁最少為2G。
一個機器上能被用於分配的總內存等於物理內存加上擴展前的虛擬內存頁大小。你一定要避免設置參數如buffer_cache_size或其他相關參數導致Oracle要求分配內存大於物理內存。盡管Oracle的分配內存大小是限制在總內存(物理內存+最小虛擬內存)之內,但是,對虛擬內存頁的訪問是非常慢的,會直接影響到系統性能,因此Oracle分配內存要小於物理內存大小以避免發生內存交換。
如果系統是混合應用,除了Oracle數據庫外還運行了其他程序,這時就需要考慮設置虛擬內存頁大於物理內存了。那些當前不活動的進程可以減少它們的工作區(working set 即物理內存)以使活動進程能增加工作區。如果Oracle數據庫運行在這樣的機器上,則推薦虛擬內頁最少1.5到2倍的物理內存大小,特別是在內存大於2G時。
在Oracle 8.1.x之前,啟動Oracle服務時不會啟動Oracle實例。此時(即只啟動了Oracle服務,沒有啟動實例),分配給Oracle.EXE的主要內存是給相關DLL的,大概20M(各個版本的DLL不同,因此占用內存情況也不同)。9i之后,Oracle實例會隨着服務啟動,不過我們可以在服務啟動后再關閉實例,這時就可以觀察出Oracle服務所占用的內存大小了:
這是windows下一個Oracle 10g的實例關閉后內存占用情況。我們看到此時Oracle服務占用的內存是32M。
Windows下,可以使用以下語句來計算Oracle占用的虛擬內存大小:
select sum(bytes)/1024/1024 + 22/*DLL占用內存*/ Mb from (select bytes from v$sgastat – SGA內存 union select value bytes from – 會話內存 v$sesstat s, v$statname n where n.STATISTIC# = s.STATISTIC# and n.name = 'session pga memory' union select 1024*1024*count(*) bytes – 線程堆棧 from v$process );
在實例啟動時,所有全局內存頁都被保留和提交(所有共享全局區、Buffer Cache和Redo Buffer)——可以通過TopShow觀察到實例啟動后,所需要的Page File都已經被分配。但只有一小部分內存頁(如果沒有設置PRE_PAGE_SGA的話;這小部分內存以granule為單位,固定SGA【包括redo buffer】一個、Buffer Cache一個、Shared Pool一個)被觸及(touch)而已經分配到工作組(working set)中,而其他更多的頁需要使用時才分配到工作組中。
通過設置注冊表可以設置Oracle進程的最小工作組大小和最大工作組大小:
o ORA_WORKINGSETMIN或ORA_%SID%_WORKINGSETMIN:Oracle.EXE進程的最小工作組大小(M為單位)
o ORA_WORKINGSETMAX或ORA_%SID%_WORKINGSETMAX:Oracle.EXE進程的最大工作組大小(M為單位)
這些注冊項需要加在HKEY_LOCAL_MACHINE -> SOFTWARE -> ORACLE或者HKEY_LOCAL_MACHINE -> SOFTWARE -> ORACLE -> HOMEn下。
在混合服務器下,這種設置能防止Oracle進程的內存被其他進程爭用。設置這些注冊項時,需要考慮PRE_PAGE_SGA的設置。如前所述,PRE_PAGE_SGA使Oracle實例啟動時“觸及”所有的SGA內存頁,使它們都置入工作組中,但同時會增長實例啟動時間。
ORA_WORKINGSETMIN是一個非常有用的參數,它能防止Oracle進程的工作組被縮小到這一限制值之下:
o 如果設置了PRE_PAGE_SGA,實例啟動后,工作組就大於這個限制值。在實例關閉之前,就不會低於這個值;
o 如果沒有PRE_PAGE_SGA,當實例的工作組一旦達到這個值后,就不會再低於這個值。
另外,在10g之前,存在一個Bug(642267),導致在windows系統中設置了LOCK_SGA后,實例啟動報ORA-27102錯誤。可以通過設置ORA_WORKINGSETMIN最小為2M來解決這個問題。但是,windows中,LOCK_SGA只影響Oracle進程中的SGA部分,而分配給用戶會話的內存不會受影響,這部分內存還是可能會產生內存交換。
2.4.3. SGA的分配
當實例啟動時,oracle在虛擬內存地址空間中創建一段連續的內存區,這個內存區的大小與SGA所有區相關參數有關。通過調用Win32 API接口“VirtualAlloc”,在接口函數的參數中指定MEM_RESERVE | MEM_COMMIT內存分配標識和PAGE_READWRITE保護標識,這部分內存始終會被保留和提交。這就保證所有線程都能訪問這塊內存(SGA是被所有線程共享的),並且這塊內存區始終有物理存儲(內存或磁盤)所支持。
VirtualAlloc函數不會觸及這塊區域內的內存頁,這就是說分配給SGA組件(如buffer cache)的內存在沒有觸及之前是不會到工作組中去的。
VirtualAlloc
VirtualAlloc函數保留或提交調用此函數的進程的虛擬內存地址空間中的一段內存頁。如果沒有指定MEM_RESET,被這個函數分配的內存自動初始化為0,
Buffer Cache通常被創建為一個單一的連續內存區。但這並不是必須的,特別是當使用了非常大的Buffer Cache時。因為dll內存和線程分配的內存會導致虛擬地址空間產生碎片。
當一個進程創建后,windows NT會在進程的地址空間中創建一個堆(heap),這個堆被稱為進程的默認堆。許多Win32 API調用接口和C運行調用接口(如malloc、localalloc)都會使用這個默認堆。當需要時,進程能在虛擬內存地址空間中創建另外的命名堆。默認堆創建為1M大小(被保留和提交的)的內存區,當執行分配或釋放這個堆的操作時,堆管理器提交或撤銷這個區。而訪問這個區是通過臨界區來串行訪問的,所以多個線程不能同時訪問這個區。
當進程創建了一個線程后,windows NT會為線程堆棧(每個現場都有自己的堆棧)保留一塊地址空間區域,並提交一部分這些保留區。當一個進程連接到標准區時,系統為堆棧保留一個1M大小的虛擬地址空間並提交這個區頂部的兩個頁。當一個線程分配了一個靜態或者全局變量時,多個線程可以同時訪問這個變量,因此存在變量內容被破壞的潛在可能。本地和自動變量被創建在線程的堆棧中,因而變量被破壞的可能性很小。堆棧的分配是從上至下的。例如,一個地址空間從0x08000000到0x080FF000的堆棧的分配是從0x080FF000到0x08001000來提交內存頁,如果訪問在0x08001000的頁就會導致堆棧溢出異常。堆棧不能增長,任何試圖訪問堆棧以外的地址的操作都可能會導致進程被中止的致命錯誤。
2.4.4. 會話內存的分配
當監聽創建了一個用戶會話(線程和堆棧)時,Oracle服務進程就通過調用Win32 API函數創建用戶連接所必須的內存結構,並且指定MEM_RESERVE | MEM_COMMIT標識,以保持和提交給線程私有的地址空間區域。
當調用了VirtualAlloc來在指定地址保留內存,它會返回一個虛擬地址空間到下一個64K大塊(chunk,windows內存分配最小單位)的地址。Oracle調用VirtualAlloc時不指定地址,只傳一個NULL在相應參數中,這會返回到下一個64K大塊的地址。因此用戶會話在分配PGA、UGA和CGA時同時也遵循64K的最小粒度,來提交倍數於這個粒度值的內存頁。許多用戶會話經常分配許多小於64K的內存,這就導致地址空間出現碎片,因為許多64K區只有兩個頁被提交。
一旦地址空間被用戶會話占滿了后,如果要再創建一個新會話,就會存在無法分配到地址空間的危險。將可能報以下錯誤:
ORA-12500 / TNS-12500
TNS:listener failed to start a dedicated server process
也可能報這些錯誤:
o ORA-12540 / TNS-12540 TNS:internal limit restriction exceeded
o NT-8 Not enough storage is available to process this command
o skgpspawn failed:category = ....
o ORA-27142 could not create new process
o ORA-27143 OS system call failure
o ORA-4030 out of process memory when trying to allocate ....
因為地址空間碎片問題和DLL被載入了oracle服務進程的地址空間,這些錯誤很可能發生再當Oracle進程占用大概1.6G~1.7G(可以通過任務管理器或TopShow查看)時。
2.4.4.1. 會話內存大小設置
我們前面說了,一個進程的全部內存大小被限制在2G以內。因此,對於一個有許多用戶同時訪問的系統,要考慮這些會話總內存小於2G – SGA的大小。
下列參數會影響每個會話的內存大小(這些參數前面都有介紹):
o bitmap_merge_area_size
o create_bitmap_area_size
o hash_area_size
o open_cursors
o sort_area_size (sort_area_retained_size)
在沒有設置PGA_AGGREGATE_TARGE(這個參數能盡量但不一定使所有會話PGA之和在指定范圍內)參數時,需要調整這些參數,以使所有會話占用內存與SGA之和小於2G。過多的使用PL/SQL結構體(如PL/SQL TABLE、ARRAY)也會導致會話內存增大。
2.4.4.2. ORASTACK修改線程堆棧大小
Oracle提供了ORASTACK工具讓用戶內修改Oracle執行程序創建會話、線程時的默認堆棧大小。當ORASTACK應用於一個可執行程序時,它會修改程序頭部的、定義使用創建線程API函數所指定默認堆棧大小的二進制區,以修改默認堆棧的大小。沒有必要區修改線程提交頁數的默認值,因為它們時從堆棧中請求到的。當用戶非常多時,通過減少每個創建在Oracle中會話堆棧大小,可以節省大量內存。比如,一個1000用戶的系統,將堆棧從1M降為500K后,能節省出1000 * 500K = 500M的地址空間。
在需要使用ORASTACK來降低現場堆棧大小時,你需要測試你的系統以保證新的堆棧大小能確保系統正常運行。如果堆棧大小被縮小到Oracle服務端所必須的堆棧大小以下,就會產生堆棧溢出錯誤,用戶進程就失敗(通常報ORA-3113錯誤),並且在alert log中不會有報錯而且頁不產生trace文件。Oracle一般不推薦將堆棧該到500K以下(盡管不少系統在300K時也能正常運行)。
ORASTACK必須修改所有能在oracle中創建線程的進程,使用語法如下:
orastack executable_name new_stack_size_in_bytes
下面的例子將堆棧改為500K:
orastack oracle.exe 500000
orastack tnslsnr.exe 500000
orastack svrmgrl.exe 500000
orastack sqlplus.exe 500000
在使用ORASTACK之前必須保證沒有任何oracle進程正在運行(可以通任務管理器或者TopShow)。
此外,如果有程序在本地連接(沒有通過SQL*NET)了Oracle,也要先停止(如在本地運行sqlplus連接了實例)。
2.4.4.3. 會話內存如何釋放、線程如何結束
當會話成功結束后,它會按用Win32 API函數VirtualFree來釋放它的內存,調用此函數時,需要指定MEM_DECOMMIT | MEM_RELEASE標識。當所有內存被釋放后,堆棧也被釋放,將Oracle進程中指向完成的會話的地址空間空閑出來。
如果一個用戶會話被異常結束,它將不會釋放它所分配的內存,這些內存頁會被繼續保留在Oracle進程的地址空間中,知道進程結束。會話的異常結束可能由以下原因導致的:
o Shutdown abort.
o Alter session kill session.
o orakill殺掉的會話.
o Oracle管理助手for Windows:kill session.
o 其他殺線程的工具(TopShow工具提供了殺線程的功能)
Oracle建議盡量少使用以上方式(命令),特別是shutdown abort(這種停止實例的方法所帶來的問題還不止這個)。當調用shutdown abort時,Oracle會調用Win32 API函數TerminateThread來中止每個用戶會話,這個命令將直接殺掉線程而不釋放它們的內存。如果系統已經接近2G地址空間的限制,Oracle實例再次啟動要分配內存時就會產生問題。唯一釋放Oracle全部內存的方法是停止和啟動Oracle服務(服務OracleService<SID>)。
如果windows NT下的系統需要能被許多用戶訪問,可以通過以下措施來優化內存的使用:
o 降低相關的PGA、UGA內存參數(如SORT_AREA_SIZE);
o 降低SGA的參數;
o 使數據庫作業隊列數(參數job_queue_processes控制)和並行查詢slave(參數parallel_max_servers控制)最小,因為它們也會導致Oracle進程創建線程;
o 使用ORASTACK降低會話、線程堆棧大小到500K;
o 考慮使用MTS模式;
o 考慮將Windows NT升級到Windows NT企業版;
o 考慮升級硬件以支持Intel ESMA(Extended. Server Memory Architecture,擴展服務內存架構)
3. 內存錯誤處理
Oracle中最常見的內存錯誤就是4030和4031錯誤。這兩個錯誤分別是在分配PGA和SGA時,沒有足夠內存分配導致的。經過我們以上對Oracle內存的了解以及對內存管理機制的淺析,可以總結在這兩種錯誤發生時,我們該如何分析和處理。
3.1. 分析、定位ORA-4030
4030錯誤是由於oracle進程在內存擴展時,無法從OS獲取到所需的內存而產生的報錯。在專有服務模式下,Oracle進程的內存包括堆棧、PGA、UGA(從PGA中分配)和有些進程信息;而MTS下,UGA是從SGA中分配,不包括在進程的內存范圍內。
3.1.1. 4030錯誤產生的原因
PGA的大小不是固定的,是可以擴展的。PGA通過系統調用擴展堆數據段時,操作系統分配新的虛擬內存給進程作為PGA擴展段。這些擴展段一般是幾個KB。只要需要,oracle會分配幾千個擴展段。然而,操作系統限制了一個進程的堆數據段的增長。在UNIX中,這個限制一般受到OS內核參數MAXDSIZ限制,這是限制單個進程的。還有一個堆所有進程的虛擬內存的總的大小的限制。這個限制和swap交換空間(虛擬內存)大小有關。如果在擴展PGA內存時達到這些限制,就會拋4030錯誤。
3.1.2. 4030錯誤分析
既然知道了4030錯誤產生的可能原因,我們在分析4030錯誤時,就可以從這幾個方面分別收集信息進行分析,並結合Oracle進程內存的使用情況來解決問題。
3.1.2.1. 操作系統是否由足夠的內存
在不同的操作系統下,我們可以使用相應的工具來收集系統的內存使用情況,以判斷內存是否足夠:
· OpenVMS systems
可以使用show memory查看物理內存和虛擬內存頁的使用情況
Physical Memory Usage (pages): Total Free In Use Modified Main Memory (256.00Mb) 32768 24849 7500 419
.....
Paging File Usage (blocks): Free Reservable Total
DISK$BOBBIEAXPSYS:[SYS0.SYSEXE]SWAPFILE.SYS 30720 30720 39936 DISK$BOBBIEAXPSYS:[SYS0.SYSEXE]PAGEFILE.SYS 226160 201088 249984 DISK$BOBBIE_USER3:[SYS0.PAGEFILE]PAGEFILE.SYS 462224 405296 499968
一般情況下,空閑pagefile的之和不能小於它的總數之和的一半。而且SWAPFILE必須是始終沒有被使用的,它的空閑頁必須和總頁數相同。
· Windows
Windows下可以通過任務管理器的性能頁來查看內存的使用情況,也可以使用TopShow來觀察系統進程的內存使用情況
· UNIX
不同廠商的UNIX下,各種工具的使用和統計結果可能有所不同。常用的對內存的查看工具主要有:
o TOP —— 查看物理內存和交換空間
o vmstat —— 查看物理內存和交換空間狀況,以及觀察是否有page out/page in
o swapon –s —— 查看交換空間情況
o swapinfo –mt —— 查看交換空間使用情況
下面是swapinfo的一個輸出:
> swapinfo -mt
Mb Mb Mb PCT START/ Mb
TYPE AVAIL USED FREE USED LIMIT RESERVE PRI NAME
dev 4096 0 4096 0% 0 - 1 /dev/vg00/lvol2
dev 8000 0 8000 0% 0 - 1 /dev/vg00/swap2
reserve - 12026 -12026
memory 20468 13387 7081 65%
total 32564 25413 7151 78% - 0 -
此外,在一些操作系統中,還可以通過Oracle自己提供的工具maxmem來檢查一個進程能夠分配的最大堆數據段的大小。
> maxmem
Memory starts at: 6917529027641212928 (6000000000020000)
Memory ends at: 6917529031936049152 (6000000100000000)
Memory available: 4294836224 (fffe0000)
3.1.2.2. 是否受到系統限制
在操作系統,往往會有對單個進程或者所有進程能夠分配的內存大小做了限制。當Oracle分配進程內存時,如果達到這些限制,也會導致4030錯誤。在不同操作系統中,可以用不同方式檢查系統是否有做限制。
· OpenVMS systems:
show process/id=<process id>/quota可以顯示一個進程的可用配額是多少。
· Windows
如前所述,在window 32位系統中,進程的可用內存限制為2G(可以通過其他方式突破此限制)。而windows下,oracle是以一個單獨進程方式運行的,它的內存包括了堆棧、SGA、PGA。我們可以通過任務管理器或TopShow來檢查Oracle進程是否達到此限制。
· UNIX
可以使用命令ulimit來查看unix下的限制:
> ulimit -a
time(seconds) unlimited
file(blocks) unlimited
data(kbytes) 1048576
stack(kbytes) 131072
memory(kbytes) unlimited
coredump(blocks) 4194303
3.1.2.3. 哪個Oracle進程請求了過多的內存
有些進程會做某些操作時會需要分配大量內存,如使用了PL/SQL TABLE或者做排序時。如果這樣的進程在系統中運行一段時間后,就可能導致4030錯誤的產生。我們可以用以下語句來查看各個進程的內存使用情況:
select sid,name,value from v$statname n,v$sesstat s where n.STATISTIC# = s.STATISTIC# and name like '%ga %' order by 3 asc;
同時,我們還可以從操作系統的角度來確認這些大量消耗內存的進程。
· OpenVMS systems:
show process/continious可以查看各個進程的物理和虛擬內存的使用情況。
· Windows
在windows中,由於Oracle是以一個單獨進程運行的,而由線程來服務於會話的。因此無法查看單個會話的內存占用情況。
· UNIX
UNIX中,可以通過ps –lef|grep ora來查看oracle進程占用的內存情況。
3.1.2.4. 收集進程正在進行的操作
在解決4030問題時,有一點很重要,拋出4030錯誤的進程並不一定是導致內存不足的進程。只不過在它請求分配內存時,內存已經不足了。很有可能在此之前就已經有大量消耗內存的進程導致內存不足。你需要找出內存消耗不斷增長的進程,觀察它鎖進行的操作。這條語句可以查出會話進程正在執行的語句:
select sql_text
from v$sqlarea a, v$session s
where a.address = s.sql_address and s.sid = <SID>;
另外,可以做一個heapdump,將結果發給Oracle進行分析,
SQL> oradebug unlimit SQL> oradebug setorapid <PID> (通過v$process查到的pid, 用setospid來設置OS中的PID【或者v$process中的spid】) SQL> oradebug dump heapdump 7 (1-PGA; 2-Shared Pool; 4-UGA; 8-CGA; 16-top CGA; 32-large pool)
SQL> alter session set events '4030 trace name heapdump level 25';
3.1.3. 解決4030錯誤的建議
如果問題是由於swap空間不足造成的,並且由中度或者嚴重的page in/page out(可以用vmstat查看),你就需要嘗試降低系統整體的虛擬內存的使用(如調整SGA大小),或者降低單個進程內存的使用(如調整sort_area_size),或者減少進程數量(如限制processes參數,使用MTS)。而如果page in/page out很少或者根本沒有,就可以考慮增大swap空間。某些系統中,可以考慮使用偽交換區(如hp-ux中,可以考慮設置swapmen_on)。
如果問題和PLSQL操作有關,可以,1、檢查PLSQL中的TABLE,看看其中的數據是否全都必要,是否可以減少數據放入TABLE中;2、優化相關語句(比如通過調整優化器策略,使查詢計划走sort比較少的訪問路徑),減少sort操作,或者減少sort_area_size(代價就是一部分sort操作會放在磁盤上進行,降低性能)。
9i以后可以考慮設置PGA內存自動管理。即設置PGA_AGGREGATE_TARGET在一定數值范圍內,WORKAREA_SIZE_POLICY設置為AUTO。但是注意,9i在OpenVMS系統上、或者在MTS模式下不支持PGA內存自動關聯。
如果是因為進程數過多導致的內存大量消耗,首先可以考慮調整客戶端,減少不必要的會話連接,或者采用連接池等方式,以保持系統有穩定的連接數。如果會話非常多,且無法降低的話,可以考慮采用MTS,以減少Oracle進程數。
檢查SGA中的內存區是否分配過多(如shared pool、large pool、java pool)等,嘗試減少SGA的內存大小。
在windows下,可以嘗試使用ORASTACK來減少線程的堆棧大小,以釋放更多的內存。
考慮增加物理內存。
3.2. 分析、定位ORA-4031
4031錯誤是Oracle在沒有足夠的連續空閑空間分配給Shared Pool或者Large Pool時拋出的錯誤。
3.2.1. 4031錯誤產生的原因
前面我們描述Shared Pool的空閑空間的請求、分配過程。在受到空閑空間請求時,內存管理模塊會先查找空閑列表,看是否有合適的空閑chunk,如果沒有,則嘗試從LRU鏈表中尋找可釋放的chunk,最終還未找到合適的空閑chunk就會拋出4031錯誤。
在討論4031問題之前,可以先到第一章中找到與shared pool(shared_pool_size、shared_pool_reserved_size、shared_pool_reserved_min_alloc)和large pool(large_pool_size)的參數描述,再了解一下這些參數的作用。這對於理解和分析4031錯誤會很有幫助。此外,還需要再回顧以下10g以后的SGA內存自動關聯部分(相關參數是SGA_TARGET),因為使用這一特性,能大大減少4031錯誤產生的幾率。
3.2.2. 4031錯誤分析
通常,大多數的4031錯誤都是和shared pool相關的。因此,4031錯誤的分析,主要是對shared pool的分析。
3.2.2.1. 對shared pool的分析
當4031錯誤提示是shared pool無足夠連續內存可分配時,有可能是由於shared pool不足或者shared pool中嚴重的碎片導致的。
· Shared pool不足分析
視圖V$SHARED_POOL_RESERVED中可以查詢到產生4031的一些統計數據、以及shared pool中保留區(前面說了,保留區是用來緩存超過一定大小的對象的shared pool區)的統計信息。
如果字段REQUEST_FAILURES >= 0並且字段LAST_FAILURE_SIZE < _SHARED_POOL_RESERVED_MIN_ALLOC,可以考慮減小_SHARED_POOL_RESERVED_MIN_ALLOC,以使更多的對象能放到保留區中區(當然,你還需要觀察字段MAX_USED_SPACE以確保保留區足夠大)。如果還沒有效果,就需要考慮增加shared_pool_size了。
· 碎片問題分析
Library cache和shared pool保留區的碎片也會導致4031錯誤的產生。
還是觀察上面的視圖,如果字段REQUEST_FAILURES > 0並且字段LAST_FAILURE_SIZE > _SHARED_POOL_RESERVED_MIN_ALLOC,就可以考慮增加_SHARED_POOL_RESERVED_MIN_ALLOC大小以減少放入保留區的對象,或者增加SHARED_POOL_RESERVED_SIZE和shared_pool_size(因為保留區是從shared pool中分配的)的大小。
此外,要注意有一個bug導致REQUEST_FAILURES在9.2.0.7之前所有版本和10.1.0.4之前的10g版本中統計的數據是錯誤的,這時可以觀察最后一次4031報錯信息中提示的無法分配的內存大小。
3.2.2.2. 對large pool的分析
Large pool是在MTS、或並行查詢、或備份恢復中存放某些大對象的。可以通過視圖v$sgastat來觀察large pool的使用情況和空閑情況。
而在MTS模式中,sort_area_retained_size是從large pool中分配的。因此也要檢查和調整這個參數的大小,並找出產生大量sort的會話,調整語句,減少其中的sort操作。
MTS中,UGA也是從large pool中分配的,因此還需要觀察UGA的使用情況。不過要注意一點的是,如果UGA無法從large pool獲取到足夠內存,會嘗試從shared pool中去分配。
3.2.3. 解決4031錯誤
根據4031產生的不同原因,采取相應辦法解決問題。
3.2.3.1. bug導致的錯誤
有很多4031錯誤都是由於oracle bug引起的。因此,發生4031錯誤后,先檢查是否你的系統的4031錯誤是否是由bug引起的。下面是已經發現的會引起4031錯誤的bug。相關信息可以根據bug號或note號到metalink上查找。
BUG |
說明 |
修正版本 |
Bug 1397603 |
ORA-4031 由於緩存句柄導致的SGA永久內存泄漏 |
8172, 901 |
Bug 1640583 |
ORA-4031 due to leak / 由於查詢計划中AND-EQUAL訪問路徑導致緩沖內存鏈爭用,從而發生內存泄漏。 |
8171, 901 |
Bug:1318267 |
如果設置了TIMED_STATISTICS可能導致INSERT AS SELECT無法被共享。 |
8171, 8200 |
Bug:1193003 |
Oracle 8.1中,某些游標不共享。 |
8162, 8170, 901 |
Bug 2104071 |
ORA-4031 太多PIN導致shared pool消耗過大。 |
8174, 9013, 9201 |
Note 263791.1 |
許多與4031相關的錯誤在9205補丁集中修正。 |
9205 |
3.2.3.2. Shared pool太小
大多數4031錯誤都是由shared pool不足導致的。可以從以下幾個方面來考慮是否調整shared pool大小:
· Library cache命中率
通過以下語句可以查出系統的library cache命中率:
SELECT SUM(PINS) "EXECUTIONS", SUM(RELOADS) "CACHE MISSES WHILE EXECUTING", 1 - SUM(RELOADS)/SUM(PINS) FROM V$LIBRARYCACHE;
如果命中率小於99%,就可以考慮增加shared pool以提高library cache的命中率。
· 計算shared pool的大小
以下語句可以查看shared pool的使用情況
select sum(bytes) from v$sgastat
where pool='shared pool'
and name != 'free memory';
專用服務模式下,以下語句查看cache在內存中的對象的大小,
select sum(sharable_mem) from v$db_object_cache;
專用服務模式下,以下語句查看SQL占用的內存大小,
select sum(sharable_mem) from v$sqlarea;
Oracle需要為保存每個打開的游標分配大概250字節的內存,以下語句可以計算這部分內存的占用情況,
select sum(250 * users_opening) from v$sqlarea;
此外,在我們文章的前面部分有多處提到了如何分析shared pool是否過大或過小,這里就不在贅述。
3.2.3.3. Shared pool碎片
每當需要執行一個SQL或者PLSQL語句時,都需要從library cache中分配一塊連續的空閑空間來解析語句。Oracle首先掃描shared pool查找空閑內存,如果沒有發現大小正好合適的空閑chunk,就查找更大的chunk,如果找到比請求的大小更大的空閑chunk,則將它分裂,多余部分繼續放到空閑列表中。這樣就產生了碎片問題。系統經過長時間運行后,就會產生大量小的內存碎片。當請求分配一個較大的內存塊時,盡管shared pool總空閑空間還很大,但是沒有一個單獨的連續空閑塊能滿足需要。這時,就可能產生4031錯誤。
如果檢查發現shared_pool_size足夠大,那4031錯誤一般就是由於碎片太多引起的。
如果4031是由碎片問題導致的,就需要弄清楚導致碎片的原因,采取措施,減少碎片的產生。以下是可能產生碎片的一些潛在因素:
o 沒有使用共享SQL;
o 過多的沒有必要的解析調用(軟解析);
o 沒有使用綁定變量。
以下表/視圖、語句可以查詢shared pool中沒有共享的SQL
· 通過V$SQLAREA視圖
前面我們介紹過這個視圖,它可以查看到每一個SQL語句的相關信息。以下語句可以查出沒有共享的語句,
SELECT substr(sql_text,1,40) "SQL", count(*) , sum(executions) "TotExecs" FROM v$sqlarea WHERE executions < 5 –-語句執行次數 GROUP BY substr(sql_text,1,40) HAVING count(*) > 30 –-所有未共享的語句的總的執行次數 ORDER BY 2;
· X$KSMLRU表
這張表保存了對shared pool的分配所導致的shared pool中的對象被清出的記錄。可以通過它來查找是什么導致了大的shared pool分配請求。
如果有許多對象定期會被從shared pool中被清出,會導致響應時間太長和library cache latch爭用問題。
不過要注意一點,每當查詢過表X$KSMLRU后,它的內容就會被刪除。因此,最好將查出的數據保存在一個臨時的表中。以下語句查詢X$KSMLRU中的內容,
SELECT * FROM X$KSMLRU WHERE ksmlrsiz > 0;
· X$KSMSP表
從這張表中可以查到當前分配了多少空閑空間,這對於分析碎片問題很有幫助。一些語句可以查詢shared pool的空閑列表中chunk的統計信息,
select '0 (<140)' BUCKET, KSMCHCLS, KSMCHIDX, 10*trunc(KSMCHSIZ/10) "From", count(*) "Count" , max(KSMCHSIZ) "Biggest", trunc(avg(KSMCHSIZ)) "AvgSize", trunc(sum(KSMCHSIZ)) "Total" from x$ksmsp where KSMCHSIZ<140 and KSMCHCLS='free' group by KSMCHCLS, KSMCHIDX, 10*trunc(KSMCHSIZ/10) UNION ALL select '1 (140-267)' BUCKET, KSMCHCLS, KSMCHIDX,20*trunc(KSMCHSIZ/20) , count(*) , max(KSMCHSIZ) , trunc(avg(KSMCHSIZ)) "AvgSize", trunc(sum(KSMCHSIZ)) "Total" from x$ksmsp where KSMCHSIZ between 140 and 267 and KSMCHCLS='free' group by KSMCHCLS, KSMCHIDX, 20*trunc(KSMCHSIZ/20) UNION ALL select '2 (268-523)' BUCKET, KSMCHCLS, KSMCHIDX, 50*trunc(KSMCHSIZ/50) , count(*) , max(KSMCHSIZ) , trunc(avg(KSMCHSIZ)) "AvgSize", trunc(sum(KSMCHSIZ)) "Total" from x$ksmsp where KSMCHSIZ between 268 and 523 and KSMCHCLS='free' group by KSMCHCLS, KSMCHIDX, 50*trunc(KSMCHSIZ/50) UNION ALL select '3-5 (524-4107)' BUCKET, KSMCHCLS, KSMCHIDX, 500*trunc(KSMCHSIZ/500) , count(*) , max(KSMCHSIZ) , trunc(avg(KSMCHSIZ)) "AvgSize", trunc(sum(KSMCHSIZ)) "Total" from x$ksmsp where KSMCHSIZ between 524 and 4107 and KSMCHCLS='free' group by KSMCHCLS, KSMCHIDX, 500*trunc(KSMCHSIZ/500) UNION ALL select '6+ (4108+)' BUCKET, KSMCHCLS, KSMCHIDX, 1000*trunc(KSMCHSIZ/1000) , count(*) , max(KSMCHSIZ) , trunc(avg(KSMCHSIZ)) "AvgSize", trunc(sum(KSMCHSIZ)) "Total" from x$ksmsp where KSMCHSIZ >= 4108 and KSMCHCLS='free' group by KSMCHCLS, KSMCHIDX, 1000*trunc(KSMCHSIZ/1000);
如果使用ORADEBUG將shared pool信息dump出來,就會發現這個查詢結果和trace文件中空閑列表信息一直。
如果以上查詢結果顯示大多數空閑chunk都在bucket比較小的空閑列表中,則說明系統存在碎片問題。
3.2.3.4. 編譯java代碼導致的錯誤
當編譯java(用loadjava或deployjb)代碼時產生了4031錯誤,錯誤信息一般如下:
A SQL exception occurred while compiling: :
ORA-04031: unable to allocate bytes of shared memory ("shared pool","unknown object","joxlod: init h", "JOX: ioc_allocate_pal")
這里提示時shared pool不足,其實是錯誤,實際應該是java pool不足導致的。解決方法將JAVA_POOL_SIZE加大,然后重啟實例。
3.2.3.5. Large pool導致的錯誤
Large pool是在MTS、或並行查詢、或備份恢復中存放某些大對象的。但和shared pool中的保留區(用於存放shared pool的大對象)不同,large pool是沒有LRU鏈表的,而后者使用的是shared pool的LRU鏈表。
在large pool中的對象永遠不會被清出的,因此不存在碎片問題。當由於large pool不足導致4031錯誤時,可以先通過v$sgastat查看large pool的使用情況,
SELECT pool,name,bytes FROM v$sgastat where pool = 'large pool';
或者做一個dump,看看large pool中空閑chunk的大小情況。
進入large pool的大小條件是由參數LARGE_POOL_MIN_ALLOC決定的,根據以上信息,可以適當調整LARGE_POOL_MIN_ALLOC的大小。
Large pool的大小是由LARGE_POOL_SIZE控制的,因此當large pool空間不足時,可以調整這個參數。
3.2.4. SGA內存自動管理
10g以后,Oracle提供了一個非常有用的特性,即SGA內存自動管理。通過設置SGA_TARGET可以指定總的SGA大小,而無需固定每個區的大小。這就是說,當分配shared pool或large pool時,只要SGA區足夠大,就能獲取到足夠內存,因而可以大大減少4031錯誤發生的幾率。
3.2.5. FLUSH SHARED POOL
使用綁定變量是解決shared pool碎片的最好方法。此外,9i以后,可以設置CURSOR_SHARING為FORCE,強行將沒有使用綁定變量的語句使用綁定變量,從而共享SQL游標。當采用以上措施后,碎片問題並不會馬上消失,並可能還會長時間存在。這時,可以考慮flush shared pool,將內存碎片結合起來。但是,在做flush之前,要考慮以下問題。
· Flush會將所有沒有使用的游標從library cache中清除出去。因此,這些語句在被再次調用時會被重新硬解析,從而提高CPU的占用率和latch爭用;
· 如果應用沒有使用綁定變量,即使flush了shared pool以后,經過一段時間運行,仍然會出現大量碎片。因此,這種情況下,flush是沒有必要的,需要先考慮優化應用系統;
· 如果shared pool非常大,flush操作可能會導致系統被hung住。
因此,如果要flush shared pool,需要在系統不忙的時候去做。Flush的語法為,
alter system flush shared_pool;
3.2.6. TRACE 4031錯誤
如果問題比較復雜(比如由於內存泄漏導致),或者你不幸遇上了oracle的bug,這時就需要考慮設置4031事件來trace並dump出相關內存信息。
以下語句在整個系統設置4031事件,
SQL> alter system set events '4031 trace name errorstack level 3'; SQL> alter system set events '4031 trace name HEAPDUMP level 3';
這個事件也可以在會話中設置,只要將以上語句中的“system”改為“session”就行了。
然后將dump出來的trace文件發給oracle吧。
不過注意一點,9205以后就無需設置這個事件了,因為一旦發生4031錯誤時,oracle會自動dump出trace文件。
4. Dump內存解析
下面以shared pool為例,解釋一下dump出來的內存結構。
SQL> conn sys/sys as sysdba
Connected.
SQL> oradebug setmypid
Statement processed.
SQL> oradebug dump heapdump 2
Statement processed.
SQL>
以下時trace文件的內容,我們分別解釋各個部分:
Dump file c:"oracle"product"10.2.0"admin"fuyuncat"udump"fuyuncat_ora_4032.trc
Tue Jul 11 16:03:26 2006
ORACLE V10.2.0.1.0 - Production vsnsta=0
vsnsql=14 vsnxtr=3
Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - Production
With the Partitioning, OLAP and Data Mining options
Windows XP Version V5.1 Service Pack 2
CPU : 2 - type 586
Process Affinity : 0x00000000
Memory (Avail/Total): Ph:885M/2039M, Ph+PgF:2702M/3890M, VA:1590M/2047M
Instance name: fuyuncat
Redo thread mounted by this instance: 1
Oracle process number: 18
Windows thread id: 4032, image: ORACLE.EXE (SHAD)
*** SERVICE NAME:(SYS$USERS) 2006-07-11 16:03:26.322
*** SESSION ID:(159.7) 2006-07-11 16:03:26.322
這部分是關於trace文件的基本信息,oracle版本、資源情況、用戶和會話等。
KGH Latch Directory Information
ldir state: 2 next slot: 75
Slot [ 1] Latch: 03C3D280 Index: 1 Flags: 3 State: 2 next: 00000000
Slot [ 2] Latch: 1EC9D4B0 Index: 1 Flags: 3 State: 2 next: 00000000
Slot [ 3] Latch: 1EC9D540 Index: 1 Flags: 3 State: 2 next: 00000000
Slot [ 4] Latch: 03C3E100 Index: 1 Flags: 3 State: 2 next: 00000001
Slot [ 5] Latch: 1ED65CE4 Index: 1 Flags: 3 State: 2 next: 00000000
Slot [ 6] Latch: 1ED65F14 Index: 1 Flags: 3 State: 2 next: 00000000
... ...
這部分記錄的是shared pool中的latch信息。每個latch的具體信息可以通過視圖V$LATCH、V$LATCH_PARENT、V$LATCH_CHILDREN或者表x$ksllt查出
******************************************************
HEAP DUMP heap name="sga heap" desc=03C38510
extent sz=0x32c8 alt=108 het=32767 rec=9 flg=-126 opc=0
parent=00000000 owner=00000000 nex=00000000 xsz=0x10
******************************************************
這是堆dump信息的頭部,heap name說明了內存所述的堆,shared pool是屬於SGA區的,因此,這里是"sga heap";
extent sz記錄的是所有擴展段的大小。
HEAP DUMP heap name="sga heap(1,0)" desc=04EC131C
extent sz=0xfc4 alt=108 het=32767 rec=9 flg=-126 opc=0
parent=00000000 owner=00000000 nex=00000000 xsz=0x400000
EXTENT 0 addr=1CC00000
Chunk 1cc00038 sz= 24 R-freeable "reserved stoppe"
Chunk 1cc00050 sz= 212888 R-free " "
Chunk 1cc33fe8 sz= 24 R-freeable "reserved stoppe"
Chunk 1cc34000 sz= 3977544 perm "perm " alo=3977544
Chunk 1cfff148 sz= 3768 free " "
EXTENT 1 addr=1D000000
Chunk 1d000038 sz= 24 R-freeable "reserved stoppe"
Chunk 1d000050 sz= 212888 R-free " "
Chunk 1d033fe8 sz= 24 R-freeable "reserved stoppe"
Chunk 1d034000 sz= 2097168 perm "perm " alo=2097168
這部分信息是trace文件中的主要部分,它詳細記錄了shared pool中各個chunk的信息。
首先看它的頭部信息,注意到這里heap name是"sga heap(1,0)"。這是什么意思呢?我們前面提到,oracle 10g會將shared pool分為幾個區來管理,這里就是其中的一個區。共有4個區。通過表X$KGHLU可以看到對應的LRU鏈表。
EXTENT 0 addr=1CC00000
這一行說明下面的chunk都屬於這個擴展段(extent),0是它的編號,addr是它的起始地址。
Chunk 1cc00038 sz= 24 R-freeable "reserved stoppe"
這是一個chunk的信息,sz是這個chunk的大小(24字節)。R-freeable是這個chunk的狀態,"reserved stoppe"是這個chunk的用途。Chunk有4種可能狀態,以下是這四種狀態的含義:
free:即空閑chunk,可以隨時分配給適合大小的請求;
freeable:這種狀態的chunk表示它當前正在被使用,但是這種使用是短期的,比如在一次調用中或者一個會話中,會話或者調用解釋就可以被釋放出來。這種狀態的chunk是不放在LRU鏈表中的,一旦使用結束,自動成為free狀態,放到空閑列表中;
recreatable:這種狀態的chunk正在被使用,但是它所包含的對象是可以被暫時移走、重建,比如解析過的語句。它是被LRU鏈表管理的。
permanent:顧名思義,這種狀態的chunk所包含的對象是永遠不會被釋放的。即使flush shared pool也不會釋放。
我們注意到,這里還有一些狀態是有前綴“R-”的。帶有這種前綴的chunk說明是shared pool中的保留區的chunk。
Total heap size = 41942480
最后是這一shared pool區的總的大小。
FREE LISTS:
Bucket 0 size=16
Bucket 1 size=20
Chunk 166ed050 sz= 20 free " "
Chunk 167de068 sz= 20 free " "
Chunk 164b9c10 sz= 20 free " "
Chunk 1f2776f8 sz= 20 free " "
接下來便是這個shared pool區的空閑列表。Bucket是一個空閑列表的范圍,例如Bucket 1,它的最小值是上一個Bucket的最大值,即16,最大值為20。Bucket下面是空閑列表中chunk,后面的信息和前面解釋chunk的信息一樣,8位的16進制數字是它的地址;sz是chunk的大小;free是chunk的狀態,因為是空閑列表中的chunk,這里只有一個狀態;最后是chunk的用途,因為都是free,所以肯定為空。
Total free space = 1787936
最后是這塊shared pool區中空閑chunk的總的大小。
RESERVED FREE LISTS:
Reserved bucket 0 size=16
Reserved bucket 1 size=4400
Reserved bucket 2 size=8204
Reserved bucket 3 size=8460
Reserved bucket 4 size=8464
Reserved bucket 5 size=8468
Reserved bucket 6 size=8472
Reserved bucket 7 size=9296
Reserved bucket 8 size=9300
Reserved bucket 9 size=12320
Reserved bucket 10 size=12324
Reserved bucket 11 size=16396
Reserved bucket 12 size=32780
Reserved bucket 13 size=65548
Chunk 1b800050 sz= 212888 R-free " "
Chunk 16c00050 sz= 212888 R-free " "
Chunk 1ac00050 sz= 212888 R-free " "
Total reserved free space = 638664
Shared pool的普通區的空閑列表下面就是關於這塊shared pool區中保留區的空閑列表的描述,其中除了在名字上bucket前面都有一個Reserved標識,和狀態前面有“R-”前綴外,含義和普通空閑列表相同。
UNPINNED RECREATABLE CHUNKS (lru first):
Chunk 1aee99c0 sz= 4096 recreate "sql area " latch=1D8BDD48
Chunk 1ae4aeec sz= 4096 recreate "sql area " latch=1D8BDDB0
... ...
SEPARATOR
Chunk 166e8384 sz= 540 recreate "KQR PO " latch=1DD7F138
Chunk 1f333a5c sz= 284 recreate "KQR PO " latch=1DC7DFC8
Chunk 166e9340 sz= 540 recreate "KQR PO " latch=1DE00A70
Chunk 1f0fe058 sz= 284 recreate "KQR PO " latch=1DC7DFC8
Chunk 1f2116b4 sz= 540 recreate "KQR PO " latch=1DE81910
Chunk 1f21127c sz= 540 recreate "KQR PO " latch=1DE81910
... ...
Unpinned space = 1611488 rcr=645 trn=864
空閑列表后面就是LRU鏈表了。LRU鏈表不是按照大小分的,因而沒有Bucket。它的chunk是按照最近最少使用的順序排列。其中chunk的信息和前面解釋的一樣。但是要注意一點,因為LRU鏈表中的chunk都是使用的,因為每個chunk根據用途不同,都會有一個latch來保護,Chunk信息最后便是latch的地址。
注意,我們前面提到,shared pool中是有兩種LRU鏈表的,一種循環LRU鏈表;另外一種是暫時LRU鏈表。在這里LRU信息中前面部分是循環LRU鏈表,SEPARATOR后面部分是暫時LRU鏈表信息。
最后是LRU鏈表中chunk的總的大小,rcr是循環LRU鏈表中的chunk數,trn是暫時LRU鏈表中的chunk數。
此外,有一點提示,如果是有多個shared pool區,第一個區是不含LRU鏈表信息的。
PERMANENT CHUNKS:
Chunk 1d234010 sz= 1884144 perm "perm " alo=1728440
Chunk 1cc34000 sz= 3977544 perm "perm " alo=3977544
Chunk 1d034000 sz= 2097168 perm "perm " alo=2097168
Chunk 1d434000 sz= 3117112 perm "perm " alo=3117112
... ...
Chunk 1f434000 sz= 3917704 perm "perm " alo=3917704
Permanent space = 38937696
最后是永久chunk的信息。Chunk部分解釋和前面一致。alo表示已經分配的大小。
如果有多個shared pool區,永久chunk信息則只存在於第一個shared pool區。
SQL> conn sys/sys as sysdba
Connected.
SQL> oradebug setmypid
Statement processed.
SQL> oradebug dump heapdump 2
Statement processed.
SQL>
Dump file c:"oracle"product"10.2.0"admin"fuyuncat"udump"fuyuncat_ora_4032.trc
Tue Jul 11 16:03:26 2006
ORACLE V10.2.0.1.0 - Production vsnsta=0
vsnsql=14 vsnxtr=3
Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - Production
With the Partitioning, OLAP and Data Mining options
Windows XP Version V5.1 Service Pack 2
CPU : 2 - type 586
Process Affinity : 0x00000000
Memory (Avail/Total): Ph:885M/2039M, Ph+PgF:2702M/3890M, VA:1590M/2047M
Instance name: fuyuncat
Redo thread mounted by this instance: 1
Oracle process number: 18
Windows thread id: 4032, image: ORACLE.EXE (SHAD)
*** SERVICE NAME:(SYS$USERS) 2006-07-11 16:03:26.322
*** SESSION ID:(159.7) 2006-07-11 16:03:26.322
KGH Latch Directory Information
ldir state: 2 next slot: 75
Slot [ 1] Latch: 03C3D280 Index: 1 Flags: 3 State: 2 next: 00000000
Slot [ 2] Latch: 1EC9D4B0 Index: 1 Flags: 3 State: 2 next: 00000000
Slot [ 3] Latch: 1EC9D540 Index: 1 Flags: 3 State: 2 next: 00000000
Slot [ 4] Latch: 03C3E100 Index: 1 Flags: 3 State: 2 next: 00000001
Slot [ 5] Latch: 1ED65CE4 Index: 1 Flags: 3 State: 2 next: 00000000
Slot [ 6] Latch: 1ED65F14 Index: 1 Flags: 3 State: 2 next: 00000000
... ...
******************************************************
HEAP DUMP heap name="sga heap" desc=03C38510
extent sz=0x32c8 alt=108 het=32767 rec=9 flg=-126 opc=0
parent=00000000 owner=00000000 nex=00000000 xsz=0x10
******************************************************
HEAP DUMP heap name="sga heap(1,0)" desc=04EC131C
extent sz=0xfc4 alt=108 het=32767 rec=9 flg=-126 opc=0
parent=00000000 owner=00000000 nex=00000000 xsz=0x400000
EXTENT 0 addr=1CC00000
Chunk 1cc00038 sz= 24 R-freeable "reserved stoppe"
Chunk 1cc00050 sz= 212888 R-free " "
Chunk 1cc33fe8 sz= 24 R-freeable "reserved stoppe"
Chunk 1cc34000 sz= 3977544 perm "perm " alo=3977544
Chunk 1cfff148 sz= 3768 free " "
EXTENT 1 addr=1D000000
Chunk 1d000038 sz= 24 R-freeable "reserved stoppe"
Chunk 1d000050 sz= 212888 R-free " "
Chunk 1d033fe8 sz= 24 R-freeable "reserved stoppe"
Chunk 1d034000 sz= 2097168 perm "perm " alo=2097168
EXTENT 0 addr=1CC00000
Chunk 1cc00038 sz= 24 R-freeable "reserved stoppe"
Total heap size = 41942480
FREE LISTS:
Bucket 0 size=16
Bucket 1 size=20
Chunk 166ed050 sz= 20 free " "
Chunk 167de068 sz= 20 free " "
Chunk 164b9c10 sz= 20 free " "
Chunk 1f2776f8 sz= 20 free " "
Total free space = 1787936
RESERVED FREE LISTS:
Reserved bucket 0 size=16
Reserved bucket 1 size=4400
Reserved bucket 2 size=8204
Reserved bucket 3 size=8460
Reserved bucket 4 size=8464
Reserved bucket 5 size=8468
Reserved bucket 6 size=8472
Reserved bucket 7 size=9296
Reserved bucket 8 size=9300
Reserved bucket 9 size=12320
Reserved bucket 10 size=12324
Reserved bucket 11 size=16396
Reserved bucket 12 size=32780
Reserved bucket 13 size=65548
Chunk 1b800050 sz= 212888 R-free " "
Chunk 16c00050 sz= 212888 R-free " "
Chunk 1ac00050 sz= 212888 R-free " "
Total reserved free space = 638664
UNPINNED RECREATABLE CHUNKS (lru first):
Chunk 1aee99c0 sz= 4096 recreate "sql area " latch=1D8BDD48
Chunk 1ae4aeec sz= 4096 recreate "sql area " latch=1D8BDDB0
... ...
SEPARATOR
Chunk 166e8384 sz= 540 recreate "KQR PO " latch=1DD7F138
Chunk 1f333a5c sz= 284 recreate "KQR PO " latch=1DC7DFC8
Chunk 166e9340 sz= 540 recreate "KQR PO " latch=1DE00A70
Chunk 1f0fe058 sz= 284 recreate "KQR PO " latch=1DC7DFC8
Chunk 1f2116b4 sz= 540 recreate "KQR PO " latch=1DE81910
Chunk 1f21127c sz= 540 recreate "KQR PO " latch=1DE81910
... ...
Unpinned space = 1611488 rcr=645 trn=864
PERMANENT CHUNKS:
Chunk 1d234010 sz= 1884144 perm "perm " alo=1728440
Chunk 1cc34000 sz= 3977544 perm "perm " alo=3977544
Chunk 1d034000 sz= 2097168 perm "perm " alo=2097168
Chunk 1d434000 sz= 3117112 perm "perm " alo=3117112
... ...
Chunk 1f434000 sz= 3917704 perm "perm " alo=3917704
Permanent space = 38937696