SQL優化之六步走

提到SQL優化,咱們不得不學習幾個名詞,這就比如武俠小說裏練習武功同樣,不知道幾個穴道,不瞭解幾個氣門都很差意思提本身是練功夫的。名詞挺多,除了這裏提到的還有好多,好比內外鏈接、嵌套循環、遊標共享、綁定變量、軟硬解析等等,武功太多,練不過來,那咱們先把馬步紮好再說。沒有提到的功夫在藏經閣都有,請按需百度。sql

2.1 執行計劃數據庫

執行計劃是什麼呢?就是SQL執行的路徑和執行步驟。怎麼理解呢?你從家裏到公司能夠選擇開車走高速,也能夠選擇作公交車走市區街道,那麼你選擇用哪一個交通工具,走哪一個路線,就是你的執行計劃。可是一次只能有一個方案。函數

一個SQL生成了執行計劃,他就會被固定到共享池裏,只有在表發生了變動、統計信息過舊、共享池被刷新、數據庫重啓等狀況下SQL纔會從新生成執行計劃。仍是從家到公司,城市在修路,公交路線變化都會影響你選擇什麼樣的方式上班。工具

2.2 索引性能

索引是一個單獨的、物理的數據庫結構,它是某個表中一列或若干列值的集合和相應的指向表中物理標識這些值的數據頁的邏輯指針清單。怎麼理解呢?表是一本書,索引就是這本書的目錄。學習

2.3 統計信息測試

統計信息主要是描述數據庫中表,索引的大小、規模、數據分佈情況等的一類信息。好比表的行數、塊數、平均每行的大小、索引的leaf blocks、索引字段的行數、不一樣值的大小等,都屬於統計信息。優化

Oracle的基於成本的優化器(還有一種是基於規則的,武功過期了,不練了)正是根據這些統計信息數據,計算出不一樣訪問路徑下,不一樣鏈接方式下,各類計劃的成本,最後選擇出成本最小的計劃。若是沒有統計信息呢?會發生動態採樣,就是在生產執行計劃前去表、索引去進行數據採樣。url

怎麼理解呢?仍是從家到公司,有了地圖和公交信息,高速收費狀況等信息,你們就能夠選擇最合適的上班方案了。沒有這些現成的信息你就須要實際去調研一下了,比較麻煩,並且可能形成不合適的選擇。spa


3.某個SQL是否能優化?


怎麼能肯定一個SQL是否能優化呢?這個不太好回答,DBA們常常說先分析分析看看。那麼分析什麼呢:如今效率如何?執行計劃?跑的時候等待事件?表多大?選擇列上有無索引?選擇性如何?有統計信息?鏈接方式?......又是這些名詞,尚未徹底理解的小夥伴不要着急,我不是賣野藥的,立刻就要表演啦!

其實呢,也能夠不用這麼複雜。一個很是劣質的SQL(跑幾十分鐘,甚至是幾個小時的SQL)最終能不能有質的提速,主要就是看業務的本質上須要訪問多少數據量,若是業務本質上須要訪問的數據量越少,通常來說提速餘地就越大。簡單吧,因此一個業務專家,你遇到了一個劣質SQL,不用DBA分析,你應該就知道有沒有優化餘地了。

怎麼理解呢?一個大操場上有一個紅色的玻璃球,讓你去拿到,你沒看見,地毯式的找半天,有人給你個座標圖,你10秒鐘跑過去就找到了,這就是有提速餘地,若是滿操場上都是須要的紅色玻璃球讓你拿,不管怎麼拿,都要拿半天,這就是無法有質的飛躍(就是沒辦法幾秒鐘、幾分鐘把活幹完)。


下面咱們按部就班的講一些案例吧。

01
第一劍

少商劍:合理利用索引

1.1利用索引提升效率

INS_USER_571 表上有兩個索引IDX_INS_USER1_571,IDX_INS_USER2_571分別對應列BILL_ID,EXPIRE_DATE列,SQL 和執行計劃:

select * from SO1.INS_USER_571 M where M.BILL_ID = '13905710000' and M.EXPIRE_DATE > sysdate;


符合BILL_ID的數據只有一條,而符合EXPIRE_DATE條件的數據有幾萬條,就是說BILL_ID選擇性好,選擇IDX_INS_USER1_571索引就會更快的找到這條數據。


