當數據庫執行一條SQL時,從發起SQL到返回結果集,數據庫引擎通常有2種方式用來優化執行計劃(執行計劃詳見《窺探PostgreSQL執行計劃》),從而降低資源開銷提升響應時間。具體如下:
基于規則的優化器(rbo,rule based optimizer),在上古版本中的數據庫較為常見,比如過去常聽DBA說的SQL語句驅動表要放前面,被驅動表要放后面之類的云云,聽的好像很深奧,究其本質為數據庫引擎按照固定規則去優化執行計劃;這樣的規則往往較為簡單比如是否有索引、驅動表是否在前等等。舉個例子,汽車導航中始發地到目的地之間有多種路線,rbo中就是按距離規則來計算,不考慮實際路況比較傻瓜化,現在基本都已經淘汰。
基于成本的優化器(cbo,cost based optimizer),同樣的汽車導航例子,cbo中還需要參考路況是否存在堵車,是否存在限速等等,通過對比各種行程的耗時從而選擇最合適的路線。在數據庫中就是通過對比各種不同計劃(是采用hashjoin 還是nestloop,采用全表掃描還是索引掃描等等)的估算開銷(cost), 從中選擇開銷(cost)最低的計劃,相對RBO的呆板,CBO顯得較為智能,目前主流數據庫均采用CBO模式。
在CBO模式下,由于開銷都是估算值,所以精確性嚴重依賴于統計信息(比如表大小,行數,字段數據分布情況,是否存在索引),當表的統計信息越接近表的真實數據時,CBO優化器的估算值則越準確,產生的執行計劃也更佳準確 ; 當統計信息與真實數據差異過大時,CBO的估算值精確性將無法得到保證,這時往往產生錯誤的執行計劃,從而引發SQL性能類故障。
了解了CBO優化器以及統計信息的概述后,接下來我們將PostgreSQL為例進行實際案例演示:
先看一個簡單的執行計劃:
由于這個查詢沒有WHERE子句,它必須掃描表中的所有行,因此計劃器只能選擇使用一個簡單的順序掃描計劃。被包含在圓括號中的數字是(從左至右):
1. 0.00,估計的啟動開銷。在輸出階段可以開始之前消耗的時間。
2.11173.00,估計的總開銷。這個估計值基于的假設是計劃結點會被運行到完成,即所有可用的行都被檢索。
3.rows=500000,這個計劃結點輸出行數的估計值。同樣,也假定該結點能運行到完成。
4.width=70,預計這個計劃結點輸出的行平均寬度(以字節計算)。
注意各個版本計算公式可能存在稍許差異,大致可以參考如下公式
總成本=seq_page_cost*relpages+cpu_tuple_cost*reltuples
這里涉及了postgresql幾個參數:
seq_page_cost: 連續塊掃描操作的單個塊的cost. 例如全表掃描 random_page_cost: 隨機塊掃描操作的單個塊的cost. 例如索引掃描 cpu_tuple_cost: 處理每條記錄的CPU開銷(tuple:關系中的一行記錄) cpu_index_tuple_cost:掃描每個索引條目帶來的CPU開銷 cpu_operator_cost: 操作符或函數帶來的CPU開銷. |
當前實例參數默認設置:
當前表統計信息:
帶入公式計算:
1*6173+0.01*500000 =11173;與前文Cost值11173對應。
再來看一個索引掃描的執行計劃:
這里是索引掃描,注意各個版本計算公式可能存在稍許差異,大致可以參考如下公式:
cost= 2*random_page_cost + cpu_tuple_cost +cpu_index_tuple_cost +100* cpu_operator_cost
其中random_page_cost *2 為先讀取indexpage, 再回表 datapage從而進行了2次隨機IO。
我們在來看一個統計信息差異過大導致的執行計劃錯誤的案例:
可以看到在行數較少時,雖然tid字段有索引,但是CBO考慮到索引的隨機掃描(random_page_cost)且需要加上回表(再次random_page_cost)的總Cost大于全表的seq_page_cost,則執行計劃選擇了錯誤的全表掃描方式。
注:
Cost全表=1* 4+ 59*0.01 = 4.59,與圖中估算值1.74存在些許差異
Cost索引=2*4+0.01+0.25=8.26,與圖中估算值8.44存在些許差異
由于全表Cost比索引Cost低,則CBO優化器將選擇全表掃描方式.
在我們發起analyzetable更新統計信息后,CBO根據page以及tuple重新進行Cost計算并及時更新執行計劃為索引掃描。
注:
Cost全表=3703*4 + 299908*0.01 =15111;大致估算值
Cost索引=2*4+0.01+0.25=8.26,與圖中估算值8.44存在些許差異
由于索引Cost比全表Cost低,則CBO優化器將選擇索引掃描方式.
由于統計信息的準確性將直接影響到CBOCost計算的準確性,可以確認統計信息的維護工作將是DBA日常工作中的重中之重。下面我們在來看一下PostgreSQL中的統計信息知識點。
PostgreSQL的統計收集器是一個支持收集和報告服務器活動信息的子系統。目前這個收集器可以對表和索引的訪問計數,計數可以按磁盤塊和個體行來進行。它還跟蹤每個表中的總行數、每個表的清理和分析動作的信息。它也統計調用用戶定義函數的次數以及在每次調用中花費的總時間。因為統計信息的收集給系統增加了一些額外負荷,系統可以被配置為自動收集或部分收集或不收集信息。這由配置參數控制,如下:
track_activities允許監控當前被任意服務器進程執行的命令。 track_counts 控制是否收集關于表和索引訪問的統計信息。 track_functions啟用對用戶定義函數使用的跟蹤。 track_io_timing啟用對塊讀寫次數的監控。 |
這里我們將只對表級統計信息收集進行介紹:
語法如圖:
Verbose選項輸出詳細的INFO信息,包含正常行,已刪除的行,數據page等等信息。
注:由于pg的mvcc采用tuple模式,刪除的行只是標記為delete狀態,數據仍然保留在表中,通過deadrows可用用來作為vacuum的判斷依據,這里vacuum不在展開介紹。
表的統計信息收集采樣比例可以在表列級指定或使用系統參數default_statistics_target值;該值范圍在1-10000,可以理解為值越大采樣比例越高,耗時越長,默認值為100。日常運維過程中當發現采樣比例不夠時可在表列級擴大該值。
一旦指定列的statistics值后,PostgreSQL就不再參考默認的default_statistics_target值,它會先去系統表pg_attribute的對應表對應字段的attstattarget值,如果是-1,表示的是該列的取樣顆粒度是采用默認的值(default_statistics_target),如果是大于0的,那么就表示是使用著自己手動定義的,比如我們修改t_user.tid通過STATISTICS150,查看attstattarget值的變化:
圖中tid列的statisticstarget值已更新為150.以上就是一個從CBO優化器到統計信息的一個完整了解過程,本文就到此為止,咱們下回見。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/130118.html
摘要:概述本文是源碼閱讀系列文章的第八篇。圖中黑色字體算子為邏輯算子,藍色字體為物理算子,黃色箭頭為已經計算過代價的算子,會獲取已經緩存在哈希表中的結果,紅色虛線箭頭為不符合的算子。 概述 本文是 TiDB 源碼閱讀系列文章的第八篇。內文會先簡單介紹制定查詢計劃以及優化的過程,然后用較大篇幅詳述在得到邏輯計劃后,如何基于統計信息和不同的屬性選擇等生成各種不同代價的物理計劃,通過比較物理計劃的...
閱讀 1346·2023-01-11 13:20
閱讀 1684·2023-01-11 13:20
閱讀 1132·2023-01-11 13:20
閱讀 1858·2023-01-11 13:20
閱讀 4100·2023-01-11 13:20
閱讀 2704·2023-01-11 13:20
閱讀 1385·2023-01-11 13:20
閱讀 3597·2023-01-11 13:20