深度解析:資料庫核心如何透過邏輯推理與常值推導突破去重效能瓶頸

作為長年在第一線與海量資料打交道的後端開發者,在複雜的業務系統開發中,處理重複資料是我們繞不開的命題。為了保證輸出資料的唯一性,我們經常在 SQL 中使用 `DISTINCT` 關鍵字,或者利用全欄位 `GROUP BY` 來實現語意上的去重。

然而,隨著業務資料的指數級成長,這類去重操作屢屢成為拖垮系統查詢效能的「隱形殺手」。最近,團隊在進行底層基礎軟體的遷移與效能攻堅,我們將核心業務線遷移至了電科金倉資料庫 Kingbase。在深度剖析慢查詢日誌與底層執行計畫時,我發現現代資料庫的最佳化器早已跨越了單純比對物理成本的階段,進化出基於編譯邏輯推理的高級智慧核心。

今天,我們就結合具體的 SQL 實作和極端場景下的底層執行計畫對比,硬核拆解現代資料庫是如何透過「常值推導」等語意重寫手段,優雅且徹底地消滅去重效能瓶頸的。 在這裡插入圖片描述

@[toc]


一、 傳統執行引擎的痛點:物理層面的「暴力去重」

要理解最佳化的精妙,我們首先要明白傳統資料庫在處理去重時有多麼「死板」。在傳統的執行引擎眼中,無論是 DISTINCT 還是 GROUP BY 去重,底層的物理實現邏輯基本都是一套標準流程:全表或索引掃描過濾目標資料 -> 將資料送入記憶體建立雜湊表或進行排序 -> 逐列分組比對剔除重複項

為了直觀展示這種效能浪費,我們在資料庫中建構了一個包含海量冗餘資料的測試表 s1,並模擬了一個需要對常數條件進行去重的查詢場景:

sql 體驗AI代碼助手 代碼解讀複製代碼-- 傳統的去重查詢寫法(使用 GROUP BY 替代 DISTINCT 實現語意去重)
EXPLAIN ANALYZE SELECT a, b FROM s1 WHERE a=1 AND b=1 GROUP BY a, b;

在這裡插入圖片描述

效能災難分析:如上圖的執行計畫所示,資料庫極其「老實」地執行了並行全表掃描(Parallel Seq Scan),把滿足條件的冗餘資料全部撈了出來。接著,啟動了 Group 算子,在記憶體中為這些長得一模一樣的資料進行毫無意義的分組比對。整個過程白白消耗了 16.717 毫秒。不僅浪費 CPU,還極大地占用了記憶體空間。

二、 破局之道:常值推導與 LIMIT 1 的降維打擊

真正的技術突破,發生在 SQL 解析階段之後的邏輯最佳化環節。Kingbase 的核心思路是:在進入物理執行之前,先在抽象語法樹層面審視這條 SQL 的語意。 在這裡插入圖片描述

具備高級智慧的資料庫核心在這裡引入了一項堪稱「降維打擊」的最佳化策略:目標欄位被常值固定時的 LIMIT 1 替代法

它的底層邏輯推理鏈條非常嚴密:

  1. 述詞提取:解析器發現 WHERE 條件中明確限制了 a = 1b = 1
  2. 目標對映:查詢返回的欄位恰好就是 ab
  3. 邏輯固化判定:最佳化器推斷出,既然結果集裡的每一行都必須滿足這兩個欄位等於固定常數,那麼最終查出來的所有行,必然全部是 (1, 1)
  4. 觸發等價轉換:既然結果的樣貌已被完全鎖定,且業務明確要求結果唯一(去重)。此時最佳化器會在底層直接把原始語句等價重寫為帶有 LIMIT 1 的形態,直接繞過底層的去重算子。

為了驗證這個理論,我們手動將 SQL 改寫為 LIMIT 1 模式來觀測極限效能:

sql 體驗AI代碼助手 代碼解讀複製代碼-- 驗證 LIMIT 1 帶來的短路效應
EXPLAIN ANALYZE SELECT a, b FROM s1 WHERE a=1 AND b=1 LIMIT 1;

在這裡插入圖片描述

實作見證奇蹟:執行時間從 16.717 毫秒,斷崖式暴降到了驚人的 0.048 毫秒! 原因就在於頂層的 Limit 算子。底層的掃描器只要碰巧掃到了第一條符合要求的資料,就會立刻觸發「短路機制(Short-circuit)」,直接向客戶端返回結果,並強行終止底層後續的無數次掃描。沉重的去重操作被瞬間消解。

三、 深入無人區:複雜多表 JOIN 下的去重災難

單表的常值推導固然驚豔,但真實的企業級應用充斥著複雜的表關聯。在多表 INNER JOIN 的環境下,傳統資料庫的去重機制會引發多大的災難?

