天天看點

一次PostgreSQL行估算偏差導緻的慢查詢分析 本文為DBAPlus投稿文章, 原文連結: http://dbaplus.cn/news-19-1514-1.html 一次PostgreSQL行估算偏差導緻的慢查詢分析

最近某業務系統上線了新功能,然後我們就發現PostgreSQL日志中多了很多慢查詢。這些SQL語句都比較相似,下面是其中一個SQL的explain analyze執行計劃輸出。

一次PostgreSQL行估算偏差導緻的慢查詢分析 本文為DBAPlus投稿文章, 原文連結: http://dbaplus.cn/news-19-1514-1.html 一次PostgreSQL行估算偏差導緻的慢查詢分析

這個SQL執行了18秒,從上面的執行計劃不難看出,時間主要耗在兩次嵌套join時對子表的順序掃描(圖中藍線部分)。乘以5429的循環次數,每個join都要順序掃描2000多萬條記錄。

既然是順序掃描惹的禍,那麼在join列上加個索引是不是就可以了呢?

但是檢視相關表定義後,發現在相關的表上已經有索引了;而且即使沒有索引,PG也應該可以通過Hash join回避大量的順序掃描。

再仔細看下執行計劃裡的cost估算,發現PG估算出的rows隻有1行,而實際是5429(圖中紅線部分)。看來是行數估算的巨大偏差導緻PG選錯了執行計劃。

一次PostgreSQL行估算偏差導緻的慢查詢分析 本文為DBAPlus投稿文章, 原文連結: http://dbaplus.cn/news-19-1514-1.html 一次PostgreSQL行估算偏差導緻的慢查詢分析

通過嘗試,發現問題出在下面的過濾條件上。不加這個過濾條件估算行數和實際行數是基本吻合的,一加就相差的離譜。

而上面的zsite的資料類型是char(10),tmall_flg的資料類型是int,難道是類型轉換惹的禍? 在測試環境把嘗試去掉SQL裡的類型轉換,發現執行時間立刻從10幾秒降到1秒以内。看來原因就是它了。

==》

生産環境下,因為修改應用的SQL需要時間,臨時采用下面的回避措施

即把zsize的類型從char(10)改成varchar(10)(varchar到text的類型轉換不會影響結果行估算)。由于沒有改tmall_flg,修改之後,估算的行數是79行,依然不準确。但是這帶來的cost計算值的變化已經足以讓PG選擇索引掃描而不是順序掃描了。修改之後的執行時間隻有311毫秒。

一次PostgreSQL行估算偏差導緻的慢查詢分析 本文為DBAPlus投稿文章, 原文連結: http://dbaplus.cn/news-19-1514-1.html 一次PostgreSQL行估算偏差導緻的慢查詢分析

PG通過收集的統計資訊估算結果行數,并且收集的統計資訊也很全面,包括唯一值數量,頻繁值分布,柱狀圖和相關性,正常情況下應該是比較準确的。看下面的例子

無where條件

全表資料的估算值來自pg_class

估算值和實際值的誤差隻有5%左右

帶等值where條件

帶where條件後,PG根據pg_stats收集的列值分布資訊估算出where條件的選擇率。tmall_flg = 1屬于頻繁值,most_common_freqs中直接記錄了其選擇率為0.258133322

結合總記錄數,可以算出估算結果行數。

估算值和實際值的誤差隻有1%左右

帶等值where條件,且條件列帶類型轉換

一旦在條件列上引入包括類型轉換,函數調用之類的計算,PG就無法通過pg_stats計算選擇率了,于是籠統的采用了一個0.005的預設值。通過這個預設的選擇率計算的結果行數可能會和實際結果行數有巨大的偏差。如果where條件中這樣的列不止一個,偏差會被進一步放大。

src/include/utils/selfuncs.h:

src/backend/utils/adt/selfuncs.c:

在條件列上引入計算帶來的危害:

該列無法使用索引(除非專門定義與查詢SQL比對的表達式索引)

無法準确評估where條件比對的結果行數,可能會引發連鎖反應進而生成糟糕的執行計劃

回避方法:

規範表的資料類型定義,避免不必要的類型轉換

将計算從列轉移到常量上

比如:

改成

改成其它等價的寫法

也可以改成更簡潔的正規表達式

但是,正規表達式中如果帶了類似^$*這樣的内容,行數估算準确性也受一定的影響