題目與背景
一張多租戶事件表擁有 B-tree 索引 (tenantid, createdat)。業務新增查詢只給出 created_at 範圍,舊版本常選擇順序掃描。請基於 PostgreSQL 18 的 skip scan 說明最佳化器如何利用後綴欄位、怎樣驗證收益,以及為什麼不能把它當作所有場景的索引替代品。
面試官考察什麼
重點是理解 B-tree 左側前綴規則與 skip scan 的差別。PostgreSQL 18 可以列舉前導欄位的不同值,為每個值執行後綴條件的索引搜尋;成本取決於前導欄位基數、後綴選擇性、資料表與索引相關性及估算統計。候選人還應能用 EXPLAIN (ANALYZE, BUFFERS) 證明實際收益。
先問清楚的澄清問題
資料分布
確認租戶數量、每個租戶的列數、時間範圍寬度和資料是否按時間聚集。前導欄位不同值很多時,重複搜尋可能比順序掃描更昂貴。
工作負載與版本
確認執行版本確實是 PostgreSQL 18、查詢是否高頻、是否允許新增覆蓋索引,以及是否存在並行寫入。skip scan 是計畫選擇,不是 SQL 語法保證。
觀測基線
確認已有 EXPLAIN、緩衝命中率、執行時間和冷快取基線。必須比較實際計畫,不能只看估算成本或單次熱快取結果。
30 秒回答框架
「索引 (tenantid, createdat) 的傳統規則要求先約束 tenantid;PostgreSQL 18 在成本合適時可以對 tenantid 的不同值逐一嘗試,再利用 createdat 的範圍條件跳過無關索引區間。我要先更新統計資訊,用 EXPLAIN ANALYZE BUFFERS 比較 skip scan、順序掃描和專門的 (createdat) 索引。租戶基數高、範圍寬或相關性差時,skip scan 可能更慢。」
深入解答步驟
第一步:明確索引與謂詞關係
列出索引欄位順序、等值條件、範圍條件和排序要求。skip scan 主要幫助缺少前導欄位約束但後續欄位有選擇性謂詞的多欄 B-tree,不會改變索引鍵的實體排序。
第二步:解釋列舉前綴的成本
最佳化器可以把前導欄位的不同值當作隱含搜尋入口,針對每個值查找後綴範圍。列舉次數近似受前導欄位不同值和統計誤差影響;前綴基數越高,隨機存取和重複定位成本越大。
第三步:刷新統計並檢查計畫
先對表執行 ANALYZE,確保欄位不同值、直方圖和相關性統計反映目前資料。用 EXPLAIN (ANALYZE, BUFFERS, SETTINGS) 記錄實際列數、共享命中、讀盤、計畫節點和啟用的最佳化器設定。
第四步:建立可比基線
在同一資料快照上比較三種方案:現有索引的 skip scan、順序掃描,以及新增後綴欄位索引。分別測試冷快取、熱快取、窄時間範圍、寬時間範圍和租戶傾斜,避免用單一樣本下結論。
第五步:識別覆蓋與回表成本
如果查詢還要讀取大量非索引欄位,skip scan 之後的 heap 存取可能成為瓶頸。評估索引是否覆蓋投影、可見性圖是否支援 index-only scan,以及隨機回表是否抵銷索引過濾收益。
第六步:處理計畫穩定性
資料增長會改變前導欄位基數和選擇性,使最佳化器在 skip scan、順序掃描和其他索引之間切換。記錄計畫指紋和 p95 延遲,必要時調整統計目標或單獨建立符合主要存取路徑的索引。
第七步:規劃升級與回滾
在 PostgreSQL 18 升級後重新收集統計並做真實流量回放。發布時監控 buffer read、CPU、鎖等待和尾延遲;若計畫回歸,先回到穩定索引或調整查詢,再評估是否保留 skip scan 路徑。
高品質示例回答
對於 (tenantid, createdat),我會把 skip scan 視為一種成本驅動的計畫:最佳化器列舉 tenantid 的不同值,再對 createdat 範圍做索引搜尋。先 ANALYZE,使用 EXPLAIN ANALYZE BUFFERS 與順序掃描、(created_at) 索引做冷熱快取和不同時間範圍的對照。若租戶基數、回表量或範圍寬度使重複搜尋昂貴,就建立符合主查詢的後綴或覆蓋索引,並監控升級後的計畫穩定性。
常見錯誤
- 錯誤: 認為有多欄索引就一定能高效過濾後綴欄位。→ 原因: 傳統左前綴規則仍然存在,skip scan 由成本決定。→ 改進: 以實際計畫和資料分布驗證。
- 錯誤: 把 skip scan 當成新的索引類型。→ 原因: 它是最佳化器存取 B-tree 的策略。→ 改進: 說明實體索引未改變。
- 錯誤: 只比較估算成本。→ 原因: 統計誤差會導致計畫誤判。→ 改進: 使用 ANALYZE BUFFERS 和多種快取狀態測量。
- 錯誤: 忽略回表和覆蓋欄位。→ 原因: 過濾快不代表讀取投影便宜。→ 改進: 評估 index-only 條件與 heap 存取。
追問與回答
追問 1:前導欄位只有兩個租戶就一定會使用 skip scan 嗎?
不一定。還要看後綴謂詞選擇性、頁面相關性、快取狀態和估算成本;最佳化器可能認為順序掃描更便宜。
追問 2:skip scan 會跳過所有不匹配的葉頁嗎?
它透過不同前綴值建立多次索引搜尋,減少無關範圍掃描,但每次搜尋仍有定位和可能的回表成本,並非免費跳躍。
追問 3:為什麼 ANALYZE 後計畫仍可能錯誤?
多欄相關性、資料傾斜、參數值和快取狀態會超出基礎統計模型。應結合擴充統計、真實參數回放和長期 p95 監控判斷。
追問 4:什麼時候直接建 (created_at) 索引更好?
當後綴查詢是穩定主路徑、前導欄位基數高、時間範圍寬或回表量大時,專用索引能減少重複前綴搜尋。需要權衡寫入放大、儲存和其他查詢對原索引的依賴。