PolarDB-X 的 XPlan 索引選擇

前言

對於數據庫來說,正確的選擇索引是基本的要求,選錯索引輕則導致查詢緩慢,重則導致數據庫整體不可用。PolarDB-X存在多種不同的索引,局部索引、全局索引列存索引歸檔表索引

局部索引就是單機數據庫上常用的索引,目的是避免全表掃描。

全局索引是分佈式數據庫爲了避免全分片掃描,冗餘一份數據,採用與主表不同分區鍵的索引表。

列存索引是主表的列存副本,提供HTAP能力。

歸檔表索引是歸檔表上的列布隆過濾器,爲歸檔表提供一定的TP查詢能力。

本文主要介紹一種CN上的局部索引算法:XPlan索引選擇。

什麼是XPlan

PolarDB-X包含計算節點(CN)和數據節點(DN),CN負責SQL解析、優化和執行,DN節負責數據的持久化,CN與DN之間通過RPC通信。DN 100%兼容Mysql,也是作爲PolarDB-X標準版進行售賣的。

CN與DN之間RPC通信的內容其實就是標準的SQL,CN會將解析優化好的語法樹轉成SQL傳給DN重新解析、優化。對比起來,將CN的語法樹直接傳給DN執行聽起來就更優[1]。

但這樣其實不一定好,主要原因是作爲存算分離的架構,數據都在DN上,DN可以直接在數據上進行index dive,而CN的統計信息是採樣出來的靜態數據,更新不及時,所以基數估計比不上DN精確,導致索引選擇準確度不如DN,在很多場景下節省的DN解析優化的消耗遠不如選錯索引的後果。

但對於用戶核心的點查場景,這樣的CN優化一遍DN再優化一遍的流程就會成爲瓶頸,所以PolarDB-X提供XPlan機制:對於點查場景,直接傳輸執行計劃交給DN執行。

這樣的定位說明XPlan不是必須的能力,而是錦上添花的能力。目前XPlan的適用範圍被限定爲單張表的DQL,只支持Scan、Filter和Project算子。

XPlan在Sysbench點查上有10%以上的提升,但線上在用戶的真實場景下XPlan索引錯選導致的慢查詢問題頻發。對於PolarDB-X來說,選錯索引有兩種可能:基數估計錯誤和執行計劃緩存下的傾斜索引。

基數估計錯誤的三個常見原因統計信息缺失、傾斜數據和關聯列,學術界、工業界研究了幾十年都無法解決[2]。這些問題雖然無法解決,但是很容易檢測到,PolarDB-X基本策略是檢測到這些問題就禁用XPlan,交給DN做局部索引選擇。同樣發現索引錯選也是容易的。通過預先和事後的檢測,希望儘量減少XPlan錯選概率。

PolarDB-X的優化器與索引選擇

下圖是一條sql過PolarDB-X優化器的大致過程:經過RBO和CBO後生成最好的單機執行計劃,並基於CBO產生的最優執行計劃的代價判斷當前查詢是否爲AP查詢,如果不是AP查詢則直接構造單機執行計劃,否則進一步考慮是否可以走列存索引。

無法走列存索引則基於最優單機執行計劃插入shuffle算子構造分佈式執行計劃,否則將基於列存索引構造最優分佈式執行計劃。

局部索引、全局索引、歸檔表索引選擇都在CBO裏,局部索引選擇影響的是Logicalview算子的IO代價,全局索引選擇會將掃描主表的執行計劃替換爲全局索引回表,歸檔表索引選擇可以將過濾條件複雜無法走索引的歸檔表掃描替換爲多個簡單走索引的歸檔表掃描。列存索引選擇是利用列存對AP查詢重新生成最優的分佈式執行計劃。

XPlan索引選擇則是在單機優化器的最後對logicalview中進行索引選擇。這與CBO裏的局部索引選擇不同,CBO裏的局部索引選擇隻影響Logicalview算子的IO代價進而影響整個執行計劃的代價,是CN基於自己的統計信息模擬DN做索引選擇的過程,並不是DN真正使用的索引,只有XPlan會指定DN的索引。

PolarDB-X的執行計劃緩存與傾斜值問題

PolarDB-X的執行計劃獲取大致邏輯如下

