深入剖析 ORA-04031 的前世今生

每一個接觸過 Oracle 數據庫的人想必聽到 Ora-04031 都會有一種捶胸頓足的感覺,至少在兩年前的我是這樣子的。都說 Ora-04031 和 Ora-01555 等是 Oracle 的經典錯誤,之所以成爲經典,可能就是因爲它們會經常出現,卻又不是那麼好解決的緣故吧。今天我就跟大家分享一個我工作當中的4031案例,解讀一下4031的前世今生,希望通過今天晚上的交流,當我們再次遇見4031錯誤時不再像之前那麼恐懼。

 

本次跟大家分享的這個案例是去年我在某電力公司駐場的時候,某天下午剛回到住處就收到手機報警短信,說 pmdb1 庫有4031報錯(因爲該客戶使用了 zabbix 監控,所以手機可以收到數據庫的所有警告和錯誤),於此同時開發人員打來電話說應用日誌裏面有4031錯誤,我立馬趕回辦公室,處理這個棘手的4031錯誤。

 

在繼續下面的內容之前我先介紹一下本次案例中這套數據庫的情況。這是一個運行在 AIX 上的一套雙節點 RAC,數據庫的版本爲11.2.0.3.0,庫的數據數據量不是很大,在80G左右,但是是一個非常重要的業務系統的中間庫。

 

以我這幾年的工作經驗來說,當我們遇到 Oracle 報錯時,通常都會從數據庫的 alert 日誌看起,結合與錯誤相關的 trace 文件以及發生錯誤時的一些系統狀態、數據庫狀態等獲取相關信息,綜合上面的信息來判斷該報錯。本次對4031錯誤的分析也是一樣。哪麼我首先來查看了數據庫的 alert 日誌,果不其然,日誌裏面有大量的4031錯誤,記錄如下所示:



 

經歷過 Ora-04031 錯誤的 DBA 都知道,如果數據庫遇到了4031錯誤,可能會導致很多會話都會拋出4031錯誤,嚴重的會導致整個數據庫不能運行任何 SQL 語句,即使是一條非常簡單的語句,更有甚者還會導致數據宕機。

 

而一提到數據庫宕機相信可能會讓很多 DBA 都膽戰心驚,這裏我先用一句話簡單的總結一下4031錯誤的原因,讓大家對4031先有一個初步的認識。當客戶端的 SQL 操作被傳送到oracle端,oracleserver 進程在處理客戶端 SQL 請求時,首先需要向 SGA(注意,我這裏說的是 SGA 而不是 share pool)申請內存,當 SGA 不能滿足 Oracle server 進程請求內存的需求時就會發生4031錯誤。

 

我們知道 SGA 中有很多 pool,例如 share pool,java pool,large pool,stream pool 等,所以前面我所強調的“向 SGA 申請內存”,意思就是說4031錯誤會在上面的任何一個 pool 中發生。

 

回到上面的 alert 日誌中關於4031報錯的信息,這裏它告訴了我們兩個非常重要的信息,一個是發生錯誤的 trace 文件;本次案例中4031錯誤的 trace 文件爲 /u01/oracle/app/oracle/admin/pmdb1 /bdump/pmdb1_ora_42338038.trc,另外一個是發生錯誤的信息;本案例中發生4031錯誤的信息爲:ORA-04031: unable to allocate3896 bytes of shared memory ("shared pool","select /*+leading(CM_BUSI_M...","sga heap(2,0)","kglsim objectbatch")

 

4031錯誤的trace文件中記錄了更加詳細的關於4031錯誤的信息,而且裏面也記錄了發生4031錯誤時內存的狀態信息。OK,哪我們就打開 /u01/oracle/app/oracle/admin/pmdb1/bdump/pmdb1_ora_42338038.trc 來看看。第一部分就是該數據庫的環境信息:




通過 trace 文件也可以判斷這是在運行在 AIX 上的一個 RAC 環境,數據庫的版本爲11.2.0.3.0,數據庫的實例名爲 pmdb1,發生錯誤的進程號爲 42338038。

 

