博客 / 詳情

返回

培訓班誤人子弟!扒源碼見真相

本文在綠泡泡“狗哥瑣話”首發於2025.11.24 <-關注不走丟。

前言

大家好,這裏是狗哥。前陣子一個粉絲髮了我一個鏈接,問了我個問題。

image.png

image.png

那從這篇文章可以看到呢,這裏的確提到了一些RBO規則啊,還説是關鍵優化示例,但並不是啊,真實情況會有50多個規則,50多個規則裏呢,會有不少規則是關鍵優化示例。那今天就帶大家一個個解讀過去,主打一個真實硬核的扒源碼説話!
image.png

image.png
那要注意的是呢,講解時會牽涉一些邏輯算子的概念。因為SQL最終都會變成算子,去底層的存儲上做操作。那現在廢話不多説我們lets go!

逐個解讀

操作符下推類 (Operator Push Down)

1. PushProjectionThroughUnion
  • 作用: 將投影操作下推到Union的每個子分支
  • 示例:

-- 優化前
SELECT a, b FROM (
  SELECT a, b, c FROM table1 
  UNION ALL 
  SELECT a, b, c FROM table2
)

-- 優化後
SELECT a, b FROM table1 
UNION ALL 
SELECT a, b FROM table2
2. PushProjectionThroughLimitAndOffset
  • 作用: 將投影操作下推到Limit之下。因為Project操作通常會減少列的數量,提前執行Project
  • 示例:
-- 原始查詢
SELECT id, name FROM users LIMIT 10
-- 優化前的執行計劃
Project(id, name)
  LocalLimit(10)
    Scan(users) - 包含所有列如 id, name, age, email 等
-- 優化後的執行計劃
LocalLimit(10)
  Project(id, name)
    Scan(users) - 只需要讀取 id 和 name 列
3. ReorderJoin
  • 作用: 重新排序多個內連接,將有連接條件的表優先連接
  • 示例:
-- 優化前:沒有連接條件的笛卡爾積在前
SELECT * FROM t1, t2, t3 WHERE t1.id = t3.id

-- 優化後:有連接條件的先連接
SELECT * FROM t1 JOIN t3 ON t1.id = t3.id CROSS JOIN t2

這裏簡單來説,就是避免沒有條件的join,出現過大的笛卡爾積。儘量讓有條件的先join,儘量避免出現大笛卡爾積。

4. EliminateOuterJoin
  • 作用:如果外連接的空值行會被過濾掉,則將外連接轉換為內連接
  • 示例:
-- 優化前
SELECT * FROM t1 LEFT JOIN t2 ON t1.id = t2.id WHERE t2.name IS NOT NULL

-- 優化後  
SELECT * FROM t1 JOIN t2 ON t1.id = t2.id WHERE t2.name IS NOT NULL

這個例子中,優化前,會保留t1的所有行,如果t2中沒有匹配的行,則t2的字段填充NULL。但是後面的條件 會過濾掉所有t2.name為NULL的行。所以這個left join沒啥軟用。那改寫成inner join以後,就不用保持左表的所有記錄了。

5. PushDownPredicates
  • 作用:將過濾條件儘早下推到數據源
  • 示例:
-- 原始查詢
SELECT * FROM t1 
JOIN t2 ON t1.id = t2.id 
WHERE t1.name = 'John' AND t2.age > 25;

-- 優化前的計劃可能是:
-- Filter(t1.name = 'John' AND t2.age > 25)
--   Join(t1, t2, condition = t1.id = t2.id)

-- 優化後:謂詞被推入到各自的表掃描中
-- Join(
--   Filter(t1.name = 'John') -> Scan(t1),
--   Filter(t2.age > 25) -> Scan(t2),
--   condition = t1.id = t2.id
-- )
6. PushDownLeftSemiAntiJoin
-- 原始查詢
SELECT * FROM (
  SELECT id FROM table1
  UNION ALL
  SELECT id FROM table2
) t
WHERE NOT EXISTS (SELECT 1 FROM exclude_table e WHERE t.id = e.id);

-- 優化前:
-- Join(LeftAnti, Union(Scan(table1), Scan(table2)), Scan(exclude_table))

-- 優化後:LeftAnti Join 被推入到 Union 的每個子查詢中
-- Union(
--   Join(LeftAnti, Scan(table1), Scan(exclude_table)),
--   Join(LeftAnti, Scan(table2), Scan(exclude_table))
-- )

這種優化在更早的階段執行連接操作,減少中間結果的數據量,從而提高查詢性能。

7. PushLeftSemiLeftAntiThroughJoin
-- 原始查詢
SELECT t1.* 
FROM (t1 JOIN t2 ON t1.id = t2.id) 
WHERE EXISTS (SELECT 1 FROM t3 WHERE t1.name = t3.name);