getPlan(String sql) 
    if PlanCache doesn't contain sql :
        PlanCache.put(sql, getPlanByOptimizer(sql))
    Plan =  PlanCache.get(sql)
    if PlanManager contiain sql :
        Plan = PlanManager.choose(sql)
    return Plan

所有的執行計劃都會緩存在PlanCache中,如果PlanManager中有執行計劃,則由PlanManager選擇代價最低的執行計劃。

這篇文章提及了Optimize Once和Optimize Always的概念,PolarDB-X採用的理念就是Optimize Once,儘量少進入優化器,主要的考量是PolarDB-X的優化器結構相當複雜,如果採用Optimize Always,優化器的耗時在高併發tp的查詢中代價將無法忽視。

這裏回顧一下Parameterized Queries的常見問題,考慮以下場景

create table hot_select (
    id int not null,
    c_int int,
    c_varchar varchar(20),
    PRIMARY KEY (`id`),
    KEY i_int(c_int),
    KEY i_varchar(c_varchar)
)

select * from hot_select where c_int = 1 and c_varchar = 'a';
select * from hot_select where c_int = 2 and c_varchar = 'a';

若滿足c_int = 1的數據有1行,滿足c_varchar = 'a'的數據有100行,滿足c_int = 2有10000000行,則第一條查詢應該走索引i_int,第二條查詢應該走索引i_varchar。

但兩條查詢共用了同一個sql模版,同一個sql模版只會Optimize Once,這兩條sql都只會走i_int,導致第二條查詢事實上走錯了索引。

這個問題學術界已經提出了很多解決方案[3],PolarDB-X之前已經在線上驗證過論文裏面的部分方案,設計了下圖所示的一套反饋和演化的機制,由於執行計劃飄忽不定導致rt不穩定,最後導致反饋演化功能被關閉。TiDB也做過類似的嘗試,也是強制關閉的狀態。

基於大部分學術界方案生產上不可用的事實和XPlan的錦上添花定位,Xplan索引選擇的設計都以不負優化爲前提,PolarDB-X採取的方案有點類似於[4],不同點在於XPlan會考慮期望基數,而是最大基數。

當然同樣的問題也出現在全局索引選擇上,但是由於全局索引選擇的必要性,XPlan的方案並不適用,PolarDB-X有一套不同的方案來處理全局索引的傾斜值問題,在後續文章會進一步展開。

XPlan索引選擇算法

XPlan核心問題有兩個:如何選擇索引以及如何進行執行計劃傳輸和執行。執行計劃傳輸和執行的大致邏輯如下圖所示:在算子樹上將filter儘量下推,用filter-XplanScan的pattern進行索引選擇並記錄到XplanScan中,基於算子樹填充protobuf,利用私有協議傳輸給DN解析出來後直接對Innodb數據進行讀取和過濾。

由於本文的主旨是XPlan索引選擇而不是XPlan,這個部分不再展開,後面主要介紹如何進行XPlan的索引選擇。

XPlan索引選擇會盡量減少錯選的概率,具體流程下圖所示: 首先檢查當前表的統計信息是否過期,由於統計信息可能因爲各種原因無法自動更新,沒有統計信息的索引選擇就是亂猜,所以統計系信息過期之後會禁用XPlan,有個小優化是pk、uk的查詢不受此影響。

統計信息過期的時限是7天,內核每天都會自動檢查並收集3天未更新的統計信息,並在完成後再次檢查統計信息,依然存在超過3天未更新的表則會發出內核報警。這個判斷會減少統計信息缺失導致的基數估計錯誤。 第二步是過濾可能的傾斜索引,統計信息模塊提供能力檢查給定的列集合是否存在傾斜值,傾斜列的索引不會被XPlan使用。

這個過濾會減少Plan Cache導致的傾斜值問題。關聯列估算錯誤一般是由於列間獨立性假設的選擇率迭乘導致基數估計過小,由於傾斜列被過濾,也不會出現關聯列導致的基數估計過小。 第三步利用基數估計模塊挑選選擇率最好的索引,只有足夠好的索引纔可以走XPlan。

由於XPlan是Robust Query Optimization而不會選最好的索引,所以可能選不出好索引,這種情況下也會直接禁用XPlan。 最後將選擇出的索引記錄到XplanScan中,到此XPlan的索引選擇就完成了。

再考慮一下之前的例子,由於c_int存在傾斜,XPlan不會再選擇i_int而是會選擇i_varchar,從而避免了傾斜值問題。

