優化器在數據庫中一直位於相當重要的位置,性能調優也經常須要圍繞優化器來進行。做爲數據庫廠商,咱們但願在各種複雜的業務場景中,TiDB 都可以給出比較理想的執行計劃,所以在優化器和執行器上作了很是多的工做和努力,可是選錯執行計劃或者索引的狀況仍然是平常中最爲常見的一個問題。git
優化器有關的問題能夠簡單歸結爲兩種:github
選錯索引是其中比較常見的一種狀況,用戶但願添加索引來加速查詢速度,某些狀況下,優化器可能會走到全表掃的物理執行計劃或者選錯索引使得實際執行效果退化成全表掃的狀況。算法
針對上述狀況,咱們須要從更微觀的層面來度量優化器的執行計劃和索引選擇的性能,評估在優化器上作的改進工做可否切實起到指望的效果。數據庫
爲了測量優化器和執行器,從去年開始咱們構建了daily benchmark 平臺 perf.pingcap.com,覆蓋常見的幾種複雜查詢的測試場景,包含 TPC-H、TPC-DS、Star Schema Benchmark 等,跟蹤天天開發分支上這些查詢的執行速度狀況。tcp
經過 daily benchmark,咱們觀測和定位到了若干次性能提高以及性能回退的狀況。有些提高或者回退是優化器組件上的優化致使的,有些則是 TiDB 其餘組件,或者存儲層引起的。工具
雖然 daily benchmark 可以觀測到性能改進或者回退,可是對於如下幾個問題它卻一籌莫展:性能
所以,咱們須要另一種更系統的測試工具,用於優化器的測量。測試
要測量優化器,咱們須要:優化
這裏咱們參考「OptMark: A Toolkit for Benchmarking Query Optimizers」給出的方法來度量優化器有效性。簡單地講某個查詢的有效性指標,是指在可遍歷的執行計劃空間中,優化器選出的默認執行計劃的執行時間比其餘的執行計劃的執行時間更快的比例。ui
例如 100% 能夠解釋爲默認執行計劃的執行時間比其餘執行計劃的執行時間都更快,50% 解釋爲有一半的執行計劃要比默認執行計劃更快。
因爲須要一種方式可以讓 TiDB 按照咱們所指定的物理執行計劃來實際執行查詢,爲此咱們在 TiDB 中添加了 nth_plan(n)
這個 SQL hint。
當查詢語句提交到 TiDB 後,TiDB 會爲搜索空間中的每一個執行計劃綁定一個固定的序號,經過這個序號咱們就能指定優化器去選擇哪個執行計劃。
nth_plan 的序號從 1 開始遞增,當其超出優化器對該條查詢的搜索空間時,查詢返回會產生一個 warning 來提示當前已經完成了搜索空間上的遍歷。
TiDB(root@127.0.0.1:test) > explain select /*+ nth_plan(1) */ * from t where a = 1 and b > 0 and b < 10; +-------------------------+----------+-----------+---------------+----------------------------------------------------+ | id | estRows | task | access object | operator info | +-------------------------+----------+-----------+---------------+----------------------------------------------------+ | TableReader_7 | 0.25 | root | | data:Selection_6 | | └─Selection_6 | 0.25 | cop[tikv] | | eq(hehe.t.a, 1), gt(hehe.t.b, 0), lt(hehe.t.b, 10) | | └─TableFullScan_5 | 10000.00 | cop[tikv] | table:t | keep order:false, stats:pseudo | +-------------------------+----------+-----------+---------------+----------------------------------------------------+ 3 rows in set (0.00 sec) TiDB(root@127.0.0.1:test) > explain select /*+ nth_plan(2) */ * from t where a = 1 and b > 0 and b < 10; +-------------------------------+---------+-----------+-------------------------+---------------------------------------------+ | id | estRows | task | access object | operator info | +-------------------------------+---------+-----------+-------------------------+---------------------------------------------+ | IndexLookUp_11 | 0.25 | root | | | | ├─IndexRangeScan_8(Build) | 10.00 | cop[tikv] | table:t, index:idx_a(a) | range:[1,1], keep order:false, stats:pseudo | | └─Selection_10(Probe) | 0.25 | cop[tikv] | | gt(hehe.t.b, 0), lt(hehe.t.b, 10) | | └─TableRowIDScan_9 | 10.00 | cop[tikv] | table:t | keep order:false, stats:pseudo | +-------------------------------+---------+-----------+-------------------------+---------------------------------------------+ 4 rows in set (0.00 sec)
互聯網上有不少開放的數據集,也有一些 benchmark 提供了 dbgen 工具用來隨機構造數據集,咱們比較傾向於選擇真實數據集,所以咱們選擇了 IMDB 數據集來進行測試。
有了數據集,咱們須要在其上構造一些查詢。爲了測試索引選擇問題,參考 Manuel Rigger 的 「Testing Database Engines via Pivoted Query Synthesis」 論文中的思路,Horoscope 會在某些表中隨機選擇一行數據做爲 pivot row 去構建查詢,使得查詢返回的結果會包含這些選擇的行。經過這種方式,咱們能保證生成的查詢是更具意義。
例如針對索引選擇問題,查詢構造的流程以下所示,經過在有索引覆蓋的列上構造條件來測試是否選對了索引。
例如會生成以下的查詢:
咱們預先導入了一份 IMDB 數據集到 imdb 數據庫中,能夠經過以下命令使用 Join Order Benchmark 的查詢度量有效性指標。
$ git clone https://github.com/chaos-mesh/horoscope.git $ cd horoscope && make $ ./bin/horo --round 4 -d root:@tcp(localhost:4000)/imdb?charset=utf8 bench -p -w benchmark/job
通過漫長的等待,在測量結束時 Horoscope 會出入一份測試報告:
ID
列標識查詢的名稱,#PLAN SPACE
是這條查詢當前 TiDB 的搜索空間,DEFAULT EXECUTION TIME
記錄了默認執行計劃的執行時間(經過中值以及上下界誤差比例給出),BEST PLAN EXECUTION TIME
給出最優的執行計劃的執行時間,EFFECTIVENESS
算出該條查詢優化器的有效性,BETTER OPTIMAL PLANS
給出更優的執行計劃的 ID 以及對應執行時間和默認執行計劃執行時間的佔比。
咱們使用 Horoscope 測量了不一樣數量級的 TPC-H,而且 IMDB 數據集上針對索引選擇生成了一些查詢來測試。咱們也在 Github 上建立了一個項目來跟蹤這些問題的進展:https://github.com/orgs/pingcap/projects/29
相比於 TPC-H,Horoscope 在 IMDB 的數據集和查詢上發現了更多更優的執行計劃,但由於 IMDB 數據是靜態的,當想驗證統計信息過時場景下優化器的狀況時比較困難。
爲此 Horoscope 提供了將數據按照某個字段進行切分而後導出的功能,經過分批次插入數據,提供了數據更新狀況下的優化器測試場景。
真實數據集上的數據分佈每每具有傾斜的特徵,而這種傾斜的性質對於優化器也更有挑戰。
以 IMDB 爲例,數據在 title.produciton_year 上發生了傾斜,越靠後的年份,所關聯的數據行數越多。咱們經過對數據集在 title.prodution_year 上將數據集切分紅一塊塊不均等的切片,再進行分批導入,能夠模擬數據修改所引起的統計信息過時對於優化器的影響。
切分的過程以下:
在 IMDB 上,咱們選擇 title.produciton_year 進行數據切分,切分後每一個切片文件的大小以下圖所示。
約有一半的數據集中在最後 20 份切片中,越日後導入數據的修改行增速越快,統計信息的過時速度也愈快。
咱們設計了 2 個對照試驗,實驗開始以前預先導入切片 0 到切片 124 的數據,並從切片 125 開始,每導入一個切片,測量一輪各查詢的有效性指標。
在第一組試驗中咱們關閉了 auto analyze 和 feedback,第二組關閉了 auto analyze 但會打開 feedback。而後讓 Horoscope 隨機生成一批簡單查詢,在獲得數據後咱們分別繪製了有效性指標的比例曲線以及散點圖。
曲線上的點表示有效性指標大於橫座標數值的查詢的比例。從數據上看,當打開 feedback 時,有 50.77% 查詢的有效性指標超過了 80%,即對於一半以上的查詢優化器選擇到了較優的執行計劃。而當關閉 feedback 時,這個比例只有 38.70%。這和咱們一般所認爲的 feedback 可以必定程度抵抗統計信息過時相符。
另外從散點圖上看會發現打開 feedback 也有可能會讓優化器選擇到更差的執行計劃。例以下面的這條 SQL,feedback 機制反而使優化器選擇到了更差的執行計劃,這些能夠做爲 bad case 來具體分析。
SELECT * FROM title WHERE (title.id IS NOT NULL AND title.title!="(#1.69)" AND title.imdb_index IS NULL AND title.kind_id<8 AND title.production_year!=1974 AND title.imdb_id IS NULL AND title.phonetic_code IS NULL AND title.episode_of_id>184590 AND title.season_nr IS NULL AND title.episode_nr IS NULL AND title.series_years IS NULL AND title.md5sum<="7cf95ddbd379fdb3e530e0721ff61494") LIMIT 100
Horoscope 還能夠作更多的事情,例如當版本升級時,能夠用 Horoscope 來測試執行計劃會不會變化,若是變化了,是否發生了回退。
用戶線上的數據一般十分敏感,咱們內部積累了比較多的有統計信息和 schema 但無實際數據的用例集,經過 Horoscope 咱們如今但願可以將這些用例集利用起來,擴充優化器測試用例,來幫助優化器的開發者們決策一些優化策略是否要合併到下一版本中。
此外,Horoscope 也提供了一種測試優化器正確性的途徑。咱們正在計劃讓 Horoscope 生成更復雜的查詢,經過比對每一個物理執行計劃的結果來驗證優化器實現的正確性。
優化器的工做是個長期且難度很是大的事情,優化器的測試也是如此,若是您有更多更好的關於優化器或者其餘組件的優化以及測試的方法或者思路,歡迎在 TiDB 社區中和咱們進行交流。