本文在綠泡泡“狗哥瑣話”首發於2025.11.24 <-關注不走丟。
前言
大家好,這裏是狗哥。前陣子一個粉絲髮了我一個鏈接,問了我個問題。
那從這篇文章可以看到呢,這裏的確提到了一些RBO規則啊,還説是關鍵優化示例,但並不是啊,真實情況會有50多個規則,50多個規則裏呢,會有不少規則是關鍵優化示例。那今天就帶大家一個個解讀過去,主打一個真實硬核的扒源碼説話!
那要注意的是呢,講解時會牽涉一些邏輯算子的概念。因為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源碼真正看過的人,一定不會這麼講。只能説現在培訓機構還是太好混了,一堆沒法證明的履歷+善乏可陳的內容,就有小白去交錢,屬實難繃。
那今天的內容就到這裏了。這裏是狗哥,一名熱愛讀源碼的工程師。大家可以給這篇文章多點點贊、以及轉發、在看之類的,讓一些年輕的同學少踩一些坑,交更少的學費。