我們引入表 s2,建構一個更複雜的關聯去重場景。請注意,接下來的資料會非常震撼。

sql 體驗AI代碼助手 代碼解讀複製代碼-- 複雜多表關聯場景下的去重查詢
EXPLAIN ANALYZE
SELECT s1.a, s2.b 
FROM s1 
INNER JOIN s2 ON s1.a = s2.b 
WHERE s1.a = 5 
GROUP BY s1.a, s2.b; 

在這裡插入圖片描述

史詩級的效能災難:仔細剖析這條 SQL,存在一個隱蔽的邏輯陷阱:查詢目標欄位是 s1.as2.b。但在 WHERE 條件中,開發者只寫了 s1.a = 5並沒有直接說明 s2.b 等於幾

對於傳統的低階最佳化器來說,它在這裡就停止思考了。從執行計畫中我們可以看到恐怖的一幕:底層的 Nested Loop(巢狀迴圈連接)瘋狂地執行了 2 億次loops=200000000)!最終導致這條查詢耗時飆升到了令人窒息的 37330.185 毫秒(近 37 秒)

四、 欄間等值推導:將 37 秒化為 12 毫秒的神奇魔法

面對上述災難,具備高階推導能力的現代最佳化器是如何破局的呢?它會在核心中建構一張等值傳遞網路

  1. 已知明確條件s1.a = 5
  2. 已知關聯條件s1.a = s2.b
  3. 高階推導:既然 A=5 且 A=B,那麼必然得出 B=5。即隱藏的常數條件 s2.b = 5 絕對成立!
  4. 此時回頭檢查查詢的目標欄位 s1.as2.b,發現它們的值已經在邏輯上被完全固化為常數!

一旦推導成功,高級核心即可觸發等價重寫規則,將外層的去重操作直接替換為 LIMIT 1。我們再次手動模擬核心重寫後的形態,進行效能驗證:

sql 體驗AI代碼助手 代碼解讀複製代碼-- 模擬高階最佳化器透過等值推導重寫後的 SQL
EXPLAIN ANALYZE
SELECT s1.a, s2.b 
FROM s1 
INNER JOIN s2 ON s1.a = s2.b 
WHERE s1.a = 5 
LIMIT 1; 

在這裡插入圖片描述

執行計畫結果令人振奮:頂層的 Limit 算子如同定海神針。雖然底層掃描定位第一條符合 s1.a=5 的資料花了十幾毫秒,但關鍵在於,那個原本瘋狂循環 2 億次的 Nested Loop,在探測到第一組匹配的資料後被瞬間截斷(loops=1)

原本耗時近 37 秒 的關聯去重慢查詢,最終耗時僅僅定格在 12.930 毫秒。效能提升了近 3000 倍!

五、 寫在最後

剛才咱們把這些藏在資料庫最底層的執行機制給捋了一遍,其實看完之後,我自己也在琢磨平時大家寫業務程式碼的習慣。也就是說,要是遇到慢查詢的情況,除了砸錢去加伺服器硬體配置這種笨辦法之外,其實利用底層的編譯邏輯去把 SQL 重新處理一遍,才是現在這些底層軟體最關鍵的技術點。

那麼,這些東西對咱們平時做開發的同學來說,其實有這麼幾個挺實在的建議:

  1. 順著引擎的思路去寫:通常來說,我們在寫那種很複雜的好幾張表互相連表的情況的話,你的常數過濾條件盡量能寫多全就寫多全。哪怕它只是個間接的條件,也給它加上。為什麼呢?因為這樣一來,就能幫最佳化器省下很多事,它就能更容易地去把那個等值傳遞的鏈路給順暢地串起來。
  2. 自己把控好邏輯:如果說在寫業務程式碼的時候,你心裡非常清楚查出來的結果肯定就只有這一條的話。接著你乾脆老老實實就在 SQL 語句的最後面加上個 LIMIT 1。這種做法,往往僅僅只是多敲了幾個字母,但真的比你完全指望著資料庫底層自己去幫你做分組和去重來得穩當得多。
  3. 多去了解底層的變化:其實像電科金倉這種國產資料庫,現在他們確實在引擎最底層的那塊(比如像是用 GUC 參數去控制的那些自動改寫功能)做了很多很實在的改動。平時要是能多花點時間去弄明白這些底層到底是怎麼去做邏輯推理的,那麼以後咱們自己去搞架構設計,或者說系統卡了需要去調優的時候,心裡就有底了,不至於兩眼一抹黑到處亂試。

原文出處:https://juejin.cn/post/7642537542860996643


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝17   💬11   ❤️1
588
🥈
alicec
📝1   ❤️2
81
🥉
我愛JS
💬2  
7
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
📢 贊助商廣告 · 我要刊登