怎麼理解?大操場上找1個玻璃球,給你兩個座標圖,一張直接告訴你這個玻璃球的具體位置,一張圖告訴你在某個10米的範圍圈內,選擇哪一個呢?確定選第一張。


1.2走索引反而慢?


在CREATE_DATE上創建個索引IDX_INS_USER4_571:

SQL:

SELECT COUNT(*) FROM SO1.INS_USER_571 M WHERE M.CREATE_DATE >= TO_DATE('20110101','YYYYMMDD') AND M.CUST_TYPE='1';

PLAN:沒有走索引?!


難道是優化器有問題?測試下就知道了。爲了明確加Hints測試:

SELECT /*+full(M)*/COUNT(*) FROM SO1.INS_USER_571 M WHERE M.CREATE_DATE >= TO_DATE('20110101','YYYYMMDD') AND M.CUST_TYPE='1';

SELECT /*+INDEX(M,IND_INS_USRE4_571)*/COUNT(*) FROM SO1.INS_USER_571 M WHERE M.CREATE_DATE >= TO_DATE('20110101','YYYYMMDD') AND M.CUST_TYPE='1';

看來優化器沒有錯!

當須要掃描的數據量較大時,走索引的成本比全表掃描的成本還要高。


怎麼理解?一本書1000頁,其中800頁的內容都是你須要的,那你看書的時候就沒有必要每看一頁都要回過頭來翻翻目錄了,這樣多羅嗦呀!直接順序往下讀好了。


1.3本招的幾個口訣

  • 在where條件中選擇列上加了函數,無法利用索引


例如:where to_char(create_date,’YYYY-MM-DD HH24:MI:SS’)>= ‘2015-04-08 00:00:00’


這樣無法利用到create_date的索引


正確的寫法:

where create_date>=to_date( ‘2015-04-08 00:00:00’,’YYYY-MM-DD HH24:MI:SS’)

案例:select count(t.visitor_id) as pv

from t_stat_logbase t

where instr(t.visit_url, :""SYS_B_0"")>:""SYS_B_1"" and

to_char(t.visit_date,:""SYS_B_2"")=:""SYS_B_3""

visit_url, visit_date 都有索引,並且visit_url列的選擇性很是好,可是由於在本列上加了instr的函數,形成只能全表掃描,因天天零點左右執行頻率高,數據庫常常出現性能告警,應用改造後有效利用了索引,數據庫恢復正常。


Ø 變量類型要正確,避免隱式轉換形成無法利用索引

Bill_Id 是varchar2的類型

Where bill_id=1 就無法利用索引。


Ø Where條件終須用到前導列才能走上組合索引,組合索引中選擇性好的要放在前面:

索引ind_ins_user5_571(create_date, cust_type): where cust_type =1

只有cust_type這個條件,而沒有create_date走不上索引ind_ins_user5_571


Ø 在多表關聯的狀況下,即便表很小,在關聯字段上加索引每每都很是有效,可能影響表的鏈接方式而節約成本。


Ø 不要試圖每個字段上都加索引,索引越多對錶的DML影響越大,對DML要求很高的接口表都不建議加索引,組合索引的列越多,索引樹可能越深,有時候不但不能查詢提速,反而還影響了DML效率。


例如:Where條件中有一個選擇性很是好的字段,如BILL_ID/ACC_ID/USER_ID,其餘選擇條件如STATE/TYPE沒有必要再進行組合索引。


1.4老闆不再用擔憂個人數據積壓啦


某報表庫下有這樣一條sql,每一個地市啓動了10個線程,去進行數據處理,每一個地市執行頻率每小時高達幾萬次,形成數據庫CPU壓力巨大,而下降線程又會出現數據積壓,咋整呢?DBA們也捉急呀!

select ROWID, t.*

from CHK_ORD_INTERFACE_574 t

where mod(INTERFACE_ID, :"SYS_B_0") = :"SYS_B_1"

and SCAN_STATE = :"SYS_B_2"

and rownum <= :"SYS_B_3"

sql特色: 執行計劃走全表掃描,過濾條件SCAN_STATE字段只有2鍾狀況(0,1是否已稽覈)


