初探Oracle裏的Cursor
遊標(Cursor)
談起遊標首先要談的就是sga和pga了,先簡單討論一下sga和pga:
SGA:
在SGA中有一個塊固定區域Fixed Size ,它包含幾千個變量和一些小的數據結構,如 Latch或地址指針等,這部分內存分配和特定的數據庫版本以及平臺有關,不受用戶控制,而且這些信息對於數據庫來說非常重要,但是通常我們用戶不需要關心。固定部分只需要很小的內存,可以通過一個內部表 X$KSMFSV([K]ernel [S]ervice Layer ,[M]emory Management,Addresses of [F]ixed [S]GA [V]ariables)查詢。此外 Oracle 的內部表X$KSMMEM 記錄了整個 SGA 的地址映射關係,通過 X$KSMFSV 和 X$KSMMEM 關聯,可以找出 Fixed Area 中每個變量的設置。
1.Buffer Cache:Buffer Cache是SGA中一個重要的組件。在Buffer Cache物理層是由很多個內存顆粒組成的內存區域,並且受LRU管理,超出該規則的數據塊就會age out,這種會影響Oracle額外的io消耗並且Oracle爲此問題提出了多緩衝池技術。多緩衝池技術是指,根據不同數據的不同訪問方式,將 Buffer Cache 邏輯層分爲 Default、Keep 和 Recycle 池三個部分。對於經常使用的數據,我們可以在建表時就指定將其存放在 Keep池中(Keep 池中的數據傾向於一直保存);對於經常一次性讀取使用的數據,可以將其存放在 Recycle 池中(Recycle 池中的數據傾向於即時老化);而 Default 池則存放未指定存儲池的數據,按照 LRU 算法管理。
這裏對Default池進行討論,在Buffercache Default池中有LRU、WLRU兩個鏈表用於管理申請Buffer chche中的Buffer,這兩個鏈表(邏輯層)分別用指針指向Buffer Cache中的內存顆粒(物理層)。
LRU list:LRU List 用於維護內存中的 Buffer,按照 LRU 算法進行管理所有的 Buffer 都被 Hash 到 LRU List 上管理。當需要從數據文件上讀取數據時,首先要在LRU List上尋找Free-Bucket通過指針找到對應的Free-Buffer,若沒有足夠的Free-Bucket則通過LRU原則age out部分Bucket使其有足夠的Free-Bucket,然後讀取數據到選取的Buffer Cache中;當數據被修改之後,之前選取的Free-Bucket狀態變爲 Dirty,並且移動至 LRUW List,LRUW List 上的都是待被DBWR 寫出到數據文件的 Bucket對應的Buffer,一個 Buffer對應的Bucket 要麼在 LRU List 上,要麼在LRUW List 上存在,不能同時在這兩個 List 上存在。
LRUW List:存放更改過的Buffer對應的Bucket,等待被DBWR寫出到file中,在數據落盤時同時需要檢查點隊列,遵循先進先出原則增量落盤。
檢查點隊列(Checkpoint Queue)則負責按照數據塊的修改順序記錄數據塊,同時將 RBA和數據塊關聯起來,這樣在進行增量檢查點時,數據庫可以按照數據塊修改的先後順序將其寫出,從而在進行恢復時,可以根據最後寫出數據塊及其相關的 RBA 開始進行快速恢復。在檢查點觸發時 DBWR 根據檢查點隊列執行寫出,在其他條件觸發時,DBWR 由 Dirty List 執行寫出。
請注意:此處的邏輯層和物理層是爲了方便理解引用的。
在Buffer Cache中還存在Hash Bucket 和 Cache Buffer Chain兩個結構用於快速從Oracle Buffer Cache中查找目標Buffer,其結構是這樣的:
Oracle將Buffer Cache中的諸多Buffer利用hash算法分配到諸多bucket中,每個bucket存放Buffer的地址由Buffer數據塊地址(DBA,Data Block Address)決定,bucket 和Buffer之間通過指針連接。在 Bucket 內部,存在Buffer Header這樣一個結構,Buffer Header 存放的是對應數據塊的概要信息(數據塊的文件號、塊地址、狀態),通過 Cache Buffer Chain(雙向鏈表)將所有的 Bucket 通過 Buffer Header 信息聯繫起來。在實際尋址Buffer Cache時,Oracle只需要查到hash bucket,通過其中的Cache Buffer Chain找到目標Buffer Header然後找到目標Bucket,最後通過hash bucket中的DBA找到Buffer Cache中的Buffer,讀取Buffer Cache中的信息。
當衆多SQL從Buffer Cache中讀取數據時,會有那麼一種情況:多條SQL共同訪問一個或者多個Bucket來獲取對應得內存數據,而Oracle力求串行操作故會對Buffer中Oracle訪問目標chain產生Latch。爲解決此問題,Oracle對於 Cache Buffer Chain 的只讀訪問,其 Latch 可以被共享(即共享Latch)。但是如果對目標塊進行更改就需要產生獨佔Latch。這就是 Buffer Cache 與 Latch 競爭。由於 Buffer 根據 Buffer Header 進行散列,最終決定存入那一個 Hash Bucket,那麼 Hash Bucket 的數量在一定程度上決定了每個 Bucket 中 Buffer 數量的多少,也就間接影響了產生Latch的多少。
2.Shared Pool :Shared Pool又是SGA中的另一個重要組件,主要爲Oracle用戶進程分配內存,可以說是一個比較核心的組件。
Oracle Shared Pool主要由 Library Cache 、Dictionary cache 和Result Cache Memory(11g and later)三塊區域組成,爲了增加對大共享池的支持,Shared Pool Latch 從原來的一個增加到現在的7個,如果用戶的系統有4個或者4個以上的cpu,oracle可以把Shared Pool分割爲subShared Pool進行管理,每個subpool都擁有獨立的機構、LRU和Shared Pool Latch。
(1)Dictionary cache:主要存放存放數據字典信息,包括表、視圖等對象的結構信息,用戶以及對象權限信息,這部分信息相對穩定,在 Shared Pool 中通過字典緩存單獨存放,字典緩存的內容是按行(Row)存儲的(其他數據通常按 Buffer 存儲),所以又被稱爲 Row Cache,其信息可以通過 V$ROWCACHE 查詢。
(2)Library Cache:
oracle中所有的庫緩存對象(Library Cache Object)都是以庫緩存對象句柄(Library Cache Object Handle)的形式存放的,庫緩存對象句柄是以哈希表(Hash Table)的方式存儲在Library Cache中,在Hash Table中有多個Hash Bucket,Hash Bucket中存放了哈希值相同數庫緩存對象句柄,不同對象句柄之間通過指針形成庫緩存對象句柄鏈表(Library Cache Object Handles)。
在庫緩存對象句柄鏈表(Library Cache Object Handle)中有很多的屬性,其中NAME代表庫緩存對象的名稱(即SQL文本或表名);Namespace代表庫緩存對象對應的分組名,不同類型的庫緩存對象可能對應相同的分組名。
(3)Result Cache Memory(for 11g and later):
結果集緩存就是講結果集存放在Shared Pool中,經過多次調用可以達到consistent gets=0,結果集緩存大大降低了特定SQL的執行成本。Oracle 通過一個新引入初始化參數 result_cache_max_size 來控制該 Cache 的大小。如果result_cache_max_size=0 則表示禁用該特性。參數 result_cache_max_result 則控制單個緩存結果可以佔總的 Server Result Cache 大小的百分比
[oracle@edsir4p1-PROD1 ~]$ sqlplus / as sysdba
SQL*Plus: Release 11.2.0.1.0 Production on Sun Sep 1 03:31:59 2019
Copyright (c) 1982, 2009, Oracle. All rights reserved.
Connected to:
Oracle Database 11g Enterprise Edition Release 11.2.0.1.0 - Production
With the Partitioning, OLAP, Data Mining and Real Application Testing options
SYS@PROD1>
SYS@PROD1>
SYS@PROD1> set linesize 200;
SYS@PROD1> set pagesize 10000;
SYS@PROD1> set autotrace trace;
SYS@PROD1> select * from hr.employees where salary>3000;
81 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 1445457117
-------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 81 | 5589 | 3 (0)| 00:00:01 |
|* 1 | TABLE ACCESS FULL| EMPLOYEES | 81 | 5589 | 3 (0)| 00:00:01 |
-------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter("SALARY">3000)
Statistics
----------------------------------------------------------
33 recursive calls
0 db block gets
17 consistent gets
0 physical reads
0 redo size
7532 bytes sent via SQL*Net to client
475 bytes received via SQL*Net from client
7 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
81 rows processed
SYS@PROD1> show parameter result
SYS@PROD1> set autotrace off;
SYS@PROD1> show parameter result
NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
client_result_cache_lag big integer 3000
client_result_cache_size big integer 0
result_cache_max_result integer 5
result_cache_max_size big integer 3136K
result_cache_mode string MANUAL
result_cache_remote_expiration integer 0
--開啓表HR.EMPLOYEES的結果集緩存模式爲force。
SYS@PROD1> alter table hr.employees result_cache(mode force);
Table altered.
SYS@PROD1> alter system set result_cache_max_size=15m;
System altered.
發現此時consistent gets和physical reads已經全爲0了,直接從result cache memory讀取結果集
SYS@PROD1> set autotrace trace;
SYS@PROD1> select * from hr.employees where salary>3000;
81 rows selected.
Execution Plan
----------------------------------------------------------
Plan hash value: 1445457117
-------------------------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 81 | 5589 | 3 (0)| 00:00:01 |
| 1 | RESULT CACHE | 9cgfp0b36ugmzb9s3wkk1thpmm | | | | |
|* 2 | TABLE ACCESS FULL| EMPLOYEES | 81 | 5589 | 3 (0)| 00:00:01 |
-------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("SALARY">3000)
Result Cache Information (identified by operation id):
------------------------------------------------------
1 - column-count=11; dependencies=(HR.EMPLOYEES); name="select * from hr.employees where salary>3000"
Statistics
----------------------------------------------------------
0 recursive calls
0 db block gets
0 consistent gets
0 physical reads
0 redo size
7532 bytes sent via SQL*Net to client
475 bytes received via SQL*Net from client
7 SQL*Net roundtrips to/from client
0 sorts (memory)
0 sorts (disk)
81 rows processed
(4)redo and undo(for 10g and later):
在Oracle 10g以後爲了解決redo undo 產生的Latch爭用和IO問題,在Shared Pool中單獨分配了一塊專用內存,用於緩存相應的結果集,減少Latch和IO爭用。
redo:在Log Buffer中申請了多塊 Private Redolog Strands區域,同時綁定在Shared Pool中分配的多塊區域,每個事物在Shared Pool中產生的redo信息由Shared Pool中的專用區域暫時存儲,最後一起寫入到 Log Buffer對應的 Private Redolog Strands中,通過lgwr統一寫入 online redo。
undo:在 Shared Pool中單獨分配了一塊區域IMU Buffer,用於轉儲臨時產生的undo塊,最後有序批量的寫入undo段中。
(5)Golabl Resource Directory(for RAC):
在Oracle RAC中還需要在Shared Pool中單獨分配一塊區域(GRD)用於存儲實例間的通訊信息和鎖信息,在GRD中包含 GCS(global Cache serves)和GES (global queue servers)兩個服務。
GCS(global Cache serves):協調集羣中數據塊中全局鎖的處理訪問(cache fusion),處理工作的進程是LMS
LMS:維護數據文件狀態和每個高速緩存塊的記錄。該LMS過程還控制到遠程實例的消息流
GES (global queue servers):協調集羣中的其他資源(鎖),保證實例信息同步,維護各節點的數據字典表和庫緩存一致性。處理工作的進程是LMD和LCK0
LMD:管理每個實例的傳入資源請求
LCK0:管理 非cache fusion資源請求,例如庫和行緩存請求、實例本身的鎖
(6)Other:
除了以上列舉的一些組件以外,Oracle Shared Pool中還有一些其他的組件,這些組件不像Library Cache 那樣一直存在,例如rman備份恢復時也需要在Shared Pool中申請內存,這個內存在完成備份恢復之後就釋放了。
PGA
在專用模式下,客戶端的每一個process都由服務端專門的server process接管,並且在pga中分配相應的區域
固定 PGA(Fixed PGA):固定 PGA 和固定 SGA 類似,包含了大量原子變量、小的數據結構和指向可變 PGA 的指針,這些變量在源碼中定義,在編譯時分配,可以被認爲是 PGA 的保留內存
可變 PGA(Variable PGA): 可變 PGA 通過具體的內存 Heap 分配來實現,其空間分配與使用時可以變化的,通過內部視圖 X$KSMPP([K]ernel [S]ervice [M]emory [P]GAhea[P])可以查詢可變 PGA 內存的分配和使用情況。PGA 的可變區中主要包含會話內存及私有 SQL 區等。
會話內存 - Session Memory:用於存放會話的登錄信息以及其他相關信息,對於共享服務器模式,這部分內存是共享而非私有的。
私有的 SQL 區 - Private SQL Area:Private SQL Area 包含綁定變量信息、查詢執行狀態信息以及查詢工作區等。每個發出 SQL 查詢的會話都擁有一塊私有 SQL 區,對於專用服務器模式,這部分內存在 PGA 中分配,對於共享服務器模式,這部分內存在 SGA 中分配。
永久區域 - Persistent Area:這個區域包含綁定變量等信息,這部分內存只有在遊標被關閉時纔會被釋放
運行時區域 – Runtime Area:這個區域存放了 SQL 語句運行時所需要的信息,在執行請求時首先創建,其中包含了查詢執行的狀態信息(如對於全表掃描,則記錄全表掃描的進度等)、SQL work areas(這部分區域在內存密集型請求下分配,如 Sort或者 Hash-Join 等,對於 DML 語句來說,SQL 語句執行完畢就釋放該區域,對於查詢語句則是在記錄返回後或查詢取消時釋放)
UGA 中的內存分配可以通過內部表 X$KSMUP(X$KSMUP - [K]ernel [S]ervice [M]emory[U]GA Hea[P])查詢得到。UGA 堆包含了存儲一些固定表(X$表)的永久內存(依賴於特定參數的設置,如 OPEN_CursorS,OPEN_LINKS 和 MAX_ENABLED_ROLES)。除此以外,大部分的 UGA 用於私有 SQL 區和 PL/SQL 區。
討論完sga和pga後討論一下Shared Pool中的遊標:
遊標的存在直接影響oracle執行效率,在之前文章中討論了優化器和統計信息對執行計劃的影響,但是優化器估算出來的執行計劃也不完全等價於實際中的運行成本,oracle中的一條SQL運行時會受Buffer Cache、Cursor、Latch等諸多因素影響。實際中相同的SQL在oracle中最理想的情況下就是使用一條執行計劃,之後的語句直接使用此執行計劃,那麼此時的實際成本就低於CBO估算的成本。實際上,oracle中解析樹和執行計劃就是以Shared Cursor的形式存放在Library Cache中,在專用模式下,一個會話也會在pga中申請對應的內存,並在內存裏產生Session Cursor以供sql調用,同一個會話中的相同SQL會使用同一個Session Cursor,此時就不需要再次打開可一次調用,這也會大大降低SQL的實際運行成本。可以說oracle中以遊標標記了已經執行過的所有記錄,並通過此方式降低非必須的性能消耗。
Shared Cursor
上面已經討論了Shared Pool。當一條SQL執行是首先是與存放在Dictionary cache 中的數據字典、表和視圖等信息進行校驗解析,然後再在Library Cache中申請Latch在其中找到相應解析樹和執行計劃,通過此執行計劃直接讀取目標表中的數據塊到Buffer Cache中進行數據關聯返回結果;若在Library Cache中找不到相應的解析樹和執行計劃oracle就會立即釋放 Library Cache Latch 同時在Shared Pool中申請 Shared Pool Latch申請內存,通過CBO在pga中利用目標表的統計信息估算出合理的執行計劃並將執行計劃存放到Library Cache中,然後釋放 Shared Pool Latch在Library Cache中產生Library Cache Latch申請內存,讀取目標表的數據塊至Buffer Cache中(若Shared Pool中沒有連續的trunk塊就會報ORA - 04031錯誤)。一條SQL的執行過程大致是這樣的,此處首先討論Library Cache中的內存結構以及執行計劃的存放方式和尋址方式。
Shared Cursor:存在於Library Cache中庫緩存對象的一種,對應的庫緩存對象句柄鏈表的Namespace值爲CRSR。它會存儲目標SQL的SQL文本,解析樹,該SQL所涉及的對象定義,該SQL使用的綁定變量類型和長度和SQL的執行計劃等信息。爲了減少庫緩存對象句柄鏈表長度oracle引入了Parent Cursor和Child Cursor的概念。在oracle中Parent Cursor和Child Cursor的結構相同,Child Cursor的name屬性爲null,實際執行計劃和解析樹存放於Child Cursor中,意味着oracle只能通過Parent Cursor才能找到Child Cursor並且同一個Parent Cursor下的SQL文本或表名是相同的。換句話說oracle硬解析就產生產生一個Parent Cursor和Child Cursor。Shared Cursor就是Library Cache諸多緩存對象中的一種。
select * from hr.employees where salary>3000;
EMPLOYEE_ID FIRST_NAME LAST_NAME EMAIL PHONE_NUMBER HIRE_DATE JOB_ID SALARY COMMISSION_PCT MANAGER_ID DEPARTMENT_ID
----------- -------------------- ------------------------- ------------------------- -------------------- --------- ---------- ---------- -------------- ---------- -------------
200 Jennifer Whalen JWHALEN 515.123.4444 17-SEP-03 AD_ASST 4400 101 10
SYS@PROD1> select sql_text,sql_id ,version_count from v$sqlarea where sql_text like 'select * from hr.employees where salary>%';
SQL_TEXT SQL_ID VERSION_COUNT
-------------------------------------------------- ------------- -------------
select * from hr.employees where salary>:"SYS_B_0" 1cd3n1marv8qa 1
SYS@PROD1> select plan_hash_value,Child_number from v$sql where sql_id='1cd3n1marv8qa';
PLAN_HASH_VALUE Child_NUMBER
--------------- ------------
1445457117 0
oracle中根據目標SQL的SQL文本的哈希值去相應的Hash Bucket中找匹配的Parent Cursor,由於是hash 運算所以此過程對大小寫是極其敏感的。
select * from hr.EMPLOYEES where SALARY>3000;
select * from hr.employees where salary>3000;
SYS@PROD1> select sql_text,sql_id ,version_count from v$sqlarea where sql_text like 'select * from hr.%';
SQL_TEXT SQL_ID VERSION_COUNT
-------------------------------------------------- ------------- -------------
select * from hr.EMPLOYEES where SALARY>:"SYS_B_0" 86vf8hxvtccg9 1
select * from hr.employees where salary>:"SYS_B_0" 1cd3n1marv8qa 1
Session Cursor:
Session Cursor是當前Session解析和執行的SQL載體,結構同Shared Cursor一致都是c語言的一種複雜架構,以哈希表的形式緩存在server process pga中
①Session Cursor存在於server process pga中,不同Session之間不能共享。
②Session Cursor生命週期內至少經歷一次open、parse、bind、execute、fetch和close中的一個或多個階段,若Session_CACHED_CursorS參數設置大於0則用過的Session Cursor會存放在server process pga中。
經過以上對Shared Cursor和Session Cursor的討論可以很容易理解硬解析、軟解析、軟軟解析
硬解析:當一條SQL在Library Cache產生Latch 進行查找解析樹和執行計劃時,首先找到對應Parent Cursor,在通過Parent Cursor找到相對應的Child Cursor,同Child Cursor中找到對應的解析樹和執行計劃,Parent Cursor和Child Cursor一定是成對存在的,若找不到Parent Cursor 或者找不到Child Cursor 就需要立即釋放Latch,在Shared Pool中分配內存在server process pga中進行執行解析,這就是硬解析。
軟解析:對比硬解析,若可以找到對應的一對Parent Cursor和Child Cursor,那麼Oracle就不需要再重新分配內存重新執行解析了,而是使用當前Child Cursor存放的執行計劃解析數直接從Buffer Cache中讀取目標數據塊返回查詢結果。這就是軟解析。
軟軟解析:對比軟解析,軟解析在執行過程中會產生對應的Session Cursor來承載SQL,首次產生Session Cursor直至結束一定是執行完整的open、parse、bind、execute、fetch和close過程的,那麼在這個過程中會有額外的性能消耗,若在同一個會話可以使用同一個Session Cursor作爲多條相同SQL的載體,意味着Shared Cursor 和Session Cursor 就都能找到匹配記錄,不需要再重新生成Session Cursor直接拿來使用現有的Session Cursor,只需執行parse、bind、execute、fetch,使用完畢直接標記爲Soft Closed,不需要close。顯然軟軟解析是最理想的。
當目標SQL執行時對應的Session Cursor狀態爲execute(該SQL正在執行)時,Oracle就需要先把該SQL對應的Child Cursor 給pin到庫緩存中(首先持有與庫緩存相關的Latch,在持有Library Cache pin這個enqueue來實現(是Oracle另一種鎖,比Latch要重遵循先進先出,不具有原子性)),防止被age out出去。
Shared Cursor 和Session Cursor總結:
①一個Session首先去server process pga中找是否存在匹配的Session Cursor。
②若pga中找不到匹配的Session Cursor,就會在server process pga中新生成一個Session Cursor和一對Shared Cursor(若找到了Parent Cursor找不到匹配的Child Cursor,就會生成一個Child Cursor)--硬解析
③若在pga中找不到匹配的Session Cursor,在Library Cache中找到了對應的Shared Cursor,就會在server process pga中產生一個新的Session Cursor並和Shared Cursor匹配-- 軟解析
④若在pga中找到了匹配的Session Cursor,那麼此時就不需要在去Library Cache中尋找對應的Shared Cursor,僅通過Session Cursor就可完整整個SQL的解析過程--軟軟解析
相關參數:
open_Cursors:用於設定Session中能夠以open狀態並存的Session Cursor個數
SYS@PROD1> show parameter open_Cursors
NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
open_Cursors integer 300
查看具體已經存放pga中的Session 個數:
SYS@PROD1> col Cursor_TYPE for a40;
SYS@PROD1> col SQL_TEXT for a60;
SYS@PROD1> col USER_NAME for a10;
SYS@PROD1> col SID for 999;
SYS@PROD1> set linesize 200;
SYS@PROD1> select * from v$open_Cursor where sid=173;
SADDR SID USER_NAME ADDRESS HASH_VALUE SQL_ID SQL_TEXT LAST_SQL_ SQL_EXEC_ID Cursor_TYPE
-------- ---- ---------- -------- ---------- ------------- ------------------------------------------------------------ --------- ----------- ------------------------------
457C8190 173 SYS 45902498 2195068792 asvzxj61dc5vs select timestamp, flags from fixed_obj$ where obj#=:1 Session Cursor CACHED
457C8190 173 SYS 45940270 576224028 g6sky1sj5hysw table_1_ff_210_0_0_0 OPEN
457C8190 173 SYS 400B8F88 337957580 a6u3yjca29nqc declare m_stmt varchar2(512); begin m_stmt:='delete fr DICTIONARY LOOKUP Cursor CACHED
457C8190 173 SYS 459C79B0 3819099649 3nkd3g3ju5ph1 select obj#,type#,ctime,mtime,stime, status, dataobj#, flags DICTIONARY LOOKUP Cursor CACHED
457C8190 173 SYS 3C72332C 907602229 2205n7sv1ju9p table_4_9_138b_0_0_0 OPEN-RECURSIVE
457C8190 173 SYS 3C787420 3199009429 2b85h9azau0np select * from v$open_Cursor where sid=:"SYS_B_0" 16777221 Session Cursor CACHED
457C8190 173 SYS 400B87E0 1949913731 3972rvxu3knn3 delete from sdo_geor_ddl__table$$ PL/SQL Cursor CACHED
457C8190 173 SYS 3E8FF2F4 3296299297 5dwybn727m291 select count(FA#) from SYS_FBA_TRACKEDTABLES where OBJ# = 73 DICTIONARY LOOKUP Cursor CACHED
457C8190 173 SYS 45936B04 1950984145 5sxszjtu4m9yj table_1_ff_218_0_0_0 OPEN
457C8190 173 SYS 45931BC4 864012087 96g93hntrzjtr select /*+ rule */ bucket_cnt, row_cnt, cache_cnt, null_cnt, DICTIONARY LOOKUP Cursor CACHED
457C8190 173 SYS 458BAF38 2907818604 g4um54aqp3kmc table_1_ff_220_0_0_0 OPEN
457C8190 173 SYS 45937840 3349278045 4n6n6yr3u3vax table_1_ff_214_0_0_0 OPEN
12 rows selected.
Session_cached_Cursors:設定單個Session能夠以soft closed狀態並存的Session Cursor的總數
SYS@PROD1> show parameter Session_cached_Cursors;
NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
Session_cached_Cursors integer 50
Cursor_space_for_time:解決Child Cursor上與Library Cache相關的Latch徵用個,Oracle 11gr1之後用mutex替代了Latch,已經不在使用,在Oracle10g還在廣泛使用。
SYS@PROD1> show parameter Cursor_space_for_time
NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
Cursor_space_for_time boolean FALSE
在Cursor_space_for_time=false(默認)時,一個Session Cursor反覆的進行parse、bind、execute、fetch過程(不論是否需要反覆open 和close,在這裏對下面要討論的Latch爭用起不到決定性作用,這裏爲理解方便假定使用Session Cursor共享),這個Session Cursor對應的Child Cursor上的Library Cache pin在每次execute之後就會完全釋放。當再次執行時還需要反覆進行以上一系列操作,在高併發時顯然這會產生Latch爭用。Cursor_space_for_time=true就是在Session Cursor 每次執行完之後依然持有其對Child Cursor上的Library Cache pin,不釋放,當再次運行該SQL時就不需要在進行 parse、bind等操作,直接使用Child Cursor就可以,在11gr1之前可以近乎完美的解決Shared Pool中的Latch爭用,當然由於Latch一直持有不釋放也會造成給Shared Pool空間造成一定的壓力,就會出現我們常見的ORA - 04031 。
隱式遊標:
隱式遊標是Oracle數據庫的一種Session Cursor,它的生命週期管理不需要人爲干涉,全部是由SQL引擎或者PL/SQL引擎自動來完成
SQL%FOUND:一條SQL語句執行成功後受其影響的而改變的記錄數是否大於等於1。
SQL%NOFOUND:一條SQL語句執行成功後受其影響而改變的記錄數是否爲0。
SQL%ISOPEN:隱式遊標是否處於open狀態。
SQL%ROWCOUNT:一條SQL語句執行成功後受其影響而改變的記錄的數量。
顯式遊標:
顯示遊標是Oracle數據庫的另外一種類型的Session Cursor,通常用PL/SQL代碼來管理其生命週期(open、fetch和close)
CursorNAME%FOUND:指定顯示遊標(Cursorname)是否至少有一條記錄被fetch了。當一個顯式遊標被open後,如過一次都沒有被fetch,那麼CursorNAME%FOUND值爲NULL;當這個顯示遊標被fetch後,CursorNAME%FOUND值爲TURE,直到全部fetch完畢。而全部fetch完畢後,再次還行一次fetch,Oracle不會報錯,只是此時CursorNAME%FOUND值爲FALSE。
CursorNAME%NOTFOUND:指定顯式遊標是否已經fatch完畢了,邏輯上與CursorNAME%FOUND相反。當一個顯式遊標被open後,如過一次都沒有被fetch,那麼CursorNAME%NOTFOUND值爲NULL;當這個顯示遊標被fetch後,CursorNAME%NOTFOUND值爲FALSE,直到全部fetch完畢。而全部fetch完畢後,再次還行一次fetch,Oracle不會報錯,只是此時CursorNAME%NOTFOUND值爲TRUE。
CursorNAME%ISOPEN:執行顯式遊標是否沒open,用於標準的exception處理流程中,close那些由於發生了exception而導致有顯式遊標沒有正常關閉的情形。
CursorNAME%ROWCOUNT:迄今爲止一共fetch多少行記錄。當一個顯式遊標被open後,如過一次都沒有被fetch,那麼CursorNAME%ROWCOUNT值爲0;當這個顯示遊標被open後,如果被fetch後範圍結果集爲空,那麼CursorNAME%ROWCOUNT值也是0,只有這個顯式遊標被fetch過一次且返回結果集不爲空,那麼CursorNAME%ROWCOUNT的值不爲0,並且隨着每一次fetch,CursorNAME%ROWCOUNT的值都會遞增,他的值就表示指定的顯示遊標迄今爲止一共fetch的記錄數。
參考遊標(Ref Cursor):
和顯示遊標一樣,參考遊標也通常用於PL/SQL代碼中,由PL/SQL管理參考遊標的生命週期。
同樣包含四種屬性,同顯式遊標。
CursorNAME%FOUND:
CursorNAME%NOTFOUND:
CursorNAME%ISOPEN:
CursorNAME%ROWCOUNT:
綁定變量(Bind variable)
之前討論過,在OLTP業務中會出現大量的SQL文本相同,只有每次傳入的具體值不同,這樣的SQL在實際解析時需要根據每條SQL的hash_value和SQL文本查找對應的Child Cursor,每次都要分配內存解析執行計劃,爲了減少無用的SQL解析帶來的時間消耗,爲每次傳入的具體值列引入了綁定變量,實際上理想狀態下一條SQL文本只需解析一次,剩下的只需要用產生的執行計劃就可以。這大量介紹了硬解析的次數。
綁定變量典型用法:
declare
vc_name varchar2(10);
begin
execute immediate 'select ename from emp where empno = :1' into vc_name using 7369;
dbms_output.put_line(vc_name);
end;
/
PL/SQL綁定變量的典型用法:
declare
vc_sql_1 varchar2(4000);
vc_sql_2 varchar2(4000);
n_temp_1 number;
n_temp_2 number;
begin
vc_sql_1 := 'insert into emp(empno,ename,job) values(:1,:2,:3)';
execute immediate vc_sql_1 using 7370,'CAP1','DBA';
n_temp_1 :=sql%rowcount;
vc_sql_2 := 'insert into emp(empno,ename,job) values(:1,:1,:1)';
execute immediate vc_sql_2 using 7371,'CAP2','DBA';
n_temp_2 :=sql%rowcount;
dbms_output.put_line(to_char(n_temp_1+n_temp_2));
commit;
end;
/
JAVA中綁定變量的典型用法:
String dml = "update emp set sal = ? where emono = ?";
pstmt = connection.prepareStatement(dml);
pstmt.clearBatch();
for(int i = 0 ; i < UPDATE_COUNT; ++i)
{
pstmt.setInt(1,generateEmpno(i));
pstmt.setInt(2,generateSal(i));
pstmt.addBatch();
}
pstmt.executeBatch();
connection.commit();
在使用綁定變量時綁定變量數量不宜過多,數量過多在變量匹配時會消耗很長的時間,這無異於是不能接受的。
對於Shared Cursor還在Shared Pool中使用綁定變量的SQL我們可以通過V$SQL_BIND_CAPTURE查看具體傳入的變量值。V$SQL_BIND_CAPTURE中記錄了硬解析的綁定變量值,對於軟解析或軟軟解析默認是每15分鐘捕獲一次,並且對於insert 語句只捕獲where以後的綁定變量值,對於values字句中的綁定變量值不去捕獲。
對於Shared Cursor已經age out出Shared Pool的使用綁定變量的SQL我們可以通過DBA_HIST_SQLSTAT或DBA_HIST_SQLBIND來查看。
前面文章《淺談Oracle優化器和統計信息》已經做了討論,對於出現嚴重列傾斜的不建議在該列使用綁定變量。在Oracle10g之前不會自動收集直方圖信息,那麼CBO就會錯誤估算每一個值的成本,選錯執行計劃;Oracle10g以後會自動收集了直方圖信息,由於列傾斜嚴重目標值的Selectivity也不一樣,使用綁定變量之後就會出現比較糟糕的現象就是如果第一次執行硬解析的目標值在該列中的Selectivity比較大甚至很大,那麼該SQL的Cardinality也是比較大的,在CBO的情況下這種SQLOracle很可能會選擇全表掃描的形式,那麼使用該綁定變量的後續SQL也都是使用這個執行計劃,就會出現嚴重的性能問題。這個現象直到11g以後引入自適應遊標後纔得到緩解,但是相應的也會帶來其他問題(硬解析變多,軟解析尋址變慢,Shared Pool空間緊張)
綁定變量窺探(Bind PeeKing):
前面已經討論到了綁定變量可以大大縮減硬解析次數,給Oracle性能起到了很不錯的幫助,那麼深入討論下去就會發現,在生產中除了主鍵約束列其他的表列或多或少都會有一定的列傾斜情況,對於列傾斜嚴重的列我們可以選擇收集直方圖信息來幫助CBO選擇正確的執行計劃,但是這樣做也就意味着此列開始跟綁定變量see goodbye了,對於列傾斜不嚴重的或者只是有細微列傾斜的目標列使用綁定變量也會有那麼一個問題 - 就是Oracle根本不知道每次傳入的是一個什麼樣的具體值,對於每次傳入值之後Oracle會使用默認的Selectivity和Cardinality,這種的執行計劃顯然是不準確的。這嚴重情況下會造成很大的故障,這個故障對數據庫一定是致命的。Oracle爲解決此問題在9i以後引入了綁定變量窺探(Bind Peeking):Oracle在每次使用綁定變量進行硬解析時,都會使用綁定變量做PeeKing這個動作,來具體虧窺探變量中傳入的值,根據傳入的值計算實際的Cardinality。
優點:
綁定變量窺探的優點是顯而易見的:這個PeeKing動作實際上是爲變量值選擇正確的Selectivity和Cardinality,避免使用默認的值,選出正確的執行計劃。
缺點:
由於出現列傾斜問題,這個PeeKing之後雖然解決了大部分變量值選擇正確的執行計劃問題,同時也對不具代表性的變量值產生了錯誤的執行計劃,他會帶來一些列問題- 如果第一次硬解析時傳入的變量在目標列具有代表性,那麼這個過程我們維護人員很難感受的到其中的差別,如果這個SQL的 Shared Cursor 被從Shared Pool中age out了,再次使用該綁定變量傳入的值是不具代表性的值,那麼綁定變量窺探之後就會產生比較高的執行計劃,在後續的變量傳入也會使用此執行計劃。這會嚴重影響數據庫性能,造成比較可怕的性能故障。
在Oracle 10g以後,Oracle會自動收集直方圖信息,Oracle會更清晰的直到數據分佈情況,綁定變量窺探帶來的副作用更加明顯。
通過以下驗證綁定變量窺探,該數據庫開啓綁定變量窺探且沒有使用常規遊標共享。
SYS@PROD1> set linesize 200;
SYS@PROD1> col name for a40;
SYS@PROD1> col value for a10;
SYS@PROD1> select nam.ksppinm NAME, val.KSPPSTVL VALUE from x$ksppi nam, x$ksppsv val
2 where nam.indx = val.indx and nam.ksppinm ='_optim_peek_user_binds';
NAME VALUE
---------------------------------------- ----------
_optim_peek_user_binds TRUE
SYS@PROD1> show parameter Cursor_sharing
NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
Cursor_sharing string EXACT
SYS@PROD1> create table CAP_1 as select * from dba_objects;
Table created.
SYS@PROD1> create index ind_id on cap_1(object_id);
Index created.
SYS@PROD1> exec dbms_stats.gather_table_stats(ownname=>'sys',tabname=>'cap_1',estimate_percent=>100,cascade=>true,method_opt=>'for all columns size 1',no_invalidate =>false);
PL/SQL procedure successfully completed.
先看兩條SQL和執行計劃,可以看到分別使用了索引範圍掃描和索引快速全掃描
SYS@PROD1> select count(*) from cap_1 where object_id between 999 and 1000;
COUNT(*)
----------
2
SYS@PROD1> select count(*) from cap_1 where object_id between 999 and 60000;
COUNT(*)
----------
58180
SYS@PROD1> set linesize 200;
SYS@PROD1> col sql_text for a80;
SYS@PROD1> select sql_id,version_count,sql_text from v$sqlarea where sql_text like 'select count(*) from cap_1%';
SQL_ID VERSION_COUNT SQL_TEXT
------------- ------------- --------------------------------------------------------------------------------
4k1x0ubh7kpvg 1 select count(*) from cap_1 where object_id between 999 and 1000
gq0d20hmcr1g9 1 select count(*) from cap_1 where object_id between 999 and 60000
SYS@PROD1> select * from table(dbms_xplan.display_Cursor('&sql_id',null,'advanced'));
Enter value for sql_id: 4k1x0ubh7kpvg
old 1: select * from table(dbms_xplan.display_Cursor('&sql_id',null,'advanced'))
new 1: select * from table(dbms_xplan.display_Cursor('4k1x0ubh7kpvg',null,'advanced'))
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID 4k1x0ubh7kpvg, Child number 0
-------------------------------------
select count(*) from cap_1 where object_id between 999 and 1000
Plan hash value: 3666266488
----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 2 (100)| |
| 1 | SORT AGGREGATE | | 1 | 5 | | |
|* 2 | INDEX RANGE SCAN| IND_ID | 3 | 15 | 2 (0)| 00:00:01 |
----------------------------------------------------------------------------
Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------
1 - SEL$1
2 - SEL$1 / CAP_1@SEL$1
Outline Data
-------------
/*+
BEGIN_OUTLINE_DATA
IGNORE_OPTIM_EMBEDDED_HINTS
OPTIMIZER_FEATURES_ENABLE('11.2.0.1')
DB_VERSION('11.2.0.1')
ALL_ROWS
OUTLINE_LEAF(@"SEL$1")
INDEX(@"SEL$1" "CAP_1"@"SEL$1" ("CAP_1"."OBJECT_ID"))
END_OUTLINE_DATA
*/
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("OBJECT_ID">=999 AND "OBJECT_ID"<=1000)
Column Projection Information (identified by operation id):
-----------------------------------------------------------
1 - (#keys=0) COUNT(*)[22]
44 rows selected.
SYS@PROD1> select * from table(dbms_xplan.display_Cursor('&sql_id',null,'advanced'));
Enter value for sql_id: gq0d20hmcr1g9
old 1: select * from table(dbms_xplan.display_Cursor('&sql_id',null,'advanced'))
new 1: select * from table(dbms_xplan.display_Cursor('gq0d20hmcr1g9',null,'advanced'))
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID gq0d20hmcr1g9, Child number 0
-------------------------------------
select count(*) from cap_1 where object_id between 999 and 60000
Plan hash value: 4170857810
--------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
--------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 46 (100)| |
| 1 | SORT AGGREGATE | | 1 | 5 | | |
|* 2 | INDEX FAST FULL SCAN| IND_ID | 54617 | 266K| 46 (3)| 00:00:01 |
--------------------------------------------------------------------------------
Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------
1 - SEL$1
2 - SEL$1 / CAP_1@SEL$1
Outline Data
-------------
/*+
BEGIN_OUTLINE_DATA
IGNORE_OPTIM_EMBEDDED_HINTS
OPTIMIZER_FEATURES_ENABLE('11.2.0.1')
DB_VERSION('11.2.0.1')
ALL_ROWS
OUTLINE_LEAF(@"SEL$1")
INDEX_FFS(@"SEL$1" "CAP_1"@"SEL$1" ("CAP_1"."OBJECT_ID"))
END_OUTLINE_DATA
*/
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter(("OBJECT_ID"<=60000 AND "OBJECT_ID">=999))
Column Projection Information (identified by operation id):
-----------------------------------------------------------
1 - (#keys=0) COUNT(*)[22]
44 rows selected.
使用遊標之後進一步查看綁定變量窺探
SYS@PROD1> var x number;
SYS@PROD1> var y number;
SYS@PROD1> exec :x :=999;
PL/SQL procedure successfully completed.
SYS@PROD1> exec :y :=1000;
PL/SQL procedure successfully completed.
SYS@PROD1> select count(*) from cap_1 where object_id between :x and :y;
COUNT(*)
----------
2
SYS@PROD1> exec :y :=60000;
PL/SQL procedure successfully completed.
SYS@PROD1> select count(*) from cap_1 where object_id between :x and :y;
COUNT(*)
----------
58180
可以看到Oracle在執行第一條SQL時發生硬解析,自動觸發綁定變量窺探,獲取實際的Selectivity掃描方式爲INDEX RANGE SCAN,在之後的SQL中直接使用該執行計劃不再進行綁定變量窺探。
SYS@PROD1> set linesize 200;
SYS@PROD1> col sql_text for a80;
SYS@PROD1> select sql_id,version_count,sql_text from v$sqlarea where sql_text like 'select count(*) from cap_1%';
SQL_ID VERSION_COUNT SQL_TEXT
------------- ------------- --------------------------------------------------------------------------------
a8nddr1sh1qds 1 select count(*) from cap_1 where object_id between :x and :y
SYS@PROD1> set pagesize 1000;
SYS@PROD1> select * from table(dbms_xplan.display_Cursor('&sql_id',null,'advanced'));
Enter value for sql_id: a8nddr1sh1qds
old 1: select * from table(dbms_xplan.display_Cursor('&sql_id',null,'advanced'))
new 1: select * from table(dbms_xplan.display_Cursor('a8nddr1sh1qds',null,'advanced'))
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID a8nddr1sh1qds, Child number 0
-------------------------------------
select count(*) from cap_1 where object_id between :x and :y
Plan hash value: 916475704
-----------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
-----------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 2 (100)| |
| 1 | SORT AGGREGATE | | 1 | 5 | | |
|* 2 | FILTER | | | | | |
|* 3 | INDEX RANGE SCAN| IND_ID | 3 | 15 | 2 (0)| 00:00:01 |
-----------------------------------------------------------------------------
Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------
1 - SEL$1
3 - SEL$1 / CAP_1@SEL$1
Outline Data
-------------
/*+
BEGIN_OUTLINE_DATA
IGNORE_OPTIM_EMBEDDED_HINTS
OPTIMIZER_FEATURES_ENABLE('11.2.0.1')
DB_VERSION('11.2.0.1')
ALL_ROWS
OUTLINE_LEAF(@"SEL$1")
INDEX(@"SEL$1" "CAP_1"@"SEL$1" ("CAP_1"."OBJECT_ID"))
END_OUTLINE_DATA
*/
Peeked Binds (identified by position):
--------------------------------------
1 - :X (NUMBER): 999
2 - :Y (NUMBER): 1000
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter(:X<=:Y)
3 - access("OBJECT_ID">=:X AND "OBJECT_ID"<=:Y)
Column Projection Information (identified by operation id):
-----------------------------------------------------------
1 - (#keys=0) COUNT(*)[22]
52 rows selected.
SYS@PROD1> alter system flush shared_pool; --這裏模擬執行計劃被age out 出Shared Pool,生產環境謹慎操作!!!
System altered.
SYS@PROD1> var x number;
SYS@PROD1> var y number;
SYS@PROD1> exec :x :=999;
PL/SQL procedure successfully completed.
SYS@PROD1> exec :y :=60000;
PL/SQL procedure successfully completed.
SYS@PROD1> select count(*) from cap_1 where object_id between :x and :y;
COUNT(*)
----------
58180
SYS@PROD1> exec :y :=1000;
PL/SQL procedure successfully completed.
SYS@PROD1> select count(*) from cap_1 where object_id between :x and :y;
COUNT(*)
----------
2
可以發現,模擬執行計劃被age out出Shared Pool之後,先執行:y=60000的SQL之後,再次觸發綁定變量窺探,使用實際的Selectivity。
SYS@PROD1> select sql_id,version_count,sql_text from v$sqlarea where sql_text like 'select count(*) from cap_1%';
SQL_ID VERSION_COUNT SQL_TEXT
------------- ------------- --------------------------------------------------------------------------------
a8nddr1sh1qds 1 select count(*) from cap_1 where object_id between :x and :y
SYS@PROD1> select * from table(dbms_xplan.display_Cursor('&sql_id',null,'advanced'));
Enter value for sql_id: a8nddr1sh1qds
old 1: select * from table(dbms_xplan.display_Cursor('&sql_id',null,'advanced'))
new 1: select * from table(dbms_xplan.display_Cursor('a8nddr1sh1qds',null,'advanced'))
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID a8nddr1sh1qds, Child number 0
-------------------------------------
select count(*) from cap_1 where object_id between :x and :y
Plan hash value: 2157405733
---------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
---------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 46 (100)| |
| 1 | SORT AGGREGATE | | 1 | 5 | | |
|* 2 | FILTER | | | | | |
|* 3 | INDEX FAST FULL SCAN| IND_ID | 54617 | 266K| 46 (3)| 00:00:01 |
---------------------------------------------------------------------------------
Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------
1 - SEL$1
3 - SEL$1 / CAP_1@SEL$1
Outline Data
-------------
/*+
BEGIN_OUTLINE_DATA
IGNORE_OPTIM_EMBEDDED_HINTS
OPTIMIZER_FEATURES_ENABLE('11.2.0.1')
DB_VERSION('11.2.0.1')
ALL_ROWS
OUTLINE_LEAF(@"SEL$1")
INDEX_FFS(@"SEL$1" "CAP_1"@"SEL$1" ("CAP_1"."OBJECT_ID"))
END_OUTLINE_DATA
*/
Peeked Binds (identified by position):
--------------------------------------
1 - :X (NUMBER): 999
2 - :Y (NUMBER): 60000
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter(:X<=:Y)
3 - filter(("OBJECT_ID"<=:Y AND "OBJECT_ID">=:X))
Column Projection Information (identified by operation id):
-----------------------------------------------------------
1 - (#keys=0) COUNT(*)[22]
52 rows selected.
SYS@PROD1>
綁定變量分級(Bind Graduation):
Oracle會對PL/SQL中的文本類型的綁定變量定義的長度做了分級,不同長度的綁定變量在Library Cache分配的內存是不一樣的。如果綁定變量長度發生改變,SQL文本長度也就發生了改變,由於Child Cursor存放的解析樹和執行計劃的同時還存放了SQL文本,這就意味着一旦綁定變量長度發生改變,那麼其對應的解析樹和執行計劃也不能被使用,就會重新解析該語句。
等級:
第一等級:定義長度在32 byte以內的文本型綁定變量,對應的該等級綁定變量會分配32字節的內存空間。
第二等級:定義長度在33 - 128 byte的文本型綁定變量,對應的該等級綁定變量會分配128字節的內存空間。
第三等級:定義長度在129 - 2000 byte的文本型綁定變量,對應的該等級綁定變量會分配2000字節的內存空間。
第四等級:定義長度在2000 byte以上的文本型綁定變量,對應的該等級綁定變量會根據傳入的實際綁定變量的值分配內存空間。
遊標共享(Cursor sharing):
在產品開發階段,開發可能並不會注意到SQL語句帶來的重大影響,就會對一些SQL不適用綁定變量,在產品上線以後就會對數據庫造成很大的壓力
常規遊標共享:
如上所述,面臨這樣的問題如果更改代碼那是相當耗時的,在Oracle 8i中引入了常規遊標共享(:"SYS_B_n"(n=0,1,2,3......)),在不改變應用代碼的情況下,開啓常規遊標共享就會使用遊標替換where子句中的具體值。
例如一條SQL“select * from emo where empno=7369”在開啓了常規遊標共享之後,Oracle就會替換改寫成“select * from emo where empno=:"SYS_B_0"”
EXACT:Cursor_sharding的默認值,Oracle不會使用系統產生的綁定變量來替換目標SQL的文本中where條件或values子句中的具體值。
SIMILAR:Oracle會使用系統產生的綁定變量替換目標SQL的文本中where條件或values子句中的具體值。在Oracle 11gR1(引入自適應遊標)之前這裏的綁定變量替換僅適用於安全的謂詞條件(where字句中等式連接的目標值條件)對於不安全的謂詞條件(where字句使用了範圍查詢的條件)不會進行替換,即使SQL文本中顯示了系統綁定變量其對應的解析樹和執行計劃也不能共享。
FORCE:Oracle會無條件的使用系統產生的綁定變量替換目標SQL的文本中的where條件或values子句中的具體值,不在區分安全的謂詞條件和不安全的謂詞條件。這種行爲在11g以後引入自適應遊標後不在被使用。
SYS@PROD1> show parameter Cursor_sharing
NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
Cursor_sharing string EXACT
通過以下驗證可以發現,開啓常規遊標共享之後,所有常量在Child Cursor均已係統遊標的形式存放
SYS@PROD1> alter system set Cursor_sharing=similar;
System altered.
SYS@PROD1> show parameter Cursor_sharing;
NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
Cursor_sharing string SIMILAR
SYS@PROD1> select count(*) from cap_1 where object_id between 1 and 3;
COUNT(*)
----------
2
SYS@PROD1> select sql_id,version_count,sql_text from v$sqlarea where sql_text like 'select count(*) from cap_1%';
SQL_ID VERSION_COUNT SQL_TEXT
------------- ------------- --------------------------------------------------------------------------------
a8nddr1sh1qds 1 select count(*) from cap_1 where object_id between :x and :y
5h2x4h75ztrau 1 select count(*) from cap_1 where object_id between :"SYS_B_0" and :"SYS_B_1"
SYS@PROD1> select count(*) from cap_1 where object_id between 1 and 2;
COUNT(*)
----------
1
由於在常規遊標中對於where字句中的範圍條件均被視爲不安全謂詞條件,所以看到的即使使用的遊標依然是硬解析,執行兩次之後產生了兩個Child Cursor。這種問題在11gR2引入自適應遊標之後得到一定的緩解,在下面我們會繼續討論自適應遊標
SYS@PROD1> select sql_id,version_count,sql_text from v$sqlarea where sql_text like 'select count(*) from cap_1%';
SQL_ID VERSION_COUNT SQL_TEXT
------------- ------------- --------------------------------------------------------------------------------
a8nddr1sh1qds 1 select count(*) from cap_1 where object_id between :x and :y
5h2x4h75ztrau 2 select count(*) from cap_1 where object_id between :"SYS_B_0" and :"SYS_B_1"
SYS@PROD1> select plan_hash_value,sql_id from v$sql where sQL_id='5h2x4h75ztrau';
PLAN_HASH_VALUE SQL_ID
--------------- -------------
916475704 5h2x4h75ztrau
916475704 5h2x4h75ztrau
而對於等式條件這種安全謂詞條件,是完全可以使用常規遊標共享的,執行多次之後仍然共享一條執行計劃。
SYS@PROD1> select count(*) from cap_1 where object_id=1;
COUNT(*)
----------
0
SYS@PROD1> select count(*) from cap_1 where object_id=3;
COUNT(*)
----------
1
SYS@PROD1> select sql_id,version_count,sql_text from v$sqlarea where sql_text like 'select count(*) from cap_1%';
SQL_ID VERSION_COUNT SQL_TEXT
------------- ------------- --------------------------------------------------------------------------------
a8nddr1sh1qds 1 select count(*) from cap_1 where object_id between :x and :y
5h2x4h75ztrau 2 select count(*) from cap_1 where object_id between :"SYS_B_0" and :"SYS_B_1"
0s45fj0v5ba2p 1 select count(*) from cap_1 where object_id=:"SYS_B_0"
自適應遊標共享(Adaptive Cursor Sharing):
誠如上述所述,使用綁定變量窺探之後會帶來一些列的副作用,尤其是Oracle 10g以後這個副作用更加明顯。爲了解決綁定變量窺探帶來的副作用,Oracle11g引入了自適應遊標共享。
自適應遊標就是在使用綁定變量窺探的前提下,能夠使目標SQL在多個執行計劃之間自適應的選擇出合適的執行計劃。其工作方式是在第一次通過硬解析產生一個執行計劃,當在之後的SQL運行時會自動記錄每一條SQL的runtime信息,並與上一條SQLruntime信息作比對,如果有較大的偏差,那麼就會重新硬解析產生一個新的執行計劃。
自適應遊標共享第一步就是擴展遊標共享(Extended Cursor sharing),將目標SQL對應的Child Cursor標記爲Bind Sensitive(Oracle認爲含某個變量的目標SQL的執行計劃可能會隨着變量值變化而變化)
條件:①啓動了綁定變量窺探
②該SQL使用了綁定變量(系統綁定變量和SQL自定義綁定變量)
③該SQL使用的是不安全的謂詞條件(範圍查詢,有直方圖信息列的等值查詢)
自適應遊標共享第二步是將目標SQL對應的Child Cursor標記爲Bind Aware(Oracle已經確定含某個變量的目標SQL的執行計劃會隨着變量值變化而變化)
條件:①該SQL所對應的Child Cursor已經在之前標記爲Bind Sensitive。
②該SQL在接下來兩次執行時所對應的的runtime和之前硬解析產生的runtime有較大偏差。
自適應遊標的整體執行流程大致應該是這樣的:
①當目標SQL第一次執行,Oracle會硬解析根據目標表的一些列信息(如統計信息,直方圖信息,還有參數信息等)來判斷是否想目標SQL對應的Child Cursor標記爲 Bind Sensitive,對於標記爲 Bind Sensitive的Child Cursor,Oracle會吧執行該SQL所對應的的runtime統計信息額外的存儲到Child Cursor中。
②目標SQL第二次執行時,Oracle會使用軟解析,重用第一次硬解析的解析樹和執行計劃。
③當目標SQL第三次執行時,若該SQL所對應的的Child Cursor已經被標記爲Bind Sensitive,同時第二次和第三次執行該SQL時所記錄的runtime統計信息和該SQL第一次實行時所記錄的runtime統計信息由較大差異,則該SQL第三次被執行時就會使用硬解析,此時Oracle就會產生一個新的執行計劃,對應的Child Cursor應當在原來Parent Cursor下,並且這個新的Child Cursor標記爲Bind Aware
④對於標記爲Bind Aware的Child Cursor所對應的目標SQL,當該SQL再被執行時,Oracle就會根據當前傳入的綁定變量值所對應的謂詞選擇率來決定使用硬解析還是軟解析/軟軟解析。遵循的原則:當前傳入的綁定變量值所在的謂詞條件的可選擇率處於該說完了之前硬解析時同名謂詞條件在V$SQL_CS_STATISTICS中記錄的可選擇率範圍之內,使用軟解析/軟軟解析,並重用之前Child Cursor中存儲的解析樹和執行計劃,反之是硬解析。
首先確定當前開啓自適應遊標並且常規遊標爲默認值,經過一下驗證自適應遊標
SYS@PROD1> select nam.ksppinm NAME, val.KSPPSTVL VALUE from x$ksppi nam, x$ksppsv val
2 where nam.indx = val.indx and nam.ksppinm like '_optimizer_%_Cursor_sharing%';
NAME VALUE
---------------------------------------- ----------
_optimizer_extended_Cursor_sharing UDO
_optimizer_extended_Cursor_sharing_rel SIMPLE
_optimizer_adaptive_Cursor_sharing TRUE
SYS@PROD1> alter system set Cursor_sharing=exact;
System altered.
SYS@PROD1> show parameter Cursor_sharing;
NAME TYPE VALUE
------------------------------------ ----------- ------------------------------
Cursor_sharing string EXACT
SYS@PROD1> create index ind_type on cap_1(object_type);
Index created.
手工更改一些數據,產生較大的列傾斜,方便驗證數據
SYS@PROD1> update cap_1 set object_type='TABLE' where rownum <60002;
60001 rows updated.
SYS@PROD1> update cap_1 set object_type='CLUSTER' where rownum <100;
99 rows updated.
SYS@PROD1> commit;
Commit complete.
SYS@PROD1> exec dbms_stats.gather_table_stats(ownname=>'sys',tabname=>'cap_1',estimate_percent=>100,cascade=>true,method_opt=>'for all columns size auto',no_invalidate =>false);
PL/SQL procedure successfully completed.
收集完直方圖之後查看直方圖信息
SYS@PROD1> select column_name,num_buckets,histogram from dba_tab_col_statistics where table_name='CAP_1' and column_name='OBJECT_TYPE';
COLUMN_NAME NUM_BUCKETS HISTOGRAM
------------------------------ ----------- ---------------
OBJECT_TYPE 30 FREQUENCY
SYS@PROD1> alter system flush shared_pool; --生產環境謹慎操作!!! 消除其他的影響因素
System altered.
SYS@PROD1> var x varchar2(30);
SYS@PROD1> exec :x :='CLUSTER';
PL/SQL procedure successfully completed.
SYS@PROD1> select count(*) from cap_1 where object_type=:x;
COUNT(*)
----------
99
第一次執行語句,硬解析產生執行計劃,Child Cursor標記爲Bind Sensitive,並在Child Cursor中記錄runtime
SYS@PROD1> select sql_id,version_count,sql_text from v$sqlarea where sql_text like 'select count(*) from cap_1%';
SQL_ID VERSION_COUNT SQL_TEXT EXECUTIONS
------------- ------------- -------------------------------------------------------------------------------- ----------
d76x20r4px2bk 1 select count(*) from cap_1 where object_type=:x 1
SYS@PROD1> col is_bind_sensitive for a20;
SYS@PROD1> col is_bind_aware for a20;
SYS@PROD1> col is_shareable for a20;
SYS@PROD1> select Child_number,executions,buffer_gets,is_bind_sensitive,is_bind_aware,is_shareable,plan_hash_value from v$sql where sql_id='d76x20r4px2bk';
Child_NUMBER EXECUTIONS BUFFER_GETS IS_BIND_SENSITIVE IS_BIND_AWARE IS_SHAREABLE PLAN_HASH_VALUE
------------ ---------- ----------- -------------------- -------------------- -------------------- ---------------
0 1 50 Y N Y 3375217814
此時的執行計劃是通過綁定變量窺探產生的貼近實際的執行計劃
SYS@PROD1> select * from table(dbms_xplan.display_Cursor('&sql_id',null,'advanced'));
Enter value for sql_id: d76x20r4px2bk
old 1: select * from table(dbms_xplan.display_Cursor('&sql_id',null,'advanced'))
new 1: select * from table(dbms_xplan.display_Cursor('d76x20r4px2bk',null,'advanced'))
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID d76x20r4px2bk, Child number 0
-------------------------------------
select count(*) from cap_1 where object_type=:x
Plan hash value: 3375217814
------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 3 (100)| |
| 1 | SORT AGGREGATE | | 1 | 7 | | |
|* 2 | INDEX RANGE SCAN| IND_TYPE | 99 | 693 | 3 (0)| 00:00:01 |
------------------------------------------------------------------------------
Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------
1 - SEL$1
2 - SEL$1 / CAP_1@SEL$1
Outline Data
-------------
/*+
BEGIN_OUTLINE_DATA
IGNORE_OPTIM_EMBEDDED_HINTS
OPTIMIZER_FEATURES_ENABLE('11.2.0.1')
DB_VERSION('11.2.0.1')
ALL_ROWS
OUTLINE_LEAF(@"SEL$1")
INDEX(@"SEL$1" "CAP_1"@"SEL$1" ("CAP_1"."OBJECT_TYPE"))
END_OUTLINE_DATA
*/
Peeked Binds (identified by position):
--------------------------------------
1 - :X (VARCHAR2(30), CSID=873): 'CLUSTER'
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("OBJECT_TYPE"=:X)
Column Projection Information (identified by operation id):
-----------------------------------------------------------
1 - (#keys=0) COUNT(*)[22]
49 rows selected.
變量傳入其他值繼續第二遍執行
SYS@PROD1> exec :x :='TABLE';
PL/SQL procedure successfully completed.
SYS@PROD1> select count(*) from cap_1 where object_type=:x;
COUNT(*)
----------
61313
通過以下驗證可以發現第二遍執行Oracle會使用遊標共享方式進行軟解析,但是由於綁定變量傳入值和之前第一次傳入值的結果集存在較大的差距,此時可以看到BUFFER_GETS從50猛增到304,這是一個很正常的現象,因爲此時所選用的執行計劃已經不是合適的執行計劃了,此時Child Cursor仍然被標記爲Bind Sensitive,並記錄runtime到Child Cursor。
SYS@PROD1> select sql_id,version_count,sql_text,executions from v$sqlarea where sql_text like 'select count(*) from cap_1%';
SQL_ID VERSION_COUNT SQL_TEXT EXECUTIONS
------------- ------------- -------------------------------------------------------------------------------- ----------
d76x20r4px2bk 1 select count(*) from cap_1 where object_type=:x 2
SYS@PROD1> select Child_number,executions,buffer_gets,is_bind_sensitive,is_bind_aware,is_shareable,plan_hash_value from v$sql where sql_id='d76x20r4px2bk';
Child_NUMBER EXECUTIONS BUFFER_GETS IS_BIND_SENSITIVE IS_BIND_AWARE IS_SHAREABLE PLAN_HASH_VALUE
------------ ---------- ----------- -------------------- -------------------- -------------------- ---------------
0 2 304 Y N Y 3375217814
SYS@PROD1> select Child_number,predicate,range_id,low,high from v$sql_cs_Selectivity where sql_id='d76x20r4px2bk';
no rows selected
第三次執行該語句,發現此時Oracle自適應遊標已經發生了改變,由於之前已經將Child Cursor標記爲Bind sensitive了並且連續兩次runtime出現了比較大的差距,觸發Oracle自適應遊標重新進行硬解析再次觸發綁定變量窺探,產生了一個貼近實際的執行計劃,此時Child Cursor標記爲Bind Aware。
SYS@PROD1> select count(*) from cap_1 where object_type=:x;
COUNT(*)
----------
61313
SYS@PROD1> select sql_id,version_count,sql_text,executions from v$sqlarea where sql_text like 'select count(*) from cap_1%';
SQL_ID VERSION_COUNT SQL_TEXT EXECUTIONS
------------- ------------- -------------------------------------------------------------------------------- ----------
d76x20r4px2bk 2 select count(*) from cap_1 where object_type=:x 3
SYS@PROD1> select Child_number,executions,buffer_gets,is_bind_sensitive,is_bind_aware,is_shareable,plan_hash_value from v$sql where sql_id='d76x20r4px2bk';
Child_NUMBER EXECUTIONS BUFFER_GETS IS_BIND_SENSITIVE IS_BIND_AWARE IS_SHAREABLE PLAN_HASH_VALUE
------------ ---------- ----------- -------------------- -------------------- -------------------- ---------------
0 2 304 Y N Y 3375217814
1 1 455 Y Y Y 549666362
發現此時該sql_id下面已經出現了兩個Child Cursor,硬解析新生成的執行計劃使用INDEX FAST FULL SCAN掃描目標表
SYS@PROD1> select * from table(dbms_xplan.display_Cursor('&sql_id',null,'advanced'));
Enter value for sql_id: d76x20r4px2bk
old 1: select * from table(dbms_xplan.display_Cursor('&sql_id',null,'advanced'))
new 1: select * from table(dbms_xplan.display_Cursor('d76x20r4px2bk',null,'advanced'))
PLAN_TABLE_OUTPUT
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
SQL_ID d76x20r4px2bk, Child number 0
-------------------------------------
select count(*) from cap_1 where object_type=:x
Plan hash value: 3375217814
------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 3 (100)| |
| 1 | SORT AGGREGATE | | 1 | 7 | | |
|* 2 | INDEX RANGE SCAN| IND_TYPE | 99 | 693 | 3 (0)| 00:00:01 |
------------------------------------------------------------------------------
Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------
1 - SEL$1
2 - SEL$1 / CAP_1@SEL$1
Outline Data
-------------
/*+
BEGIN_OUTLINE_DATA
IGNORE_OPTIM_EMBEDDED_HINTS
OPTIMIZER_FEATURES_ENABLE('11.2.0.1')
DB_VERSION('11.2.0.1')
ALL_ROWS
OUTLINE_LEAF(@"SEL$1")
INDEX(@"SEL$1" "CAP_1"@"SEL$1" ("CAP_1"."OBJECT_TYPE"))
END_OUTLINE_DATA
*/
Peeked Binds (identified by position):
--------------------------------------
1 - :X (VARCHAR2(30), CSID=873): 'CLUSTER'
Predicate Information (identified by operation id):
---------------------------------------------------
2 - access("OBJECT_TYPE"=:X)
Column Projection Information (identified by operation id):
-----------------------------------------------------------
1 - (#keys=0) COUNT(*)[22]
SQL_ID d76x20r4px2bk, Child number 1
-------------------------------------
select count(*) from cap_1 where object_type=:x
Plan hash value: 549666362
----------------------------------------------------------------------------------
| Id | Operation | Name | Rows | Bytes | Cost (%CPU)| Time |
----------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | | | 81 (100)| |
| 1 | SORT AGGREGATE | | 1 | 7 | | |
|* 2 | INDEX FAST FULL SCAN| IND_TYPE | 61313 | 419K| 81 (0)| 00:00:01 |
----------------------------------------------------------------------------------
Query Block Name / Object Alias (identified by operation id):
-------------------------------------------------------------
1 - SEL$1
2 - SEL$1 / CAP_1@SEL$1
Outline Data
-------------
/*+
BEGIN_OUTLINE_DATA
IGNORE_OPTIM_EMBEDDED_HINTS
OPTIMIZER_FEATURES_ENABLE('11.2.0.1')
DB_VERSION('11.2.0.1')
ALL_ROWS
OUTLINE_LEAF(@"SEL$1")
INDEX_FFS(@"SEL$1" "CAP_1"@"SEL$1" ("CAP_1"."OBJECT_TYPE"))
END_OUTLINE_DATA
*/
Peeked Binds (identified by position):
--------------------------------------
1 - :X (VARCHAR2(30), CSID=873): 'TABLE'
Predicate Information (identified by operation id):
---------------------------------------------------
2 - filter("OBJECT_TYPE"=:X)
Column Projection Information (identified by operation id):
-----------------------------------------------------------
1 - (#keys=0) COUNT(*)[22]
98 rows selected.
此時已經可以看到新生成的Child Cursor標記爲Bind Aware了
SYS@PROD1> select Child_number,predicate,range_id,low,high from v$sql_cs_Selectivity where sql_id='d76x20r4px2bk';
Child_NUMBER PREDICATE RANGE_ID LOW HIGH
------------ ---------------------------------------- ---------- ---------- ----------
1 =X 0 0.761484 0.930703
缺陷:
①增加了額外的硬解析
②可能會增加Parent Cursor下的Child Cursor數量,會增加軟解析和軟軟解析查找匹配Child Cursor的工作量
③額外的Child會增加Shared Pool在存儲空間方面的壓力
④自適應遊標每次改變執行計劃都需要比對連續兩次的runtime,故自適應遊標只能在一定程度上彌補了綁定變量窺探帶來的副作用,並不能完美的消除此隱患。
禁用自適應遊標:
①將隱含參數_OPTIMIZER_EXTENDED_CURSOR_SHARING和_OPTIMIZER_EXTENDED_CURSOR_SHARING_REL設置爲NONE,這樣就關閉了可擴展遊標共享,不會將目標SQL對應的Child Cursor標記爲Bind Sensitive。
②將隱含參數_OPTIMIZER_ADAPTIVE_CURSOR_SHARING設置爲FALSE,所有的Child Cursor都不能標記爲Bind Aware。
總結:
Oracle一條語句執行過程
①客戶端通過監聽連接數據庫首先產生一個user process 連接服務端的server process,在Oracle產生server process時會同時在pga中分配進程內存,這就是 server process pga,之後該會話連接的所有排序和解析等操作都是通過此pga區域完成。user process 連接server process以後會與instance 建立connect。
②當Oracle客戶端發出一條SQL,首先通過監聽將指令傳入到instance中,在shared pool中的dictionary cache中進行對語句的語法 數據字典,目標表視圖和表列進行校驗。語法語義校驗之後會在library cache中進一步執行。
③首先在server process pga中查找session cursor,如果有可用的session cursor則直接用此遊標共享執行計劃--軟軟解析,但是首次連接會話,pga中沒有session Process 這個過程遍歷完之後直接進行下一步。
④在library cache中產生library cache latch 在library cache中bucket中遍歷parent cursor,並在parent cursor根據SQL文本查找對應的child cursor,若能找到對應的child curosr,那麼就不需要在分配內存解析執行計劃了--軟解析,若沒有找到對應的child curosr,就會進行下一步分配內存解析執行計劃。
⑤釋放library cache latch 在shared pool中生成一個shared pool latch ,在pga中通過CBO進行查詢轉換成等價的簡單SQL,之後再解析生成執行計劃,並將產生的執行計劃存放到 library cache中對應的child curosr(新產生一對 shared cursor),然後釋放shared pool latch在library cache 再次產生library cache latch,遍歷執行child curosr(執行④步驟)--硬解析。
⑥一旦找到了解析樹和執行計劃下一步就是從buffer cache找到目標表數據塊是否有緩存,並讀取到buffer cache中。首先在Buffer cache中的Hash Bucket中查找對應數據塊對應的bucket header,若找到對應的bucket則直接操作buffer cache中的緩存快,若沒有則通過LRU機制釋放部分buffer 和LRU List 對應的bucket,然後將block讀取到buffer cache中,讀取buffer cache中目標數據塊。
若需要更改就需要做以下操作
1>檢查該目標數據塊是否有latch,若存在則需要等待或者通知持有者釋放latch。
2>在shared pool中對應事物申請一個內存區域,並且在Log Buffer中申請一個subLog Buffer ,用於將更改記錄寫入log buffer。
3>在shared pool中對應事物申請一個內存區域,讀取undo segment 到shared pool並映射到buffercache中,用於存放更改數據的before image。
⑦若是在RAC環境中在查找Buffer Cache中的數據塊前首先要報告master node,master node通知持有該目標塊的節點釋放鎖或者對鎖降級,然後通知當前節點讀取目標數據塊進行⑥步驟。
⑧在更改完之後用戶commit提交之後,需要做一下操作:
1>sublog buffer中的數據寫入到online log.
2>在buffer cache中的更改數據行標記爲已更改未寫入數據塊。
3>undo segment中的狀態標記爲inactive,表示事物已提交。
4>在達到觸發條件之後觸發增量檢查點將 checkpind scn寫入數據塊,此時並沒有將數據塊數據完全完成更改,在buffer cache中的LRU移除之前將所有更改過的數據寫入數據塊,完成整個操作。
在實際生產環境中,這個過程受不同環境,不同數據庫版本,以及不同的參數配置影響會有不同,在commit之後也會受實際生產環境的buffer cache大小 io性能,實際執行會有不同。
--- End