trace 文件的第二部分就是 4031 Diagnostic Information:



 

這裏記錄了關於4031錯誤更加詳細的信息,但是關鍵信息和 alert 日誌中記錄的信息是一樣的。ORA-04031: unable toallocate 3896 bytes of shared memory ("shared pool", "select /*+leading(CM_BUSI_M...", "sga heap(2,0)", "kglsim objectbatch")

 

好了,既然 ORA-04031:unable to allocate 3896 bytes of shared memory ("shared pool","select/*+ leading(CM_BUSI_M...","sga heap(2,0)","kglsim objectbatch") 是4031錯誤非常關鍵的信息,那我們就從開始看懂這個關鍵的報錯信息開始。

 

“ORA-04031: unable to allocate 3896bytes of shared memory”,報錯中提到了不能分配3896個 bytes 的共享內存,那麼是從那個 pool 中分配呢?

 

OK,讓我們往後面看,("sharedpool","select /*+ leading(CM_BUSI_M...","sgaheap(2,0)","kglsim object batch"),很清楚,這裏說是從shared pool 中分配內存。

 

既然這裏提到了 shared pool,這裏就稍微的講一下 shared pool 的組成,請看下圖(該圖來源於 concept):




通過上圖可以看出 shared pool 可以劃分爲以下幾個部分:


(1)   Library Cache

Library Cache 由 Shared Sql Area 和 Private Sql Area 組成。其中 Library cache 中主要存儲的信息有解析後的 SQL 語句,SQL 執行計劃和解析和編譯後的 PL/SQL 代碼單元;而Private SQL Area 只有在使用共享的服務連接方式並且配置 Large Pool 的時候纔會在Library Cache 中分配,Private SQL Area 由 Persistent Area 和 Runtime Area 組成,它主要記錄了會話相關的進程信息。


(2)   Data Dictionary Cache

Data Dictionary Cache 主要緩存了數據字典的相關信息


(3)   Server Result Cache

Server Result Cache 中主要緩存了部分的查詢結果,這裏請大家思考一下它和 buffer cache 的緩存有什麼不同呢?


(4)   Reserve pool

Reserve pool 是指 Oracle 在默認情況下,shared pool 中會配置一個保留區域,這個保留區域就是 reserved pool,它用做當在普通的 shared pool 列表中的空間不可用時來滿足 large request 的內存分配請求。當一個內存請求大於隱含參數_shared_pool_reserved_min_alloc (默認:4400,可以修改) 時就是一個 large request,反之當內存請求小於 _shared_pool_reserved_min_alloc (默認:4400,可以修改)時就是一個small request. 另外關於 Reserved pool 還有兩個參數需要關注一下,一個是shared_pool_reserved_size,另外一個是隱含參數 _shared_pool_reserved_pct (默認:5%),通過 shared_pool_reserved_size 我們可以爲 reserved pool 指定一個大小,也可以通過_shared_pool_reserved_pct 來爲 shared pool 指定一個比例,如果這兩個參數同時設置了,那麼就會以 _shared_pool_reserved_pct 爲準


(5)   Other

上面 sharedpool 的這幾個組成部分其中 Library Cache 也是結構是比較複雜的,關於 Library Cache 有三張非常經典的圖,如下圖所示:

圖一:Hash Table

圖二:Library CacheObject Handles


圖三:Library CacheObject


這三張圖在我們 enmo 的微信公衆號中另外一位大神做了非常詳細和明白的解釋,大家可以參考【細緻入微:Oracle 中執行計劃在 Shared Pool 中的存儲位置探祕】

http://dwz.cn/3PinUl,我在這裏就不再贅述了。

 

下面我向大家分享另外一個知識點,在 Sahred pool 中是如何尋找可用的內存呢?下面這段僞代碼是一個簡化版的 sharedpool 中分配內存的過程:



 