create table hot_select (
    id int not null,
    c_int int,
    c_varchar varchar(20),
    PRIMARY KEY (`id`),
    KEY i_int(c_int),
    KEY i_varchar(c_varchar)
)

select * from hot_select where c_int = 1 and c_varchar = 'a';
select * from hot_select where c_int = 2 and c_varchar = 'a';

傾斜值判斷

傾斜值也就是所謂的skew data,在XPlan的場景下,只需要考慮所有索引的前綴列的組合是否有傾斜。 PolarDB-X的採樣對於一張表會採出10萬行數據,採樣出來的頻率大於5且頻率/採樣率大於1萬就會被判斷成傾斜值。

這個傾斜值判斷的邏輯有改進的空間,且對抗sample的穩定性也不夠強,但目前來說還是能夠取得預期的效果。 那麼算法就很簡單了,窮舉n個索引的所有前綴列,判斷其在sample出的10萬行中最大頻率是否滿足上述條件即可。若索引平均列數爲m,則時間複雜度爲O(1e5*nm),這個時間可以忽略不計了。

當然還有更細的優化,比如傾斜列的前綴一定是傾斜列,更大的列集合優先判斷供後續剪枝之類的,不再贅述。 額外提一句PolarDB-X採樣採用的是block sampling[5],在Innodb的主鍵上Random Walk出一些page,對於主鍵是天然傾斜的(特別是複合主鍵),所以主鍵的前綴列不會做傾斜值判斷。

回退機制與可觀測性

鑑於DN的index dive能力對於單張表的估算有更好的表現,PolarDB-X選擇的兜底策略是DN返回XPlan在Innodb上掃描的行數,CN一旦發現XPlan在索引上掃描的行數超出閾值,則關閉當前sql模版的XPlan,併發出報警。

後續12小時內對應sql模版都不會再走XPlan。這個簡單的機制對於只有Plan Cache的數據庫也同樣有效:發現Plan Cache的查詢出現異常慢的情況,可以對這個模版禁用Plan Cache。

PolarDB-X支持explain execute語法查看DN物理索引。對於XPlan,explain execute會將XPlan的上下文一直傳遞到執行器下發物理sql之前將其攔截,否則會在XPlan的上下文中設置無法XPlan並走回正常物理sql路徑。

由於回退機制的存在,explain execute可能與線上發生問題的狀態不一樣,排查就會變得比較困難,所以在日誌中會記錄每個XPlan走的索引及在Innodb上掃描行數。

線上效果

下圖是最近半個月不同版本實例XPlan報警的日平均發生率。

在優化版本XPlan索引選擇邏輯改變之後,每天實例出現XPlan選錯索引的概率從5%降到了0.1%,下降爲原本的1/50。注意老版本的XPlan選錯索引後用戶可以關閉XPlan,所以真實的錯選概率只會更高。

報警率概率下降的主因並不是優化器能選擇對的索引了,而是優化器能不選擇不對的索引了。

總結

本文詳細介紹了PolarDB-X對於點查場景的專門優化XPlan的索引選擇方案。

包括PolarDB-X的優化器架構和其中涉及的多種索引選擇、XPlan面臨的索引錯選問題和其中的基數估計錯誤、執行計劃緩存機制導致的傾斜值問題,針對性設計了一個能預先檢測避免錯選的算法,並提供監控報警機制、錯選後的兜底回退機制以及良好的可觀測性,顯著降低了XPlan索引錯選的概率。

當然XPlan的普適性、傾斜值判斷的穩定性、關聯列估算能力等都可以做進一步的優化。

引用

[1] Assembling a Query Engine From Spare Parts https://www.firebolt.io/content/firebolt-vldb-cdms-2022

[2] Efficient Query Re-optimization with Judicious Subquery Selections https://arxiv.org/pdf/2202.12535.pdf

[3] Robust Query Optimization Methods With Respect to Estimation Errors: A Survey https://dl.acm.org/doi/10.1145/2854006.2854012

[4] Towards a Robust Query Optimizer: A Principled and Practical Approach https://dl.acm.org/doi/10.1145/1066157.1066172

[5] A Survey of Data Partitioning and Sampling Methods to Support Big Data Analysis https://ieeexplore.ieee.org/document/9007871

作者:升雨

原文鏈接

本文爲阿里雲原創內容,未經允許不得轉載。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章