業務特色:CHK_ORD_INTERFACE_NN表從營業端經過dsg同步到報表端,在報表端進行數據稽覈,稽覈過的數據進行delete,本表按日進行分區,也就是碎片重用率很低,形成本表碎片極爲嚴重,碎片率達99%以上,因dsg同步是經過rowid進行,形成本表不能經過move,shrink等手段進行碎片清理。走不上分區,過濾條件選擇性低!不能整理碎片!看起來這不是沒辦法嘛?!


方案:本表的碎片率高達99%,說明實際的數據量很是少,能夠建立全局索引(本表的生命週期是永久保留,全局索引在本業務特色下能夠提升索引碎片的重用率,有效控制索引大小),數據塊雖然不少,索引很小呀,創建,有效!原來1.4S,建立索引後0.002s,線程調整到1,照樣完成任務,CPU也下去了,老闆不再用擔憂個人數據積壓啦!

02
第二劍

中衝劍:合理的邏輯

1.1 有效利用鏈接,避免嵌套


表的外鏈接通常都會比半鏈接效率上高的多,在保證業務效果的狀況下,咱們更建議表關聯代替IN/EXISTS子查詢,好處多多,關係到驅動表啦,鏈接模式啦,這裏不細表。有興趣,請實踐,有疑問,喊DBA!


在某報表系統的專項優化中,咱們抓到了一個異常複雜的sql涉及到13張表,5層的嵌套....經過分析業務需求以後,發現用多表關聯能夠代替In字查詢(業務效果不變)咱們將SQL裏面的三個小段落作了相似以下修改,並在關聯鍵上建立了索引,由原來跑2個小時提速到10秒以內:



修改成:


1.2 括號內外差異很大


直接上菜:

原sql


問題出在哪裏呢?本sql緩慢的根源是括號,括號犯了什麼罪?他形成了針對RP_BUSI_DETAIL_NNN(571 180G)進行全表掃描,而業務特色是讀取distinct customer_order_id, SALER_ID 且與RPT.INSX_OFFER_PRG_577 的ustomer_order_id列進行關聯。


方案:建立customer_order_id, SALER_ID的組合索引(兩列都非空),並將RPT.INSX_OFFER_PRG_NNN表的其餘過濾條件放進子查詢中,這樣sql執行計劃掃描索引,並且不進行回表。


效果:這個sql在1小時快照週期內就沒有完成過,不過優化後就好啦,在不一樣的條件範圍內1-30秒均可以完成。


新sql:


1.3 深刻理解業務,利用好分區


很惋惜,上個故事尚未結束,過了一個星期,業務側反映有些業務很快了,但是有些卻很慢,特別是一次性統計3個月的報表根本就跑不了......有些快?有些慢?這個是重點,當即聯繫業務側和開發確認快和慢的場景,並進行了跟蹤,發現快的場景下都輸入了ORG_ID條件,而慢的場景下都沒有ORG_ID條件,那麼關鍵點就在這裏了!


不帶條件ORG_ID = :ORG_ID,某月份的數據爲例,那麼符合條件的INSX_OFFER_PRG_571的數據有14萬之多,與RP_BUSI_DETAIL_NNN表關聯時 CBO優化器只會選擇hash jion鏈接,且選擇的索引只能是done_date(取出範圍內的數據後再進行hash),如此大的兩張表確定無法在50秒內(超時限制)完成,而輸入了條件IOP.ORG_ID = :ORG_ID那麼符合條件的INSX_OFFER_PRG_571的數據在100條之內,sql能夠走嵌套循環,並且兩張表均可以利用到高效的索引,速度天然就大大提升。


那麼不帶條件ORG_ID = :ORG_ID下能不能快呢?最後發現了一個關鍵點RP_BUSI_DETAIL_NNN是個按月分區表,每一個分區只有1-6G,比起180G天然是小的多了!並且分區字段也是DONE_DATE,很惋惜sql中沒有利用到分區,當即聯繫需求側和開發側確認,INSX_OFFER_PRG_NNN 和RP_BUSI_DETAIL_NNN兩張表的DONE_DATE在業務意義上是否一致或者差距很小?是否能夠把RP_BUSI_DETAIL_NNN表的DONE_DATE也一併放在條件中?很是幸運,個人這個提議馬上獲得了確定的回覆,在後續的溝通和測試下,從新定製了業務規則,最多隻能按天然月查詢(考慮表按天然月分區)查詢,業務超時由原來的50秒改爲3分鐘,最終將SQL改寫成了:




雖然目前的執行計劃中INSX_OFFER_PRG_NNN 和RP_BUSI_DETAIL_NNN兩張表仍是經過hash jion鏈接,數據量也仍是較大,可是咱們走上了分區過濾,特別是RP_BUSI_DETAIL_NNN這張大表只會單分區掃描,因此在新的規則和超時機制下咱們順利的完成了這個報表的優化。


1.4 數據庫遷移前的優化


背景:某個有點年頭的數據庫在某次項目改造中須要遷移並升級到新的機器上,這個庫由於業務不停的上線和實在有點低的硬件配置緣由在遷移升級前主機CPU的使用率也是很高了,而遷移須要藉助某第三方工具,這個工具的初始化過程須要有足夠的CPU資源,而割接時間窗口和業務緣由只能準在線割接(能夠停業務的時間很短),也就是說雖然這個數據庫在目前的低配置機器上轉不了幾天了,可是爲了保證割接的順利進行DBA們仍是硬着頭皮去進行優化。


優化過程當中的一個案例:

原sql:

這個SQL執行時間在25秒左右,邏輯上看起來實在是有點複雜,不過咱們耐心的拆解分析下找到了第一個突破點:修改如下子查詢的寫法



上面的寫法等同於:


下面的寫法執行時間能夠從15秒降到10秒,有些進展!繼續。


考慮到這個子查詢所進行的錶鏈接,能夠將鏈接條件放置在這個子查詢的where字句中,進一步簡化邏輯,改寫爲:



該子查詢的執行時間進一步減小到1.7秒,在原SQL中使用這個新子查詢,原SQL的執行時間從25秒減小到4.6秒,性能提升了5倍多。好了,理解聯繫開發進行測試驗證,2天后他們反饋業務效果徹底同樣,並且速度的確明顯快了不少!


看吧!咱們什麼也沒作,沒有加索引,沒有改造表的物理模型,僅僅經過梳理邏輯,簡化sql執行的步驟和避免重複數據掃描就完成了一項優化,因此咱們之後在寫代碼的過程當中是否是要多想想呢?!經過2周的努力,咱們終於將主機CPU使用率給整體降下來接近10%,順利的保障了割接。

03
三劍連出

1.關衝劍:綁定執行計劃

1.1 合理利用Hints


Hints定製執行計劃,在割接、經分、稽覈等場景下應用普遍,合理的利用能夠有效利用資源,提升執行效率。


Parallel 並行:合理的利用parallel能夠有效利用資源,提升執行效率。可是在平常生產期間,核心庫不容許吆!Parallel開的越高,就是並行度越高,那麼資源使用就越嚴重,因此既要考慮效率,也要考慮負載,曾經在某些系統中抓到了/*+parallel(a,64)*/,這位老兄!咱們邏輯的CPU也不過只有32顆!

Select /*+parallel(a,6)*/count(*) from ins_offer_571 a where .....


Append nologging:Append nologging 高水位直接路徑寫入,減小寫日誌,經分,割接場景下較多使用

INSERT /*+ APPEND NOLOGGING PARALLEL(TMP_H_MD_PAR_EXT_USER_D16,3) */

INTO TMP_H_MD_PAR_EXT_USER_D16

(......)

SELECT /*+PARALLEL(R,3)*/*

from H_MD_BLL_TMN_BUSN_D_574 PARTITION(P20150406) R


  • /*+index(a,index_name)*/ 指定索引掃描

  • /*+full(a)*/ 指定全表掃描

........ 還有好多,這裏不一一列舉了,都在:select * from v$sql_hint


1.2 SQL PROFILE,還好有你!

某報表庫有個前臺的多選框操做的報表,sql的條件組合多樣化,表RP_BUSI_DETAIL_NNN上存在12個索引,且多爲組合索引,索引鍵重複狀況較多,某天爆發了一下,大量的執行計劃走錯,統計信息收集了,遊標刷出去了,沒有效果。大白天的改造索引風險又過高,還好本sql有個特色,DONE_DATE字段必定要用的...... 狀況緊急!DBA們還有一招 SQL PROFILE。



1 獲取outline

select* fromtable(dbms_xplan.display_cursor('0bbt69m5yhf3p',null,'outline'));


