閱讀495 返回首頁    go 阿裏雲 go 技術社區[雲棲]


見微知著:一條 SQL 性能問題引發的核心係統悲劇

640?wx_fmt=jpeg&tp=webp&wxfrom=5&wx_lazy
黃廷忠(網名:認真就輸)

雲和恩墨技術專家

個人博客:https://www.htz.pw/


本篇整理內容是黃廷忠在“雲和恩墨大講堂”微信分享中的講解案例,SQL 優化及 SQL審核,是從源頭解決性能問題的根本手段,無論是開發人員還是DBA,都應當持續深入的學習 SQL 開發技能,從而為解決性能問題打下根基。


第一篇為:性能為王:SQL標量子查詢的優化案例分析

第二篇為:SQL審核:OR展開與子查詢優化案例詳解


本篇為係列案例之三:IN子查詢返回結果集很小


這是不久前在一個客戶現場遇到的一條 SQL 性能問題,此 SQL 子查詢結果集返回最多10行,但是整個 SQL 的性能卻不好,此 SQL 最後還導致了一個核心係統故障,引起了一個悲劇的事情。

 

業務反應慢,查詢 v$session 發現同時有24個會話在執行此 SQL:7ug8q9myb0bsz,由於此 SQL 性能不好引起大量的 GC 等待,導致其它的業務受影響。

SQL性能問題診斷


下麵直接給出常量的 SQL

Child_number 0


640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=

 

Child_number 1

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=


首先說明一下,是 OLTP 環境。也就意味著要快速的返回結果,並且多數情況下,SQL 返回的結果集不多。

 

在 SQL 切圖中,有兩處我們用紅色的箭頭標識出來,這部分信息需要我們關注的。在整個 SQL 中,就隻存在2處過濾信息,一個是 redu_owner_id,一個是 status_cd。但是 status_cd 在兩個子執行計劃中都是相同的,所以這裏就隻剩餘 redu_owner_id 這列了,我們也可以執行 redu_owner_id 所在的 OP 這個表,肯定是驅動表,並且 redu_owner_id 這列應該存在數據傾斜的情況那麼 redu_owner_id 返回的結果集將直接影響整個 SQL 性能的好壞。

 

下麵繼續查看 SQL 部分,可以發現一個重要的信息就是在子查詢中存在 rownum<10,也就意味子查詢最多返回10行。在 OLTP 係統中,存在一個表最後最多返回10行的情況,這裏也就大概想到了用子查詢做去驅動表了,如果執行計劃中,沒有用子查詢做驅動表,那麼很有肯能執行計劃就是錯誤的,那麼這裏的自己認為的驅動表與之前根據 SQL 前部分猜測出來當前執行的驅動表(OP)不一樣。

 

下麵查看執行計劃 

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=


在執行計劃中,我們看到當前執行計劃的驅動表示 OFFER_PROD(OP) 這個表,與之前我們猜想一樣,那麼基本可以肯定,redu_owner_id 列的數據存在傾斜,當返回大量結果集時,性能就很不好。

 

在執行計劃中,這裏特意把子查詢標記出來,就是需要引起重視,子查詢當著一個整體與主查詢做 HASH 連接,沒有作為驅動表走 NL,也就可以肯定整個執行計劃連最基本的驅動表都選擇錯誤。下圖可以更直觀的看到。 

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=

這裏做個補充:子查詢當著整體,也就是被當著一個視圖與主查詢做關聯,什麼情況下子查詢會當著一個整體呢? 

其實 MOS 有相關的文檔說明的,大家可以去 MOS 一下,在本案例是由於 ROWNUM<10 導致的。

基礎信息分析


在 V$SQL 中查看每個 child 的統計信息


640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=

這裏看到,存在兩個子遊標,他們的執行計劃相等,但是兩個子優化的性能相差很大,並且性能不好的子優化執行次數很多。 

在上麵我們提到主查詢就隻存在兩個過濾條件。執行計劃+謂詞信息可以看到驅動表使用那個列來過濾數據。


640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=
 

在上麵一直在說 redu_owner_id 這個列存在數據傾斜,那麼下麵來證實一下:

 

下麵來查看,redu_owner_id 的值的分布,兩個不同綁定變量返回的行數: 

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=

通過這個信息,我們知道了,上麵 SQL 由於列的值存在傾斜,導致 SQL 執行計劃部分值執行很快,部分值執行很慢。

 

大家可能會說,在11G中,SQL 引入了 ACS 功能,但是很不幸的事在客戶這裏 ACS 都是禁用了的。

SQL 的修改


下麵就是怎麼來優化這個 SQL,在上麵提到了子查詢中最多返回10行,可以用於做NL的驅動,要讓子查詢的表做驅動表,應該怎麼來修改 SQL?

在上一個案例中,通過了with as 的方式來改寫。 

這個案例就不修改 SQL,通過提示(Hints)來達到目的。


這裏使用 cardinality 提示,在 SQL 解析的時候告訴 CBO 表上存在多少行。表上存在的行數越少,也就意味著訪問表的成本越低。

 

下麵我們拿返回8611行的綁定變量來做測試

 

添加提示後的 SQL 如下:


640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=

 

紅色部分就是添加的提示,執行計劃如下:

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=

 

可以看到,子查詢的結果集已經作為驅動表了。


性能優化效果對比

 統計信息 

640?wx_fmt=png&tp=webp&wxfrom=5&wx_lazy=


每次的邏輯讀從原來的369,927降低到現在的45 ,性能提升很明顯,並且主要解決了 RAC之間的 GC 等待,不影響其它的業務了。

 

優化 SQL 後,CPU 使用率從原來的70%左右直接下降到25%左右,此係統的主機性能很 NB 的,8路的 PC ,E7 的 CPU。


總結

此案例結束,主要提到兩個知識點:

  1. 列的值分布不均勻,導致 SQL 性能出問題

  2. 通過 cardinality 來指定表的行數,達到指定表做驅動表的目的


SQL優化是一項專業的技能,必須深入了解數據庫原理才可能做出準確的判斷,在開發過程中強化開發人員培訓,引入SQL審核、審計,是確保應用高性能的有力手段。 文章轉自數據和雲公眾號,原文鏈接

最後更新:2017-07-18 20:36:42

  上一篇:go  SQL審核:OR展開與子查詢優化案例詳解
  下一篇:go  SQL為王:oracle標量子查詢和表連接改寫