-- 優化前邏輯計劃:
-- Join(LeftSemi, Join(Inner, t1, t2, t1.id = t2.id), t3, t1.name = t3.name)

-- 優化後:LeftSemi Join 被推入到左分支(Inner Join)
-- Join(Inner, 
--   Join(LeftSemi, t1, t3, t1.name = t3.name), 
--   t2, 
--   t1.id = t2.id)

也是更早地執行 Left Semi/Anti Join 操作,減少中間結果集的數據量,從而提高查詢性能。同時,規則確保只在安全的情況下進行推入,避免改變查詢語義。

那兩者什麼區別呢?

PushDownLeftSemiAntiJoin,根據操作符類型採用不同的推入策略

  • 對於Aggregate操作符,只有在可以規劃為廣播連接時才推入
  • 對於Union操作符,會將Join推入到每個子查詢中

PushLeftSemiLeftAntiThroughJoin,通過分析連接條件來決定推入方向:

  • 如果條件只涉及左分支,則推入左分支
  • 如果條件只涉及右分支,則推入右分支
  • 無條件時根據連接類型決定推入方向
OptimizeJoinCondition
-- 原始查詢
SELECT * FROM t1 JOIN t2 ON (t1.id = t2.id) OR (t1.id IS NULL AND t2.id IS NULL);

-- 優化前邏輯計劃:
-- Join(t1, t2, condition = (t1.id = t2.id) OR (t1.id IS NULL AND t2.id IS NULL))

-- 優化後:使用 NULL 安全的等值比較
-- Join(t1, t2, condition = t1.id <=> t2.id)

用於優化連接條件中的特定模式,提高查詢性能。

LimitPushDown
** **
  • 作用:將Limit操作下推到子查詢中
  • 示例:
-- 優化前
SELECT * FROM (SELECT * FROM table ORDER BY id) LIMIT 10

-- 優化後
SELECT * FROM (SELECT * FROM table ORDER BY id LIMIT 10)
LimitPushDownThroughWindow
-- 原始查詢
SELECT *, ROW_NUMBER() OVER(ORDER BY salary) AS rn 
FROM employees 
LIMIT 5;

-- 優化前邏輯計劃:
-- LocalLimit(5)
--   Window(ROW_NUMBER() OVER(ORDER BY salary), child = Scan(employees))

-- 優化後:將 LIMIT 和排序操作推入到 Window 之下
-- Window(ROW_NUMBER() OVER(ORDER BY salary))
--   Limit(5)
--     Sort(salary, global = true)
--       Scan(employees)

也是典型的一個篩選下推的操作。

ColumnPruning
  • 作用:消除不需要的列,只讀取用到的列
  • 示例:
-- 優化前:table有a,b,c三列
SELECT a FROM (SELECT a, b, c FROM table WHERE b > 0)

-- 優化後
SELECT a FROM (SELECT a, b FROM table WHERE b > 0)
GenerateOptimization
-- 原始查詢
SELECT COUNT(*) FROM table1 LATERAL VIEW explode(complex_array) exploded_table;

-- 假設 complex_array 是一個包含多個字段的結構體數組:
-- complex_array: Array<Struct<field1:int, field2:string, field3:double, field4:bigint>>

-- 優化前邏輯計劃:
-- Project(COUNT(*))
--   Generate(Explode(complex_array))
--     Scan(table1)

-- 優化後:只選擇最小的字段進行處理
-- Project(COUNT(*))
--   Generate(Explode(GetArrayStructFields(complex_array, smallest_field)))
--     Scan(table1)

2. 操作符合並類 (Operator Combine)

CollapseRepartition
  • 作用: 合併相鄰的重分區操作
  • 示例:
-- 原始查詢
SELECT * FROM (
  SELECT * FROM table1 ORDER BY col1
) DISTRIBUTE BY col1;

-- 優化前:
-- RepartitionByExpression(col1)
--   Sort(col1, global=true)
--     Scan(table1)

-- 優化後:移除 RepartitionByExpression,因為排序已經實現了所需的數據分佈
-- Sort(col1, global=true)
--   Scan(table1)

那這種就是避免語義接近的操作符重複使用,導致計算資源浪費。

CollapseProject
  • 作用: 減少不必要的投影操作
  • 示例:

-- 優化前
SELECT a, b FROM (SELECT a, b, c FROM table)

-- 優化後
SELECT a, b FROM table
OptimizeWindowFunctions
-- 原始查詢
SELECT 
  employee_id,
  department,
  salary,
  FIRST(employee_name) OVER (
    PARTITION BY department 
    ORDER BY salary DESC
    ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW
  ) as highest_paid_name
FROM employees;