2 建立sql profile 綁定執行計劃



綁定完成後,kill執行計劃有問題的SQL,數據庫的性能就逐漸恢復了。後續業務梳理完成後改造了索引,各種sql的執行計劃也就都穩定了。


2.少衝劍:怎麼就忽然慢了?緣來是你!


某核心系統的維護找過來,每天跑的一個做業這幾天變慢了,原來跑1.5-2個小時的做業,如今6個小時了也沒跑出來。老闆說搞定他!


Sql過來了:


執行計劃先看看


這不是在動態採樣嘛!仍是level=7的,難不成跑了6個小時仍是在動態採樣中?開個10046看看會話在什麼,一看不只僅有動態採樣形成的問題呀,還有GC!


趕忙回訪一下,果真這個做業一直就是跑在1號節點的。DR_GGPRS_XX_YYYYMMDD 是在2號節點入庫的!那麼在1號節點執行本做業就是有問題!


動態採樣怎麼辦呢?這個DR_GGPRS_XX_YYYYMMDD表大地市超過400G,想收集統計信息是不可能的,況且做業天天僅僅跑一次,也不存在什麼遊標共享的問題了,乾脆直接綁定執行計劃,避免動態採樣,在2號節點測試一下。

1590s完成!

通知應用,本做業調整到2號節點,並經過Hints /*+parallel(a,4) dynamic_sampling(a,0)*/

避免動態採樣,後續反饋做業均可以在30分鐘內完成。


3.少澤劍:高效的update,不能隨心所欲!

某核心系統,Cpu使用率接近100%,監聽的響應速度很是慢,主營業務數據入庫積壓嚴重!分析事後找到了罪魁禍首,居然是幾個循環的update語句把CPU耗盡的!

咱們來看看他的威力:

僅僅從語句的變量中就發現了一個問題:本語句是循環執行的!並且循環次數很是多!找到應用方,電話回訪,果真如此!並且本做業跑了一天了好沒有跑完。


匹配條件表DR_GGPRS_XX_YYYYMMDD根據地市不一樣在70-200G之間,數據量都是億級,執行計劃都不用看了,有沒有索引都不重要了,就憑着這麼循環下去,我也只能呵呵了!


被更新的表user_fenleijx_temp_YYYYMMDD很小啊,才幾百MB!我前面說什麼來着?!一個SQL能不能本質上被提速,看看他最終業務上須要掃描的數據就能夠了,這必須能夠提速啊!


咱們開始吧!

1.create tmp_table

create table jf.tmp_tab1 parallel 10 nologging as select distinct user_number from jf.dr_ggprs_jx_20150203 where charging_characteristics='04';

create table jf.tmp_tab2 parallel 10 nologging as select distinct user_number from jf.dr_ggprs_jx_20150203 where charging_characteristics='0400';

2.Create unique index

create unique index jf.ind_user_number_1 on jf.tmp_tab1(user_number) nologging;

create unique index jf.ind_user_number_2 on jf.tmp_tab2(user_number) nologging;

3.高效的update

update (select a.status

from bossmdy.user_fenleijx_temp_20150203 a,

jf.tmp_tab1 b

where a.user_number = b.user_number

and a.status = '3')

set status = '0';

update (select a.status

from bossmdy.user_fenleijx_temp_20150203 a,

jf.tmp_tab2 b

where a.user_number = b.user_number

and a.status = '3')

set status = '0';


不須要循環update,只須要建立一張匹配條件爲惟一性的臨時表,再建立一個惟一性索引,一次性update目標表,整個工做耗時10分鐘。不只僅是高效,CPU資源的高消耗也沒有了,數據庫恢復了正常。

高效UPDATE方式

update (select a.col_a,b.col_b from t_tab_a a,t_tab_b b where a.id=b.id and b.col_x=’x’) c

set c.col_a=c.col_b;


要保證匹配條件b.id上有惟一性索引,若是沒有惟一性索引則能夠經過建立中間表的方式取出惟一性數據再建立惟一性索引如:

Create table t_tab_c as select * from t_tab_b where b.col_x=’x’;

Create unique index ind_t_tab_c on t_tab_c(id);


04
第六劍

1.商陽劍:割接這三板斧!

