自從16年以後,由於工做緣由,項目中就沒有再使用oracle了,最近最近支持一個項目,又要開始負責這塊事情了。最近在跑性能測試,配置所有調好以後,很多sql還存在性能低下的問題,主要涉及執行計劃的不合理,以及相關pga隱含參數的優化。可能由於幾年不接觸的緣由,略微有些生疏須要review了。這裏以最近優化過的某個比較典型的例子爲例(這裏只講思路、由於涉及到敏感信息,不給出最終結果,16C E5620@2.40GHz/45GB內存/fio 85/15 iops 8500/1500 配置,優化前100G臨時空間都爆掉,到20分鐘出結果,實際上容許徹底調整的話,最多5分鐘就能夠運行出來),這個例子比較經典,它是一個產品轉換的例子,從業務邏輯上來看,它須要對兩個100w(100管理人*1w支產品)記錄的結果集進行黑名單(是指A產品->B產品是否容許轉換,A->B不容許,不意味着B->A不容許)之外的笛卡爾積關聯,,重點回顧下一些重要的operation以及如何經過hint強制,子查詢獨立運行時的operation和做爲主查詢的子查詢運行時的operation不相同可能會形成性能差別巨大。首先,不要期望寫一段sql,既能夠在oracle下運行、也能夠在mysql下運行。原始的sql語句以及執行計劃以下:html
INSERT INTO C3( tenantid,c_code,l_serialno,c_txtfundcode, c_chargetype,f_sharemin,f_sharemax,l_hold,c_txtothercode, c_targetchargetype,c_custtype,c_flag,d_operatedate,d_cdate,c_cyno) SELECT a.tenantid,a.c_code,l_serialno, CASE a.agencytypes WHEN 'AB' THEN a.c_prdcode ELSE COALESCE(b.c_outprdcode,a.c_prdcode) END c_txtfundcode, c_chargetype,f_sharemin,f_sharemax,l_hold, CASE a.agencytypes WHEN 'AB'THEN a.c_othercode ELSE COALESCE(c.c_outprdcode,a.c_othercode) END c_txtothercode, c_targetchargetype,c_custtype,c_flag,d_operatedate,20100511 d_cdate,a.c_cyno FROM (SELECT 0 l_serialno, CASE t.c_mutextype WHEN 'A' THEN '0' WHEN 'B' THEN '1' WHEN 'C' THEN '2' ELSE t.c_mutextype END c_chargetype, CASE t.c_targetsharetype WHEN 'A' THEN '0' WHEN 'B' THEN '1' WHEN 'C' THEN '2' ELSE t.c_targetsharetype END c_targetchargetype, ar.f_cminmutex f_sharemin,99999999999999.9 f_sharemax,0 l_hold,'2' c_custtype, '1' c_flag,'20000101' d_operatedate, t.* FROM (SELECT fo.tenantid, fo.c_code, fo.c_prdcode,fi.c_prdcode c_othercode, fo.c_cyno,fo.c_mutextype,fi.c_mutextype c_targetsharetype, fo.d_contractdate d_contractdate, fo.c_mutextypes c_mutextypes, fo.agencytypes, fi.d_contractdate d_othercontractdate, fi.c_mutextypes c_othersharetypes FROM (SELECT DISTINCT ss.tenantid, ss.c_code, ss.c_prdcode, ss.c_cyno,ss.c_mutextype c_mutextype, ta.c_mutextypes agencytypes, getsysvalue(ss.tenantid, ss.c_code, ss.c_cyno,ss.c_managercode, ss.c_prdcode,'ContractDate', '20991231') d_contractdate, getsysvalue(ss.tenantid, ss.c_code, ss.c_cyno,ss.c_managercode, ss.c_prdcode,'ShareTypes', '') c_mutextypes FROM (SELECT fi.tenantid, fi.c_code, fi.c_prdcode,c.c_cyno, s.c_mutextype, fi.c_managercode FROM FUNDINFO fi, (SELECT 'A' c_mutextype FROM DUAL UNION ALL SELECT 'B' c_mutextype FROM DUAL UNION ALL SELECT 'C' c_mutextype FROM DUAL) s, QUALIFY c, EXPBATCH d WHERE fi.c_code = 'F6' AND fi.tenantid = '*' AND fi.c_fundstatus NOT IN ('6', '9') AND c.c_code = 'F6' AND c.tenantid = '*' AND c.c_cyno = d.c_cyno AND c.c_code = d.c_code AND c.tenantid = d.tenantid AND INSTR(fi.c_mutextypes, s.c_mutextype) > 0 AND (fi.c_fundtype<>'1' OR NOT EXISTS(SELECT 1 FROM TEXT_PARAMETER WHERE c_paramitem = 'ChangeLimit' AND c_paramvalue = '1' AND c_prdcode = fi.c_prdcode AND c_code = 'F6' AND tenantid = '*')) AND fi.c_prdcode = c.c_prdcode AND c.c_issaleflag = '1' ) ss, EXPBATCH ta WHERE ss.c_cyno = ta.c_cyno AND ss.c_code = ta.c_code AND ss.tenantid = ta.tenantid AND ta.c_notexpparamfiles = '0' ) fo, (SELECT DISTINCT ss.tenantid, ss.c_code, ss.c_prdcode, ss.c_cyno,ss.c_mutextype c_mutextype, getsysvalue(ss.tenantid, ss.c_code, ss.c_cyno,ss.c_managercode, ss.c_prdcode,'ContractDate', 20991231) d_contractdate, getsysvalue(ss.tenantid, ss.c_code, ss.c_cyno,ss.c_managercode, ss.c_prdcode,'ShareTypes', '') c_mutextypes FROM (SELECT fi.tenantid, fi.c_code, fi.c_prdcode, c.c_cyno, s.c_mutextype, fi.c_managercode FROM FUNDINFO fi, (SELECT 'A' c_mutextype FROM DUAL UNION ALL SELECT 'B' c_mutextype FROM DUAL UNION ALL SELECT 'C' c_mutextype FROM DUAL) s, QUALIFY c, EXPBATCH d WHERE fi.c_code = 'F6' AND fi.tenantid = '*' AND c.c_code = 'F6' AND c.tenantid = '*' AND c.c_cyno = d.c_cyno AND c.c_code = d.c_code AND c.tenantid = d.tenantid AND INSTR(fi.c_mutextypes, s.c_mutextype) > 0 AND fi.c_fundstatus NOT IN ('6', '9') AND (fi.c_fundtype <> '1' OR NOT EXISTS (SELECT 1 FROM TEXT_PARAMETER WHERE c_paramitem = 'ChangeLimit' AND c_paramvalue = '1' AND c_prdcode = fi.c_prdcode AND c_code = 'F6' AND tenantid = '*')) AND fi.c_prdcode = c.c_prdcode AND c.c_issaleflag = '1' ) ss, EXPBATCH ta WHERE ss.c_cyno = ta.c_cyno AND ss.c_code = ta.c_code AND ss.tenantid = ta.tenantid AND ta.c_notexpparamfiles = '0' ) fi WHERE fo.c_prdcode <> fi.c_prdcode AND fo.c_mutextype = fi.c_mutextype AND fo.c_cyno = fi.c_cyno ) t,(SELECT c_prdcode, c_cyno, MIN(a.f_cminmutex) f_cminmutex FROM ARLIMIT a WHERE c_cyno <> '*' AND c_code = 'F6' AND tenantid = '*' GROUP BY c_prdcode, c_cyno UNION ALL SELECT c_prdcode, b.c_cyno, MIN(f_cminmutex) f_cminmutex FROM ARLIMIT a, EXPBATCH b WHERE a.c_cyno = '*' AND a.c_code = 'F6' AND a.tenantid = '*' AND a.c_code = b.c_code AND a.tenantid = b.tenantid AND not exists (SELECT 1 FROM ARLIMIT c WHERE b.c_cyno = c.c_cyno AND a.c_prdcode = c.c_prdcode AND a.c_code = c.c_code AND a.tenantid = c.tenantid) GROUP BY c_prdcode, b.c_cyno) ar WHERE t.d_contractdate <= 20100511 AND t.d_othercontractdate <= 20100511 AND INSTR(t.c_mutextypes, t.c_mutextype) > 0 AND INSTR(t.c_othersharetypes, t.c_targetsharetype) > 0 AND t.c_prdcode = ar.c_prdcode AND t.c_cyno = ar.c_cyno AND NOT EXISTS (SELECT 1 FROM CHANGELIMIT b WHERE t.c_prdcode = b.c_prdcode AND (t.c_mutextype = b.c_mutextype OR b.c_mutextype = '*') AND (t.c_cyno = b.c_cyno OR b.c_cyno = '*') AND (t.c_othercode = b.c_othercode OR b.c_othercode = '*') AND (t.c_targetsharetype = b.c_othershare OR b.c_othershare = '*') ) ) a LEFT JOIN FUNDCODECHANGE b ON (a.c_prdcode = b.c_prdcode AND a.c_mutextype = b.c_mutextype AND a.c_code = b.c_code AND a.tenantid = b.tenantid) LEFT JOIN FUNDCODECHANGE c ON (a.c_othercode = c.c_prdcode AND a.c_targetsharetype = c.c_mutextype AND a.c_code = c.c_code AND a.tenantid = c.tenantid)
-- 此處隱藏了謂詞部分 Plan Hash Value : 4256998962 -------------------------------------------------------------------------------------------------------------------------- | Id | Operation | Name | Rows | Bytes | Cost | Time | -------------------------------------------------------------------------------------------------------------------------- | 0 | INSERT STATEMENT | | 1 | 129 | 2103 | 00:00:26 | | 1 | LOAD TABLE CONVENTIONAL | C3 | | | | | | 2 | NESTED LOOPS OUTER | | 1 | 129 | 2103 | 00:00:26 | | 3 | VIEW | | 1 | 95 | 2102 | 00:00:26 | | * 4 | FILTER | | | | | | | * 5 | HASH JOIN | | 1 | 8105 | 2101 | 00:00:26 | | * 6 | HASH JOIN | | 1 | 8078 | 1827 | 00:00:22 | | 7 | NESTED LOOPS OUTER | | 1 | 4060 | 914 | 00:00:11 | | 8 | VIEW | | 1 | 4026 | 913 | 00:00:11 | | 9 | SORT UNIQUE | | 1 | 72 | 913 | 00:00:11 | | * 10 | FILTER | | | | | | | 11 | NESTED LOOPS | | 1 | 72 | 912 | 00:00:11 | | 12 | NESTED LOOPS | | 1 | 72 | 912 | 00:00:11 | | 13 | NESTED LOOPS | | 1 | 58 | 911 | 00:00:11 | | 14 | NESTED LOOPS | | 1 | 49 | 910 | 00:00:11 | | 15 | NESTED LOOPS | | 1500 | 46500 | 309 | 00:00:04 | | 16 | VIEW | | 3 | 9 | 6 | 00:00:01 | | 17 | UNION-ALL | | | | | | | 18 | FAST DUAL | | 1 | | 2 | 00:00:01 | | 19 | FAST DUAL | | 1 | | 2 | 00:00:01 | | 20 | FAST DUAL | | 1 | | 2 | 00:00:01 | | * 21 | TABLE ACCESS FULL | FUNDINFO | 500 | 14000 | 101 | 00:00:02 | | * 22 | TABLE ACCESS BY INDEX ROWID | QUALIFY | 1 | 18 | 1 | 00:00:01 | | * 23 | INDEX RANGE SCAN | UIDX_QUALIFY | 1 | | 1 | 00:00:01 | | * 24 | INDEX RANGE SCAN | PK_BATCH | 1 | 9 | 1 | 00:00:01 | | * 25 | INDEX RANGE SCAN | PK_EXPBATCH | 1 | | 1 | 00:00:01 | | * 26 | TABLE ACCESS BY INDEX ROWID | EXPBATCH | 1 | 14 | 1 | 00:00:01 | | * 27 | INDEX RANGE SCAN | IDX_TEXTPARAMETER_VALUE | 1 | 30 | 1 | 00:00:01 | | * 28 | TABLE ACCESS BY INDEX ROWID | FUNDCODECHANGE | 1 | 34 | 1 | 00:00:01 | | * 29 | INDEX UNIQUE SCAN | PK_FUNDCODECHANGE | 1 | | 1 | 00:00:01 | | 30 | VIEW | | 1 | 4018 | 913 | 00:00:11 | | 31 | SORT UNIQUE | | 1 | 69 | 913 | 00:00:11 | | * 32 | FILTER | | | | | | | 33 | NESTED LOOPS | | 1 | 69 | 912 | 00:00:11 | | 34 | NESTED LOOPS | | 1 | 69 | 912 | 00:00:11 | | 35 | NESTED LOOPS | | 1 | 58 | 911 | 00:00:11 | | 36 | NESTED LOOPS | | 1 | 49 | 910 | 00:00:11 | | 37 | NESTED LOOPS | | 1500 | 46500 | 309 | 00:00:04 | | 38 | VIEW | | 3 | 9 | 6 | 00:00:01 | | 39 | UNION-ALL | | | | | | | 40 | FAST DUAL | | 1 | | 2 | 00:00:01 | | 41 | FAST DUAL | | 1 | | 2 | 00:00:01 | | 42 | FAST DUAL | | 1 | | 2 | 00:00:01 | | * 43 | TABLE ACCESS FULL | FUNDINFO | 500 | 14000 | 101 | 00:00:02 | | * 44 | TABLE ACCESS BY INDEX ROWID | QUALIFY | 1 | 18 | 1 | 00:00:01 | | * 45 | INDEX RANGE SCAN | UIDX_QUALIFY | 1 | | 1 | 00:00:01 | | * 46 | INDEX RANGE SCAN | PK_EXPBATCH | 1 | 9 | 1 | 00:00:01 | | * 47 | INDEX RANGE SCAN | PK_EXPBATCH | 1 | | 1 | 00:00:01 | | * 48 | TABLE ACCESS BY INDEX ROWID | EXPBATCH | 1 | 11 | 1 | 00:00:01 | | * 49 | INDEX RANGE SCAN | IDX_TEXTPARAMETER_VALUE | 1 | 30 | 1 | 00:00:01 | | 50 | VIEW | | 7073 | 190971 | 274 | 00:00:04 | | 51 | UNION-ALL | | | | | | | 52 | SORT GROUP BY | | 1 | 16 | 20 | 00:00:01 | | 53 | TABLE ACCESS BY INDEX ROWID | ARLIMIT | 1 | 16 | 20 | 00:00:01 | | * 54 | INDEX FULL SCAN | UIDX_ARLIMIT | 1 | | 20 | 00:00:01 | | 55 | SORT GROUP BY | | 7072 | 275808 | 254 | 00:00:04 | | * 56 | HASH JOIN RIGHT ANTI | | 2970075 | 115832925 | 175 | 00:00:03 | | * 57 | INDEX FULL SCAN | UIDX_ARLIMIT | 30000 | 420000 | 20 | 00:00:01 | | * 58 | HASH JOIN | | 2970075 | 74251875 | 146 | 00:00:02 | | * 59 | INDEX FULL SCAN | PK_EXPBATCH | 99 | 891 | 1 | 00:00:01 | | * 60 | TABLE ACCESS FULL | ARLIMIT | 30000 | 480000 | 137 | 00:00:02 | | * 61 | TABLE ACCESS BY INDEX ROWID | CHANGELIMIT | 1 | 28 | 1 | 00:00:01 | | * 62 | INDEX RANGE SCAN | IDX_CHANGELIMIT_FUNDCODE | 90 | | 1 | 00:00:01 | | * 63 | TABLE ACCESS BY INDEX ROWID | FUNDCODECHANGE | 1 | 34 | 1 | 00:00:01 | | * 64 | INDEX UNIQUE SCAN | PK_FUNDCODECHANGE | 1 | | 1 | 00:00:01 | --------------------------------------------------------------------------------------------------------------------------
這裏不會講基礎優化,好比何時用hash join,何時nl,lio怎麼看,access method,join method,fiter,predicate等基礎優化知識。java
在某些狀況下,查詢有left join時,使用了parallel會致使查詢一直出不來,不用parallel反而馬上能夠出來。mysql
在不少場景中,parallel提高性能頗有限、甚至下降了性能以及並行執行中的buffer sort。在並行的執行計劃中,咱們能夠看到有一步是buffer sort,並且這一步佔據了挺長的時間,buffer sort實際上沒有排序,只是把數據加載到內存,不掃描屢次表。一般在並行執行中出現這個操做。parallel爲何會性能提高有限呢,這其實涉及到oracle內部的算法是儘量大部分場景最優化,這就會致使對於咱們知道有規律的關聯消除並無被按預期消除,好比說在咱們的這個優化中,每一個管理人只有1w支產品,不一樣管理人之間是沒法轉換的,可是oracle不知道,即便咱們採用hash分區,且在管理人字段,設置最多100個分區(讀者確定會問,爲何要使用hash分區而不是list呢?由於這裏的管理人數量是不固定的,而咱們使用的是oracle 11.2,若是是12c,就不存在這個問題了,由於12c支持了自動分區,見oracle 12cR1&12cR2核心高實用性新特性),也並非每一個分區一個管理人,不少分區沒有數據,因此致使分區間很不均衡,採用並行執行的時候,由於很不均衡,致使執行計劃傾斜嚴重,可能高達40%。同時由於統計信息不必定準確,可能會致使某些時候應該px send hash的成了px send broadcast,這雖然能夠經過pq_distriubte hint強制,可是因爲優化器會在hint基礎上進一步進行查詢轉換(尤爲涉及到子查詢套子查詢的時候),也可能會致使非預期的狀況。實際上,對於超過三五個表以上覆雜的sql,parallel效果就不佳的,這種狀況,其實應該是應用裏面發起多線程並行請求,採用fork-join模式來達到並行執行的效果。這一點只有掌握具體數據庫好比oracle/mysql優化原理(oracle作獲得的mysql不必定作獲得,反過來通常不成立)和實現的架構師作獲得。最後,筆者會講到,本例是應該在設計上進行優化而不是單獨的oracle和sql優化的。c++
- 執行計劃相同,select很快,insert select很慢。好比帖子https://bbs.csdn.net/topics/370166028提到問題。對於結果記錄成千上萬的查詢,須要知道select只是fetch了前面幾行(即便優化器模式是all_rows),fetch也是要消耗lio的,因此若是不想insert 驗證下大概時間,至少須要select count(1) from (select /*+ no_merge*/ from ...),這樣才能獲得相對比較合理的值(這個值可能和實際insert相差50%-100%之間,主要視數據量以及磁盤的速度而定)。有時候可能count(1)也很快,insert就很慢,執行計劃又是徹底相同的,這該怎麼辦呢,結果數據量也不大,也就兩三百萬而已???還沒細看,最近真遇到這個例子了,
- oracle insert select和select執行計劃不一樣。參考insert select和select執行計劃不一樣。
- 主查詢中某子查詢的執行計劃和子查詢獨立執行的執行計劃不一樣。頗有可能在子查詢獨立執行的時候,表之間的關聯採用了hash join,做爲主查詢子句的時候,就是nl了,也有可能hash group by 變成了sort group by。這一點能夠考慮使用with子句+materialize或者no_merge以及其餘hint來控制住。
- 多with子句,只有第一個被物化爲臨時表。之前沒注意,此次發現有多個with的時候,只有第一個被物化,後面的都沒有被物化,雖然都加上了/*+ materialize */提示。若是實在沒法控制執行計劃,就考慮建立個臨時表吧(惋惜oracle 12c以前,gtt和常規表同樣會產生大量的undo,oracle 12c引入了臨時undo表空間,能夠極大的規避redo/undo的生成)。測試可見http://blog.itpub.net/7199859/viewspace-1150868/。
- not exist中有and致使優化器選擇了filter。filter從實現上跟nl差很少,對於主查詢成千上萬的查詢,極可能一個filter就查詢不出來或者嚴重影響性能。一般not exist中會出現相似下列的語句:
and not exists (select /*+ qb_name(inn)*/* from inner_table inn where (inn.status = outer_table.status or inn.status = '1') and xxx)算法
對於這種狀況,anti join就失效了,雖然可讓主查詢使用nl加上/*+ push_subq(@inn) */來pushdown查詢條件,以免主查詢使用hash join產生巨量中間結果,可是nl對於大表性能極爲低下。此時能夠考慮將not exists拆分爲以下:sql
and not exists (select * from inner_table inn where (inn.status = '1') and xxx)數據庫
and not exists (select * from inner_table inn where (inn.status = outer_table.status or inn.status = '1') and xxx)服務器
此時可能就能夠作到第一個not exists採用hash anti過濾掉大部分最終結果不須要的數據集。session
- exist中有or致使優化器選擇了filter。對於exists,一般狀況以下:
and exists (outer_table.status = '1' or select 1 from another_table x where x.type = outer_table.type and ... )mysql優化
對於這類查詢,能夠考慮使用union實現,可是有些時候,union並不見得合適,好比說查詢上使用了分析函數就不合適了。此時就須要考慮其餘方法(由於在咱們的另外一個sql中,結果集還不算大,因此也就沒有進一步優化)。
- 在java/c++中運行很慢,拷貝到pl/sql dev中執行就很快。其實瞭解oracle/mysql優化器原理的都知道綁定變量的概念,因此這就不足爲奇了,要在pl/sql dev中徹底模擬代碼中的行爲,要麼使用sql*plus的綁定變量,要麼放到存儲過程(pl/sql默認綁定變量,除非使用execute immediate)中執行,就模擬了在代碼中的行爲。
- hash group by vs sort group by。hash group by 與 sort group by的區別,能夠參考http://www.itpub.net/thread-1813825-1-1.html,要強制指定使用hash group by,可使用
/*+ NO_USE_HASH_AGGREGATION */提示。
- SQL中調用了自定義函數時性能低下之。在sql語句中調用自定義函數是一大禁忌,內置函數之因此幾乎感受不大太大的性能,是由於內置函數好比upper/lower都是肯定性函數,也就是對於相同輸入、老是產生相同輸出,實際上在一個sql語句中,咱們調用自定義函數的時候,作的一般都是相似參數/翻譯相關的功能,因爲涉及到多個級別的優先級問題,因此一個sql語句實現較爲複雜,因此在這個sql執行期間,一般不會是相同的輸入、產生不一樣的輸出,此時將自定義函數設置爲DETERMINISTIC,在某些狀況下,能夠極大的提高性能(視在這個SQL執行期間,有多少比例的重複輸入而定)。可是,若是幾乎沒有重複的輸入,此時硬是將exists/not exists改成DETERMINISTIC自定義函數,將是悲劇。。
- pga優化。workarea_size_policy模式下主要是pga_aggregate_target、_smm_px_max_size、_smm_max_size、_pga_max_size這四個參數,肯定pga是否足夠只要查詢v$sql_plan_statistics_all,查看ONEPASS_EXECUTIONS、MULTIPASSES_EXECUTIONS、LAST_TEMPSEG_SIZE、MAX_TEMPSEG_SIZE這4個字段,只要MULTIPASSES_EXECUTIONS、LAST_TEMPSEG_SIZE、MAX_TEMPSEG_SIZE都爲空或者0就能夠保證pga足夠。
- $session.event監控並優化服務器配置。當運行時間比較長的時候,咱們須要查詢下v$session看下該進程當前在等待什麼事件,根據此進一步肯定是終止執行仍是查看os的當前狀況。
- v$session_longops。對於並行執行(非並行執行不會體現),能夠經過該視圖預估語句還須要執行多久,通常來講仍是比較可靠的。
- sar -d -p 5 10000。能夠監控各個磁盤的飽和度狀況,以下:
%util 90%以上就說明當前磁盤太忙,要麼佈局不合理,要麼太慢了。
- 問題頗有可能在應用架構設計上,這事情已經超出了oracle的範圍。分而治之不是數據庫自身擅長的,這屬於應用設計的一部分,雖然相似分區、parallel算是分而自治的理念,可是它畢竟不知道業務上下文的特殊性,因此,對於這個sql,應該在SQL優化的基礎上,5或者10或者20個管理人做爲條件,開管理人總數/每次處理的管理人做爲線程數,採用fork-join模式實現,不管應用層是java仍是c++均可以較爲簡單的實現,三五分鐘徹底是能夠實現的(181114,咱們最近將mysql版本進行了應用架構層的優化,將執行時間從27分鐘下降到了8分鐘,跑的比oracle還快)。
- 對於複雜的sql,建議加上/*+ gather_plan_statistics */提示以便在v$sql_plan_statistics_all中收集實際的邏輯讀、物理讀、基數等值,以便進一步優化執行計劃。