-- 優化前邏輯計劃包含:
-- WindowExpression(AggregateExpression(First(employee_name)))

-- 優化後邏輯計劃:
-- WindowExpression(AggregateExpression(NthValue(employee_name, 1)))

用於優化窗口函數表達式,特別是將 First 函數替換為更高效的 NthValue 函數實現。那為什麼更高效呢?和實現有關:

  • First 函數需要處理多種邊界情況和複雜邏輯,包括處理 NULL 值、排序規則等
  • NthValue 實現更直接,專門用於獲取窗口中第 N 個值,邏輯更簡單明確
CollapseWindow
  • 作用:合併具有相同窗口規範的相鄰窗口函數
  • 示例:
-- 原始查詢
SELECT 
  employee_id,
  department,
  salary,
  ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC) as rank1,
  LAG(employee_id, 1) OVER (PARTITION BY department ORDER BY salary DESC) as prev_emp
FROM employees;

-- 優化前邏輯計劃包含兩個獨立的窗口操作:
-- Window(ROW_NUMBER() OVER (...))
--   Window(LAG(employee_id, 1) OVER (...))
--     Scan(employees)

-- 優化後:合併為一個窗口操作
-- Window(ROW_NUMBER() OVER (...), LAG(employee_id, 1) OVER (...))
--   Scan(employees)

這個看着例子比較長,其實就是減少數據掃描和排序操作的次數。

EliminateOffsets/EliminateLimits
  • 作用: 消除無效的Offset和Limit操作
  • 示例:

-- 優化前
SELECT * FROM table LIMIT 0  -- 或 OFFSET 0

-- 優化後
-- LIMIT 0 → 空結果集
-- OFFSET 0 → 去掉OFFSET
CombineUnions
  • 作用:合併相鄰的Union操作
  • 示例:

-- 優化前
(SELECT * FROM t1 UNION ALL SELECT * FROM t2) 
UNION ALL 
SELECT * FROM t3

-- 優化後
SELECT * FROM t1 UNION ALL SELECT * FROM t2 UNION ALL SELECT * FROM t3

這個SQL看起來差不多啊,其實優化前的語句,前者會產生多個stage,但是後者不會。相當於避免了多次讀寫中間結果的問題。 

3. 常量摺疊和強度遞減 (Constant Folding & Strength Reduction)

OptimizeRepartition
-- 原始查詢
SELECT * FROM employees WHERE department = 'IT' 
DISTRIBUTE BY 'constant_value';

-- 或者
SELECT * FROM employees 
DISTRIBUTE BY 1;

-- 優化前邏輯計劃:
-- RepartitionByExpression('constant_value', Scan(employees))

-- 優化後:分區數設置為1
-- RepartitionByExpression('constant_value', Scan(employees), numPartitions=1)

用於優化 RepartitionByExpression 操作符,當所有分區表達式都是可摺疊的且用户未指定分區數時,將分區數設置為1。

EliminateWindowPartitions

用於移除窗口操作中的可摺疊分區表達式,以簡化窗口函數的執行。

-- 原始查詢
SELECT 
  employee_id,
  department,
  salary,
  ROW_NUMBER() OVER (
    PARTITION BY department, 'constant_value' 
    ORDER BY salary DESC
  ) as rank_num
FROM employees;

-- 優化前邏輯計劃:
-- Window(
--   ROW_NUMBER() OVER (PARTITION BY department, 'constant_value' ORDER BY salary DESC),
--   partitionSpec = [department, 'constant_value']
-- )

-- 優化後:移除常量分區表達式
-- Window(
--   ROW_NUMBER() OVER (PARTITION BY department ORDER BY salary DESC),
--   partitionSpec = [department]
-- )
TransposeWindow

用於優化相鄰窗口表達式的執行順序,通過轉置窗口操作來提高查詢性能。

-- 原始查詢
SELECT 
  employee_id,
  department_id,
  salary,
  ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY salary DESC) as salary_rank,
  SUM(salary) OVER (PARTITION BY department_id) as dept_total
FROM employees;

-- 優化前邏輯計劃(假設執行順序):
-- Window(SUM(salary) OVER (PARTITION BY department_id))
--   Window(ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY salary DESC))
--     Scan(employees)

-- 優化後:由於兩個窗口都使用相同的分區鍵 department_id,
-- 可以轉置執行順序以優化性能
-- Window(ROW_NUMBER() OVER (PARTITION BY department_id ORDER BY salary DESC))
--   Window(SUM(salary) OVER (PARTITION BY department_id))
--     Scan(employees)
NullPropagation

用於優化與空值(NULL)相關的表達式,通過靜態分析提前確定表達式的計算結果,從而簡化查詢計劃。