提及割接,是讓DBA、開發、測試等人都談虎色變的事情,由於割接時間窗口緊張,割接資源消耗嚴重,割接要各類備份,割接產生大量日誌,跑錯了要回滾........總之風險有點高。


1.1 備份、臨時表不寫日誌

割接中須要大量的備份、拍照,減小寫日誌能夠有效提升效率,也能減輕數據庫的歸檔壓力,由於咱們不少庫上還有DSG同步,減小日誌量也能避免DSG分析延遲。



3.ddl 代替 dml,減輕undo壓力

將A表的數據所有插入到新建的B表,推薦的作法:

create table B tablespace tbs_data nologging as select * from B;

rename 和 CTAS (create table as select)建表是在割接場景中很受歡迎的作法


1.2 批量提交,下降UNDO/REDO壓力

割接的時候有不少場景須要對大表進行DML,一次性執行大表的DML通常效率較低,並且undo表空間壓力很大,一旦取消,大事物回滾很是消耗性能,可能會影響後續割接,而用遊標進行一條條的提交,一樣會形成redo的IO問題,也可能形成大量的log file sync事件。所以對大表進行全量或者是近全量DML時咱們建議採用批量提交。



1.3 提升聚簇因子,只爭朝夕!

在某個每個月一次的維護做業下有如上一段腳本,本質上就是對大表I_USER_STATUS_CENTER進行按條件的批量更新,表I_USER_STATUS_CENTER上有約16億數據,且MSISDN列上有索引,distinct number約1.1萬全部,也就是每次循環符合條件的數據:16億/1.1萬=15萬。


都按照步驟作了,但是跑了2個小時也沒有跑完........


執行計劃也看過了,走索引,沒問題!表的屬性暫時改爲nologging了,不寫日誌了!夠了吧?!但是速度仍是不行,一分析,預計72小時以上.........在覈心庫上執行這樣的做業哪裏能夠忍受?!該作的都作了,難道沒辦法再提速了麼?


前面有個例子,操場上撿玻璃球,如今不是一個一個的撿,而是一組一組的撿,每組按照顏色不一樣來撿,一次撿1.5萬,若是這每組球都分散在不一樣的地方,有了索引又如何呢,撿這麼多還不是累死人?!若是說每組的球都堆在一個地方,不是分散到各個角落,那麼是否是會快許多呢?!


這就是提升聚簇因子。


(1)將目標表按照MSISDN排序重建。

Alter table I_USER_STATUS_CENTER rename to I_USER_STATUS_CENTER_bak;

Create table I_USER_STATUS_CENTER tablespace tbs_data parallel 10 nologging as

Select /*+parallel(a,10)*/* from I_USER_STATUS_CENTER_bak a order by MSISDN;

Create index ind_ USER_STATUS_CENTER1 on I_USER_STATUS_CENTER(MSISDN) tablespace tbs_data parallel 10 nologging;

Alter table I_USER_STATUS_CENTER noparallel;

Alter index ind_ USER_STATUS_CENTER1 noparallel;


(2)MSISDN列上創建索引

(3)執行上面的腳本1.5小時完成

(4)修改表和索引的日誌屬性

Alter table I_USER_STATUS_CENTER logging;

Alter index ind_ USER_STATUS_CENTER1 logging;

(5)還能夠更快麼?

能夠的!能夠將遊標按照業務字段net_id進行範圍拆分,如:


SELECT NET_ID||HLR_SEGMENT BILL_ID,REGION_ID FROM RES_NUMBER_HLR where net_id >=1 and net_id<100;

SELECT NET_ID||HLR_SEGMENT BILL_ID,REGION_ID FROM RES_NUMBER_HLR where net_id >=100 and net_id<200;

........


拆分紅8個遊標,同時將這段程序放在不一樣的窗口執行,那麼15分鐘以內咱們這個做業就會完成啦!


2.總結

本期的漫談SQL優化就暫時講到這裏了,這裏經過幾個例子但願能給你們一點啓發。在SQL優化過程當中每每都不是那麼順利的,大部分狀況下都是跟系統問題、業務需求、物理模型等緊密相連的,須要分析考慮的地方不少。兵無常勢,水無常形。咱們這裏也沒有萬能公式,不過咱們IT人有認真負責、靈活運用、鑽研到底的態度,因此不少難題咱們最終都能迎刃而解!

相關文章
相關標籤/搜索