解釋一下上面的這段僞代碼。當在 shared pool 中請求內存時,首先會搜索 shared pool 的 free list 中是否有足夠的空間,如果有則使用,如果沒有則判斷此次內存請求是一次 large 請求還是一次 small 請求,若是 large 請求,則在 reserved pool 中查找是否有可用的空間,如果找到了可用的內存 (chunck) 則做size檢查,並對內存 (chunck) 做截斷操作,截取所需的內存大小使用,如果在 reserved pool 中依然沒有找到可用的內存 (chunck),則會再次到 shared pool 中去查找是否有可用的內存 (chunck),如果找到了可用的內存 (chunck) 則做 size 檢查,並對內存 (chunck) 做截斷操作,截取所需的內存大小使用,如果依然沒有找到,則對 reserved pool 中的對象做 LRU 算法操作,age out 一些 reserved pool 中的對象,來滿足本次的內存 (chunck) 請求操作,如果還是沒有找到可用的內存 (chunck),則重複 LRU 算法的 age out 操作,直到找到可用內存 (chunck);若是 small 請求,則在 sharedpool 的 free list 中查找是否有可用內存 (chunck),如果找到了可用的內存 (chunck) 則做 size 檢查,並對內存 (chunck) 做截斷操作,截取所需的內存大小使用,如果沒有找到,則對 sharedpool 中的對象做 LRU 算法的 age out 操作,並再次查找是否有可用的內存 (chunck),如果找到了可用的內存 (chunck) 則做 size 檢查,並對內存 (chunck) 做截斷操作,截取所需的內存大小使用,如果沒有找到則重複 LRU 算法的 age out 操作,直到找到可用內存 (chunck)。

 

爲了看起來更加清楚,上面的過程我用一個流程圖做示意:


通過上面這些我們知道了在 shared pool 中請求內存的過程。

 

有了上面這些基礎知識,就可以對本案例的的報錯信息可以進步一的解讀爲在 shared pool請求3968個 bytes 的內存,而且本次內存請求是一個 small request,不會在 reserved pool 中分配。

 