-- 原始查詢(假設 employee_id 是非空列)
SELECT employee_id, 
       employee_id IS NULL as is_null_check,
       employee_id IS NOT NULL as is_not_null_check
FROM employees;

-- 優化前邏輯計劃:
-- Project(
--   employee_id,
--   IsNull(employee_id),
--   IsNotNull(employee_id)
-- )

-- 優化後:直接替換為常量
-- Project(
--   employee_id,
--   false as is_null_check,
--   true as is_not_null_check
-- )

4. 其他特殊優化

RewriteNonCorrelatedExists

用於將非關聯的 EXISTS 子查詢重寫為使用 ScalarSubquery 的形式,以提高查詢執行效率。

-- 原始查詢
SELECT employee_id, name 
FROM employees e
WHERE EXISTS (SELECT 1 FROM departments d WHERE d.budget > 100000);

-- 優化前邏輯計劃:
-- Filter(Exists(Scan(departments)))

-- 優化後:重寫為ScalarSubquery形式
-- Filter(
--   IsNotNull(
--     ScalarSubquery(
--       Limit(1, 
--         Project(Alias(Literal(1), "col")),
--           Scan(departments)
--         )
--       )
--     )
--   )

好處:

  • 執行效率提升:使用 ScalarSubquery 可以更高效地執行子查詢
  • 早期終止:通過 LIMIT 1 實現,一旦找到匹配行就立即終止子查詢執行
  • 簡化邏輯:將 EXISTS 語義轉換為更簡單的 IS NOT NULL 檢查
  • 資源節省:避免全量執行子查詢,只檢查是否存在匹配行
NullDownPropagation

於優化 IsNull 和 IsNotNull 表達式,當這些表達式的輸入是 NullIntolerant(對空值敏感)的表達式時,將空值檢查向下傳播到子表達式。

-- 原始查詢
SELECT * FROM orders 
WHERE (quantity > price) IS NOT NULL;

-- 優化前:
-- Filter(IsNotNull(GreaterThan(quantity, price)))

-- 優化後:
-- Filter(And(IsNotNull(quantity), IsNotNull(price)))
ConstantPropagation

用於在查詢條件中傳播常量值,通過將已知的屬性值替換到其他表達式中來簡化查詢條件。

-- 原始查詢
SELECT * FROM table WHERE i = 5 AND j = i + 3;

-- 優化前邏輯計劃:
-- Filter(And(EqualTo(i, 5), EqualTo(j, Add(i, 3))))

-- 優化後:將 i 替換為 5
-- Filter(And(EqualTo(i, 5), EqualTo(j, 8)))
FoldablePropagation

用於將可摺疊表達式的屬性替換為別名,從而讓其他優化規則可以利用這些可摺疊表達式進行進一步優化。

-- 原始查詢
SELECT 1.0 as x, 'abc' as y, Now() as z 
FROM table 
ORDER BY x, y, 3;

-- 優化前邏輯計劃:
-- Sort(x, y, 3)
--   Project(1.0 as x, 'abc' as y, Now() as z)

-- 優化後:將屬性引用替換為實際表達式
-- Sort(1.0, 'abc', Now())
--   Project(1.0 as x, 'abc' as y, Now() as z)
OptimizeIn
  • 作用: 優化IN表達式
  • 示例:
-- 小列表:IN (1) → col = 1 
-- 大列表:IN (1,2,3...100) → InSet(1,2,3...100)

沒用的表達式就替換了,一個的就是轉換成eq,小於一定數量的可以轉換成HashSet,默認值是10,可以調整。不然就簡單做個去重。

OptimizeRand

用於優化涉及 Rand() 函數的比較表達式。由於 Rand() 函數生成 [0, 1) 區間內的隨機數,當比較值超出此範圍時,比較結果可以靜態確定。基於以下原理進行優化:

  • Rand() 函數生成的隨機數範圍是 [0, 1)
  • 當比較值小於等於 0 時,Rand() > value 總是為真
  • 當比較值大於等於 1 時,Rand() < value 總是為真
  • 當比較值超出範圍時,可以將整個比較表達式替換為常量 TrueLiteral 或 FalseLiteral
-- 原始查詢
SELECT * FROM table WHERE rand() >= 1.5;

-- 優化前邏輯計劃:
-- Filter(GreaterThanOrEqual(Rand(), 1.5))

-- 優化後:由於 Rand() 永遠小於 1.5,替換為 false
-- Filter(FalseLiteral)
ConstantFolding

用於將可以靜態計算的表達式替換為等價的字面量值。這個規則通過提前計算確定性的表達式來簡化查詢計劃。

-- 原始查詢
SELECT SIZE(ARRAY(1, 2, 3, 4)) as array_size FROM table;

