背景
每當交易高峰時期,可能會暴露一些平時無法發現的問題,機遇和挑戰並存。下面聊聊最近解決的一個案例,因為執行計划走錯導致慢查詢,進而引發應用線程阻塞、線程池爆滿,最后應用功能癱瘓。如何標本兼治的解決問題,需要很多思考。
問題分析
step1 應用癱瘓
用戶反應某查詢功能一直處於加載中,並出現錯誤提示。查看后台應用日志,調用遠程查詢服務出現大量超時。
step2 線程池爆滿
通過jstack命令來分析查詢服務jvm線程堆棧,發現設定的線程池已經滿了,而且大部分線程阻塞在了數據庫查詢階段(如下圖),看來是有很多慢查詢。

step3 慢查詢的由來 - 錯誤執行計划
為何忽然出現這么多慢查詢?我們來分析一下:
SELECT no, timeFROM tablenamewhere no = :1and time >= to_date(:2, 'yyyy-mm-dd')and time < to_date(:3, 'yyyy-mm-dd')ORDER BY time DESC
注:條件字段(no、time)分別有索引(idx_no、idx_time)。
經過DBA幫助,分析上段sql的執行計划發現:
-
有時數據庫會走idx_no索引,查詢很快

-
有時會走idx_time索引,查詢很慢

解決方案
既然數據庫自動生成的執行計划有時會出問題,那么我們可以用hints語句指定當前sql走哪條索引,以固定執行計划。
下面以Oracle為例,添加hints:

SELECT /*+ index(tablename IDX_NO) */ no, timeFROM tablenamewhere no = :1and time >= to_date(:2, 'yyyy-mm-dd')and time < to_date(:3, 'yyyy-mm-dd')ORDER BY time DESC
如果感覺在sql中添加不方便的話,oracle 10g以上版本提供了SQL Profile方式。
總結思考
這個問題應該一直有,為何在交易高峰時段才表現出來值得去研究。
數據庫:平時數據庫壓力較小,出現幾個走錯執行計划的慢查詢,也只是讓數據庫壓力比均值高出一小部分,性能未受影響。但是,高峰時段數據庫本身就處於一個高負載狀態,這時出現的慢查詢無疑是雪上加霜,接近某個臨界點時性能就明顯下滑。
應用:慢查詢會導致線程阻塞等待,一直占用線程池資源。我們知道,用戶在點擊查詢按鈕后發現一直加載,會不知覺多點幾遍。這樣的話,線程數量暴漲且大部分處於阻塞狀態,線程池滿了之后應用就會癱瘓掉,使其他正常的接口也無法正常工作。
綜上所述,數據層的優化是治本,同時還要借助一些手段來保證不出現雪崩式性能下降。比如,在前台頁面添加重復提交限制,盡量避免短時間內產生大量慢查詢。
參考資料
Oracle Hints:https://docs.oracle.com/cd/B12037_01/server.101/b10752/hintsref.htm#5156
Oracle SQL Profile:https://docs.oracle.com/database/121/TGSQL/tgsql_profiles.htm#TGSQL596