回到本次案例的4031報錯信息中來,繼續進一步解析本案例的報錯:“("sharedpool", "select /*+ leading(CM_BUSI_M...", "sgaheap(2,0)", "kglsim object batch")”. “shared pool”已經解釋過了,那麼來看錯誤的下一個信息 “select /*+ leading(CM_BUSI_M...”. 這個就是導致4031錯誤的 SQL 語句,在 /u01/oracle/app/oracle/admin/pmdb1/bdump/pmdb1_ora_42338038.trc 文件會有這個語句的記錄,來看看 trace 文件中關於這條語句的記錄:




 

上面就是trc文件對於造成4031錯誤的 sql 語句的記錄,這裏記錄了 library 句柄,sql 語句的id,hash 值,持有的 lock 和 pin 的類型,語句狀態等等信息,而且下面還記錄了該語句 child 的相關信息,因爲 sql 語句的多 childcursor 問題也會引起4031錯誤,因此我們有時也需要關注下這裏的信息。

 

從上面的 trace 文件中,可以看出本案例中該語句的 child 數爲6,是一個相對比較小的值了。

 

繼續解讀本次案例的報錯信息“("sharedpool","select /*+ leading (CM_BUSI_M...", "sgaheap(2,0)", "kglsim object batch")”,其中 sga heap(2,0),這個給我透露了什麼信息呢?要理解它,我們需要先看看共享池中 subpool 的演變過程:

 

Oracle 從9i開始爲了提高 Oracle 的併發性,減少競爭,Oracle 將 shared pool 劃分爲多個 subpool,每一個 subpool 都擁有完全相同的內存結構和管理方式,以及自己的 free list,內存結構條目和 LRU list,這也是爲什麼有時我們在查看 heapdump 文件的時候會有多個 Free list 和 LRU list。

 

本次案例中有7個 shared pool(從/u01/oracle/app/oracle/admin/pmdb1 /bdump/pmdb1_ora_42338038.trc 中的 emory Utilization of Subpool 部分可以獲取,內容比較長,我在這裏就不貼了),這也是最多的 subpool 數量。在9i中每個 subpool 至少需要4個CPU(logical),128M內存,10g中每個 subpool 至少需要4個 CPU,256M 內存,11g中每個 subpool 至少需要4個 CPU,512M內存。當然了,subpool 的數量也是通過隱含參數_kghdsidx_count 來控制的,每次 instance 實例啓動的時候,Oracle 根據 CPU 的個數和 MEMORY 的大小來初始化 _kghdsidx_count 的值。而 Oracle 從10g開始將每一個 subpool 又劃分成4個更小的 pool,姑且記爲 sub subpool 吧(這個叫法只是我這樣叫,官方的叫法我沒有查到,知道這個意思就行了)




如上圖中,內部表 x$ksmsp 的 KSMCHIDX 就是 subpool 的編號(其編號爲:1-7),KSMCHDUR 就是 subsubpool 的編號(其編號爲:1-4),上面的 sgaheap(2,0) 就是告訴我們在2號 subpool 的第1(0-3)個 sub subpool,那麼進一步的我們本次案例的報錯信息可以解讀爲在 shared pool 的第2個 subpool 的第一個 sub subpool 中分配 3896 bytes 的內存失敗。

 

接着繼續解讀本次案例的報錯信息“("sharedpool", "select /*+ leading(CM_BUSI_M...", "sgaheap(2,0)", "kglsim object batch")”,下面我們關注的信息就是 “kglsimobject batch” 這個信息了。

 

我們說當一個內存塊被分配到內存池中時,它會被賦予一個內存類型,shared pool 中有四種類型,分別爲 free,freeabl,perm 和 recr。其中 free 是在 Free list 上可用的內存(chunck),freeabl 是指掛載在 recr 下面的內存,他的分配的方式使得當內存處理結束時,用戶可以顯式的釋放內存塊這些內存(chunck),perm 是指在整個實例的整個生命週期中都存在,且不可讓用戶使用的內存(chunck),recr 是指掛載在 LRUlist 上的內存(chunck)。之後 shared pool 中的內存會被賦予一種池中的內存結構或者元素,例如"SQLA heap"。而上面的kglsim objectbatch就是 shared pool 中的一種內存結構或者稱爲元素。

 

那麼本次案例中的4031報錯信息就可以解讀爲在 shared pool 的第2個 subpool 中的第1個 sub subpool 中請求 3896bytes 的 kglsim object batch 的內存結構時失敗,進而導致了4031錯誤。

 

既然通過上面的分析我們知道了本次案例是在 heap (2,0) 上分配內存失敗,那麼我們從 trc文件來看看第二個 subpool 內存情況(下面的內容有省略,只保留了 trc 文件中很少的一部分相關內容):




過查看 trc,可以看出佔用內存比較高的內存結構爲:SQLA,KGLHD,KKSSP,KGLH0,gcs resources,gcsshadows 等。注意:這些值所謂高的都只是一個相對值,並不是絕對的。

 

上面 trc 文件中的結果可能看起來比較晦澀難讀,那麼我們通過 sql 語句來查看一下 shared pool 中不同的內存結構佔用空間的情況(只截取了我們關心的部分):




從上面的結果可以看出每個 subpool 中的 perm 類型的內存基本上都是在第 一個 sub subpool 中分配的,其他的 sub subpool 中的 perm 類型的內存結構都非常小,而且是一個固定的值 (80bytes),這就是說每個 heap (x,0) 中 perm 類型的內存結構佔據了很大一部分的空間。


再來查詢一下本案例中每個 sub pool 的空閒內存的情況(截取了部分結果):




從結果可以看出,2號 subpool 的1號 subsubpool 中( 即 heap(2,0))最大的空閒可用的內存爲2944個字節,因此要在其上分配3896個字節的內存必然會失敗。如果我們細心點就會發現1號 subpool 的4號 subsubpool 中可用的最大空閒內存爲15937536個字節,這就是說 heap(1,3) 是可以滿足我們本次的內存請求,那麼它爲什麼不在 heap(1,3) 中分配呢?這是因爲使用子池會存在一個缺點:在有些情況下,個別子池被過分利用了。一旦子池選定,即使其他子池有合適的可用內存,內存塊的搜索也可能失敗。從 10g 開始,我們確實有這樣的功能,允許當內存請求在選定的子池中無法滿足時,“交換”到其他子池進行搜索,但這功能不可能對所有的內存結構和元素都起作用。注意:有一小部分功能會跨子池的利用內存塊。換句話說,就是跨越多子池的條帶化使用內存。這極少有文檔記錄,一般來說,內存請求會以輪轉的方式,從一個“隨機”的子池中找到它需要的內存塊。

 

結合不同內存結構的內存在 shared pool 中的分配情況,以及上面提到的知識點,我們可以理解爲什麼我們經常會碰到 heap(x,0)中的 4031 錯誤,而很少碰見 heap(x,1-3) 中的 4031 報錯,於此同時這也導致了 subpool 的不均衡使用。


根據上面的分析結果,我們可以確定本案例中造成4031錯誤的原因就是 subpool 的不均衡使用導致的。

 

針對本案例的解決4031錯誤的措施:因爲本案例中的數據庫版本爲11.2.0.3.0的,也使用了 AMM,但是經過查詢後得知,shared pool 和 data buffer cache pool 均設置了一個比較大的初始化值,這導致實際上可以動態分配的內存其實很少。因此可以將 shared_pool 的值在原來的基礎上適當的調大些,並且禁用 AMM 或者 ASMM;另外可以通過設置 _enable_shared_pool_durations=false 來改變 subpool 中內存分配的方式,或者完全使用 AMM。

 

到這裏,我們本案例的4031錯誤基本上已經闡述清楚了。

 

下面我簡單的總結一下發生4031錯誤的原因以及應對措施:


(1)   極高的硬解析,例如沒有使用綁定變量。如果硬解析高的話,經過一段時間 shared pool 的碎片率會變得非常的高,在申請稍微大一點的 shared pool 內存時,雖然總體來說空閒的空間還有一些,但是並沒有連續的較大空間可用,這就會造成4031錯誤。遇見這種情況我們就應該儘量減少硬解析的數量,例如使用綁定變量,使用 cursor_sharing=FORCE 參數(不建議使用)。


(2)   open_cursor 設置的過大。open_cursor 如果設置的過大,導致 library cache 中很多對象都處於 pin 狀態,而不能釋放,那麼當申請 shared pool 內存時,通過 LRU 依然不能找到可用空間,就會導致4031錯誤。遇見這種情況我們可以適當減少 open_cursor 的值。


(3)   shared pool 確實太小。有時候我們的 shared pool 相對於數據庫的壓力來講確實 shared pool,這個時候就需要增加 shared pool(SGA)的大小。


(4)   subpool 的不均衡使用。Subpool 的不均衡使用是使用 subpool 一個缺點之一,對於這種情況我們可以使用設置隱含參數 _enable_shared_pool_durations=false 來改變shared pool 的 subpool 內存結構的分配方式,或者完全使用 AMM.


(5)   Oracle Bug,Oracle 的與4031相關的 BUG 有很多,這個我們可以多關注 MOS 上的相關知識。

 

好了,關於4031的錯誤我就跟大家分享到這裏。

 

最後,我提出一個問題希望大家有時間思考一下。我們知道 SGA 中 data buffer cache 是通過標準化的 buffer 來管理的,而 shared pool 是通過 heap 的方式來管理,這也是造成爲什麼與 shared pool 相關的很多東西都不是很好理解,比如說4031錯誤,那麼我的問題是:爲什麼 Oracle 不用管理 data buffer cache 的方式來管理 shared pool,而是通過 heap 的方式來管理呢?

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章