-- 優化後:
-- Project(4 as array_size)
EliminateAggregateFilter

用於移除聚合表達式中無用的 FILTER 子句,從而簡化查詢計劃並提高執行效率。

-- 原始查詢
SELECT 
  SUM(salary) FILTER (WHERE TRUE) as total_salary,
  COUNT(*) FILTER (WHERE department = 'IT') as it_count
FROM employees;

-- 優化前邏輯計劃:
-- Aggregate(
--   AggregateExpression(Sum(salary), filter = Some(TrueLiteral)),
--   AggregateExpression(Count(*), filter = Some(EqualTo(department, 'IT')))
-- )

-- 優化後:移除 TRUE 條件的 FILTER
-- Aggregate(
--   AggregateExpression(Sum(salary), filter = None),
--   AggregateExpression(Count(*), filter = Some(EqualTo(department, 'IT')))
-- )
ReorderAssociativeOperator

用於重新排列和摺疊關聯的整數類型運算符,將常量合併為一個,從而簡化算術表達式。

-- 原始查詢
SELECT (salary + 1000) + 500 + 200 as adjusted_salary FROM employees;

-- 優化前邏輯計劃:
-- Project(Add(Add(Add(salary, 1000), 500), 200))

-- 優化後:常量被摺疊
-- Project(Add(salary, 1700))
LikeSimplification

簡化不需要完整正則表達式的 LIKE 表達式。它將特定模式的 LIKE 操作轉換為更高效的字符串操作函數。

規則識別並優化以下幾種 LIKE 模式:

  • 前綴匹配:column LIKE 'prefix%' → StartsWith(column, 'prefix')
  • 後綴匹配:column LIKE '%postfix' → EndsWith(column, 'postfix')
  • 包含匹配:column LIKE '%infix%' → Contains(column, 'infix')
  • 精確匹配:column LIKE 'exact' → EqualTo(column, 'exact')
  • 前後綴匹配:column LIKE 'prefix%postfix' → 組合條件
-- 原始查詢
SELECT * FROM users WHERE name LIKE 'John%';

-- 優化前邏輯計劃:
-- Filter(Like(name, 'John%'))

-- 優化後:轉換為更高效的 StartsWith 函數
-- Filter(StartsWith(name, 'John'))
BooleanSimplification

用於簡化布爾表達式。它通過多種優化技術來簡化邏輯表達式,從而提高查詢執行效率。進行以下幾類優化:

  • 短路優化:簡化可以不計算兩邊就能確定結果的表達式
  • 公共因子消除:提取和合並邏輯表達式中的公共部分
  • 表達式合併:合併相同的表達式
  • NOT運算符簡化:移除或轉換NOT運算符
-- 原始查詢
SELECT * FROM employees 
WHERE salary > 50000 AND TRUE OR department = 'IT';

-- 優化前邏輯計劃:
-- Filter(Or(And(GreaterThan(salary, 50000), TrueLiteral), EqualTo(department, 'IT')))

-- 優化後:TRUE條件被消除,表達式簡化
-- Filter(Or(GreaterThan(salary, 50000), EqualTo(department, 'IT')))
SimplifyConditionals

用於簡化條件表達式,包括 If 和 CaseWhen 表達式。通過以下方式進行優化:

  • 簡化確定性條件:當條件表達式的結果可以預先確定時,直接返回對應分支的值
  • 移除假分支:刪除條件永遠為假的分支
  • 合併相同結果:當多個分支返回相同結果時進行合併
  • 優化布爾表達式:將布爾條件轉換為更簡單的形式
-- 原始查詢
SELECT IF(TRUE, 'yes', 'no') as result1, 
       IF(FALSE, 'yes', 'no') as result2,
       IF(NULL, 'yes', 'no') as result3 FROM table;

-- 優化前邏輯計劃:
-- Project(
--   If(TrueLiteral, Literal('yes'), Literal('no')),
--   If(FalseLiteral, Literal('yes'), Literal('no')),
--   If(Literal(null), Literal('yes'), Literal('no'))
-- )

-- 優化後:直接返回確定的結果
-- Project(
--   Literal('yes'),    -- IF(TRUE, 'yes', 'no') => 'yes'
--   Literal('no'),     -- IF(FALSE, 'yes', 'no') => 'no'
--   Literal('no')      -- IF(NULL, 'yes', 'no') => 'no'
-- )
PushFoldableIntoBranches

用於將可摺疊的表達式推入條件分支(If 和 CaseWhen)內部,從而在分支內部進行常量摺疊優化。

-- 原始查詢
SELECT UPPER(IF(department = 'IT', 'tech', 'other')) as dept_category FROM employees;

-- 優化前邏輯計劃:
-- Project(
--   Upper(
--     If(EqualTo(department, 'IT'), Literal('tech'), Literal('other'))
--   )
-- )

-- 優化後:將 Upper 推入 IF 的每個分支
-- Project(
--   If(EqualTo(department, 'IT'), 
--      Upper(Literal('tech')), 
--      Upper(Literal('other')))
-- )
-- 後續 ConstantFolding 規則會進一步優化為:
-- Project(
--   If(EqualTo(department, 'IT'), 
--      Literal('TECH'), 
--      Literal('OTHER')))
-- )
SimplifyBinaryComparison

用於簡化二元比較表達式,特別是當比較的兩個操作數語義相等時。通過以下方式優化二元比較表達式:

  • 當兩個操作數語義相等且都不可為空時,將 =、<=、>= 替換為 true
  • 當兩個操作數語義相等且都不可為空時,將 <、> 替換為 false
  • 優化包含布爾字面量的比較表達式
  • 利用查詢中的 IS NOT NULL 約束來判斷表達式是否可為空
-- 原始查詢
SELECT * FROM products WHERE price > price;

-- 優化前:
-- Filter(GreaterThan(price, price))

-- 優化後:因為price > price總是為假
-- Filter(FalseLiteral)
-- 後續優化會返回空結果集
ReplaceNullWithFalseInPredicate

用於在特定條件下將布爾類型的 NULL 字面量替換為 False 字面量。

-- 原始查詢
SELECT * FROM employees WHERE NULL;

-- 優化前邏輯計劃:
-- Filter(Literal(null, BooleanType))

-- 優化後:將 NULL 替換為 False
-- Filter(FalseLiteral)
PruneFilters

用於移除可以被簡單評估的過濾器,從而簡化查詢計劃並提高執行效率。

該規則通過以下三種方式優化過濾器:

  • 移除恆為真的過濾條件:當過濾條件始終為 true 時,完全移除該過濾器
  • 替換恆為假的過濾條件:當過濾條件始終為 false 或 null 時,用空關係替換輸入
  • 移除可由子節點約束推導出的條件:當過濾條件可以根據子節點的約束條件推導出始終為真時,移除這些條件
-- 原始查詢
SELECT * FROM employees WHERE 1 = 1;

-- 優化前邏輯計劃:
-- Filter(EqualTo(Literal(1), Literal(1)))
--   LocalRelation(output=[employees])

-- 優化後:完全移除過濾器
-- LocalRelation(output=[employees])
SimplifyCasts

用於移除不必要的類型轉換(Cast)操作,從而簡化查詢計劃並提高執行效率。

該規則通過以下幾種方式優化 Cast 表達式:

  • 移除相同類型的轉換:當輸入類型與目標類型相同時,直接移除 Cast 操作
  • 優化嵌套數值類型轉換:當連續的數值類型轉換可以合併為一次更寬泛的轉換時,移除中間轉換
  • 移除數組和映射類型的冗餘轉換:當數組或映射類型的元素類型相同但可空性不同時,移除轉換
  • 優化 IsNotNull 包裝的 Cast:當 Cast 可以安全簡化時,同時優化外層的 IsNotNull 表達式
-- 原始查詢
SELECT CAST(id AS INT) FROM users WHERE id IS INT;

-- 優化前邏輯計劃:
-- Project(Cast(id#123, IntegerType))
--   Filter(IsNotNull(id#123))
--     LocalRelation(output=[id#123])

-- 優化後:因為id已經是INT類型,移除Cast
-- Project(id#123)
--   Filter(IsNotNull(id#123))
--     LocalRelation(output=[id#123])
SimplifyCaseConversionExpressions

用於移除嵌套的大小寫轉換表達式中不必要的內層轉換,因為外層轉換會覆蓋內層轉換的結果。該規則通過識別和簡化以下嵌套大小寫轉換模式來優化表達式:

  • Upper(Upper(child)) → Upper(child) - 連續兩次大寫轉換,只需要一次
  • Upper(Lower(child)) → Upper(child) - 先轉小寫再轉大寫,等同於直接轉大寫
  • Lower(Upper(child)) → Lower(child) - 先轉大寫再轉小寫,等同於直接轉小寫
  • Lower(Lower(child)) → Lower(child) - 連續兩次小寫轉換,只需要一次
-- 原始查詢
SELECT UPPER(UPPER(name)) as upper_name FROM users;

-- 優化前邏輯計劃:
-- Project(Upper(Upper(name#123)))
--   LocalRelation(output=[name#123])

-- 優化後:移除內層的冗餘Upper
-- Project(Upper(name#123))
--   LocalRelation(output=[name#123])
RewriteCorrelatedScalarSubquery

用於將相關的標量子查詢(Correlated Scalar Subquery)重寫為 LEFT OUTER JOIN 操作,從而提高查詢執行效率。該規則通過以下步驟優化相關標量子查詢:

  • 提取相關標量子查詢:從 Filter、Project、Aggregate 等操作中識別並提取相關標量子查詢表達式
  • 靜態評估優化:對空輸入情況下的子查詢結果進行靜態評估,避免運行時開銷
  • LEFT OUTER JOIN 轉換:將標量子查詢轉換為 LEFT OUTER JOIN 操作
-- 原始查詢
SELECT employee_id, name, 
       (SELECT MAX(salary) FROM salaries s WHERE s.emp_id = e.employee_id) as max_salary
FROM employees e;

-- 優化前邏輯計劃:
-- Project(employee_id, name, ScalarSubquery(
--   Aggregate(Seq(), Seq(max_salary), 
--     Filter(EqualTo(s.emp_id, outer(e.employee_id)), 
--       Relation[salaries]))))
--   Relation[employees]

-- 優化後:轉換為LEFT OUTER JOIN
-- Project(employee_id, name, max_salary)
--   Join(employees, salaries, LeftOuter, 
--        EqualTo(e.employee_id, s.emp_id))

這種優化可以帶來以下好處:

  • 避免重複子查詢執行:將標量子查詢轉換為 JOIN 操作,避免對每個外層行重複執行子查詢
  • 利用JOIN優化器:JOIN操作可以更好地利用查詢優化器的索引和統計信息
  • 減少網絡開銷:特別是在分佈式環境中,減少子查詢的遠程執行次數
RewriteLateralSubquery

該規則將邏輯計劃中的 LateralJoin 節點轉換為普通的 Join 節點:

  • 將 LateralSubquery 中的子查詢計劃作為右表
  • 使用 DecorrelateInnerQuery.rewriteDomainJoins 處理域連接
  • 合併連接條件
  • 保留原有的連接類型和提示信息
SELECT p.name, s.total_sales
FROM products p
LATERAL (
  SELECT SUM(o.amount) as total_sales
  FROM orders o
  WHERE o.product_id = p.id AND o.order_date >= p.launch_date
) s
WHERE p.category = 'Electronics';
-- 優化前邏輯計劃
LateralJoin(
  left = Filter(category='Electronics', products),
  right = LateralSubquery(
    Project(SUM(amount) as total_sales),
    Aggregate(...),
    Filter(product_id=id AND order_date>=launch_date, orders)
  ),
  joinType = Inner,
  condition = None
)
-- 優化後邏輯執行計劃
Join(
  left = Filter(category='Electronics', products),
  right = Project(SUM(amount) as total_sales),
           Aggregate(...),
           Filter(product_id=id AND order_date>=launch_date, orders),
  joinType = Inner,
  condition = (product_id=id AND order_date>=launch_date)
)
EliminateSerialization

用於消除在對象和序列化表示(InternalRow)之間不必要的轉換,從而提高 SQL的執行效率。

RemoveRedundantAliases

用於移除查詢計劃中的冗餘別名。

-- 原始SQL
SELECT id AS id, name AS name FROM users;

-- 優化後(移除冗餘別名)
SELECT id, name FROM users;
RemoveRedundantAggregates 

用於移除查詢計劃中冗餘的聚合操作。

-- 原始SQL,包含冗餘的嵌套聚合
SELECT COUNT(*) 
FROM (
  SELECT id, name 
  FROM users 
  GROUP BY id, name
) t;

-- 優化後,移除內層冗餘聚合
SELECT COUNT(*) 
FROM users;
UnwrapCastInBinaryComparison 

用於在二元比較或 In/InSet 操作中展開(unwrap)類型轉換(Cast)表達式。

-- 優化前
SELECT * FROM table WHERE CAST(short_col AS INT) = 100;

-- 優化後(因為 short 範圍是 -32,768 到 32,767,100 在範圍內)
SELECT * FROM table WHERE short_col = CAST(100 AS SMALLINT);


-- 優化前
SELECT * FROM table WHERE CAST(short_col AS INT) > 50000;

-- 優化後(因為 short 最大值是 32,767,50000 超出範圍)
SELECT * FROM table WHERE IF(ISNULL(short_col), NULL, FALSE);



-- 優化前
SELECT * FROM table WHERE CAST(short_col AS INT) >= 32767;

-- 優化後(因為 short 最大值是 32,767)
SELECT * FROM table WHERE short_col = 32767;

-- 優化前
SELECT * FROM table WHERE CAST(byte_col AS INT) IN (10, 20, 30);

-- 優化後
SELECT * FROM table WHERE byte_col IN (10, 20, 30);

-- 優化前
SELECT * FROM table WHERE CAST(timestamp_col AS DATE) > DATE '2023-01-01';

-- 優化後
SELECT * FROM table WHERE timestamp_col >= TIMESTAMP '2023-01-02 00:00:00';
RemoveNoopOperators

用於移除查詢計劃中不執行任何實際操作的無用操作符。

-- 原始SQL,包含空的窗口操作
SELECT id, name, ROW_NUMBER() OVER() as rn
FROM users
WINDOW w AS ();

-- 優化後,移除空的窗口操作
SELECT id, name, ROW_NUMBER() OVER() as rn
FROM users;
OptimizeUpdateFields

主要功能

  • 合併嵌套的 UpdateFields 表達式
  • 消除重複字段操作:當對同一個字段進行多次操作時,只保留最後一次操作
-- 原始SQL,包含嵌套的字段更新操作
SELECT update_fields(
  update_fields(struct_col, 'field1', 'value1'), 
  'field2', 'value2'
) FROM table;

-- 優化後,合併為單一的字段更新操作
SELECT update_fields(struct_col, 'field1', 'value1', 'field2', 'value2') FROM table;
SimplifyExtractValueOps

專門用於簡化複雜類型(結構體、數組、映射)的提取操作。

該規則通過識別和優化以下模式來簡化複雜類型的提取操作:

  • 簡化結構體字段提取
  • 簡化數組元素提取
  • 簡化映射值提取
  • 優化數組中結構體字段的批量提取
-- 優化前
SELECT transform(array_col, x -> x.name) FROM (
  SELECT array(
    named_struct('name', 'Alice', 'age', 25),
    named_struct('name', 'Bob', 'age', 30)
  ) as array_col
) t;

-- 優化後,將字段提取操作下推到數組的每個元素
SELECT array(
  (named_struct('name', 'Alice', 'age', 25)).name,
  (named_struct('name', 'Bob', 'age', 30)).name
) FROM (...);
OptimizeCsvJsonExprs

專門用於優化 CSV 和 JSON 相關的表達式操作。

該規則通過識別和優化以下模式來簡化 CSV/JSON 處理操作:

  • 消除冗餘的 JSON 轉換對
  • 列裁剪優化
  • 結構體創建優化
  • CSV 列裁剪優化
-- 優化前
SELECT from_json(json_col, full_schema).field1 FROM table;

-- 優化後,只解析需要的字段
SELECT from_json(json_col, struct_schema(field1)).field1 FROM table;
CombineConcats

專門用於合併嵌套的 Concat 表達式。該規則通過識別和優化嵌套的字符串連接操作來簡化執行計劃:

  • 扁平化嵌套的 Concat 表達式
  • 處理帶有類型轉換的嵌套連接操作
  • 減少執行計劃中的表達式層級
-- 優化前
SELECT concat(concat('A', 'B'), concat('C', 'D')) FROM table;

-- 優化後,合併為單層連接
SELECT concat('A', 'B', 'C', 'D') FROM table;
PushdownPredicatesAndPruneColumnsForCTEDef

專門用於優化 CTE(Common Table Expression,公用表表達式) 的執行計劃。該規則通過以下兩個主要優化來提升 CTE 的執行效率:

  • 謂詞下推(Predicate Pushdown):將應用在 CTE 引用上的過濾條件推入到 CTE 定義內部
  • 列裁剪(Column Pruning):只選擇 CTE 中實際被引用的列,減少不必要的數據讀取
-- 優化前的 SQL
WITH employee_summary AS (
  SELECT id, name, department, salary, hire_date 
  FROM employees
)
SELECT name, department 
FROM employee_summary 
WHERE salary > 50000 AND department = 'Engineering';

-- 優化後的 CTE 定義(內部優化)
WITH employee_summary AS (
  SELECT id, name, department, salary  -- hire_date 被裁剪,因為未被使用
  FROM employees
  WHERE salary > 50000 AND department = 'Engineering'  -- 謂詞下推
)
SELECT name, department 
FROM employee_summary;

小結

當我們真正看過代碼以後呢,會發現Spark的RBO並沒有這麼簡單。

那這個公眾號呢,其實所屬一家培訓機構的,自稱裏面的老師都是大廠的工程師,收費動不動就上w。那是不是大廠咱也不知道,也沒有辦法證明,但是這部分內容的確和實際相差有點多。但凡點開Spark源碼真正看過的人,一定不會這麼講。只能説現在培訓機構還是太好混了,一堆沒法證明的履歷+善乏可陳的內容,就有小白去交錢,屬實難繃。

那今天的內容就到這裏了。這裏是狗哥,一名熱愛讀源碼的工程師。大家可以給這篇文章多點點贊、以及轉發、在看之類的,讓一些年輕的同學少踩一些坑,交更少的學費。

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.