背景
本文基于Starrocks 3.3.5
最近在進行Starrocks 跑數據的時候,發現了一個SQL 掃描了所有分區的數據,簡化后的SQL如下:
select date_created from tableA where date_created=date_format(current_time(), '%Y-%m-%d %H:%i:%S') limit 20
其中建表語句如下:
CREATE TABLE `tableA` (...`date_created` datetime NOT NULL DEFAULT "1970-01-01 00:00:00" COMMENT "",...
) ENGINE=OLAP
PRIMARY KEY(id,date_created)
PARTITION BY date_trunc("day",date_created)
DISTRIBUTED BY HASH(id) BUCKETS 50
PROPERTIES( "compression" = "ZSTD");
但是如果用CURRENT_TIMESTAMP
替換的話,就能實現分區下推,具體的SQL如下:
select date_created from tableA where date_created=CURRENT_TIMESTAMP() limit 20
結論
current_time()
函數不支持常量折疊,也就是不支持在計劃解析和優化階段來計算結果。而CURRENT_TIMESTAMP
在計劃優化階段就可以計算出結果。
具體的explain對應的SQL如下:
可以看到 用 current_time
函數的 掃描了全部的分區
用CURRENT_TIMESTAMP
函數的 只選擇了一個分區的數據
分析
先執行兩個命令從感官上來感受一下:
TRACE LOGS OPTIMIZER SELECT CURRENT_TIMESTAMP()\G;
TRACE LOGS OPTIMIZER SELECT (date_format(current_time(), '%Y-%m-%d %H:%i:%S'))\G;
TRACE LOGS OPTIMIZER SELECT CURRENT_TIMESTAMP()\G;`的結果如下:
...
*************************** 34. row ***************************
Explain String: 0ms| [MV TRACE] [PREPARE GLOBAL] There are no valid related mvs for the query plan
*************************** 35. row ***************************
Explain String: 0ms| [MV TRACE] [PREPARE GLOBAL] MV rewrite strategy: MvRewriteStrategy{enableMaterializedViewRewrite=false, enableForceRBORewrite=false, enableViewBasedRewrite=false, enableSingleTableRewrite=false, enableMultiTableRewrite=false, mvStrategy=DEFAULT}
*************************** 36. row ***************************
Explain String: 0ms| origin logicOperatorTree:
*************************** 37. row ***************************
Explain String: LogicalProjectOperator {projection=[2025-03-28 10:35:00]}
*************************** 38. row ***************************
Explain String: -> LOGICAL_VALUES
*************************** 39. row ***************************
Explain String: 0ms| [TRACE QUERY 3efdbff0-0b7d-11f0-8f6c-00163e164034] APPLY RULE TF_PRUNE_PROJECT_COLUMNS 58
*************************** 40. row ***************************
Explain String: Original Expression:
*************************** 41. row ***************************
Explain String: LogicalProjectOperator {projection=[2025-03-28 10:35:00]}
*************************** 42. row ***************************
Explain String: -> LOGICAL_VALUES
*************************** 43. row ***************************
Explain String: 0ms|
*************************** 44. row ***************************
Explain String: New Expression:
*************************** 45. row ***************************
Explain String: 0:LogicalProjectOperator {projection=[2025-03-28 10:35:00]}
...
TRACE LOGS OPTIMIZER SELECT (date_format(current_time(), '%Y-%m-%d %H:%i:%S'))\G
的結果如下:
...
*************************** 34. row ***************************
Explain String: 0ms| [MV TRACE] [PREPARE GLOBAL] MV rewrite strategy: MvRewriteStrategy{enableMaterializedViewRewrite=false, enableForceRBORewrite=false, enableViewBasedRewrite=false, enableSingleTableRewrite=false, enableMultiTableRewrite=false, mvStrategy=DEFAULT}
*************************** 35. row ***************************
Explain String: 0ms| origin logicOperatorTree:
*************************** 36. row ***************************
Explain String: LogicalProjectOperator {projection=[date_format(cast(current_time() as datetime), %Y-%m-%d %H:%i:%S)]}
*************************** 37. row ***************************
Explain String: -> LOGICAL_VALUES
*************************** 38. row ***************************
Explain String: 0ms| [TRACE QUERY 7af2e9bb-0b7e-11f0-8f6c-00163e164034] APPLY RULE TF_PRUNE_PROJECT_COLUMNS 58
*************************** 39. row ***************************
Explain String: Original Expression:
*************************** 40. row ***************************
Explain String: LogicalProjectOperator {projection=[date_format(cast(current_time() as datetime), %Y-%m-%d %H:%i:%S)]}
*************************** 41. row ***************************
Explain String: -> LOGICAL_VALUES
*************************** 42. row ***************************
Explain String: 0ms|
*************************** 43. row ***************************
Explain String: New Expression:
*************************** 44. row ***************************
Explain String: 0:LogicalProjectOperator {projection=[date_format(cast(current_time() as datetime), %Y-%m-%d %H:%i:%S)]}
*************************** 45. row ***************************
Explain String: -> LOGICAL_VALUES
...
可以看到 CURRENT_TIMESTAMP
在優化算子階段就已經計算出來了,為LogicalProjectOperator {projection=[2025-03-28 10:35:00]}
而 (date_format(current_time(), '%Y-%m-%d %H:%i:%S'))
并沒有計算出來,為LogicalProjectOperator {projection=[date_format(cast(current_time() as datetime), %Y-%m-%d %H:%i:%S)]}
在這個案例中,主要涉及到的規則主要是:
FoldConstantsRule
PartitionPruneRule
我們分析一下簡單的SQL語句的數據流:SELECT CURRENT_TIMESTAMP()
g4文件中querySpecification||\/
ConnectProcessor.handleQuery||\/
com.starrocks.sql.parser.SqlParser.parse||\/
// 同時.g4 文件中 specialDateTimeExpression
// AstBuilder.visitSpecialDateTimeExpression 會構造 new FunctionCallExpr
// 這里最終會構建 SelectRelation(SelectList(FunctionCallExpr),ValuesRelation.newDualRelation)
AstBuilder.visitQuerySpecification ||\/
StatementPlanner.plan||\/
createQueryPlan||\/
new RelationTransformer(transformerContext).transformWithSelectLimit(query) ||\/
transform||\/
visit(relation);||\/
RelationTransformer.visitSelect||\/
QueryTransformer.plan||\/
SqlToScalarOperatorTranslator.translate => Visitor.visit => visitFunctionCall // 此時的邏輯計劃為 SelectRelation(SelectList(CallOperator(CURRENT_TIMESTAMP)),ValuesRelation.newDualRelation)||\/=> scalarRewriter.rewrite(result, ScalarOperatorRewriter.DEFAULT_REWRITE_RULES) // 這里有ImplicitCastRule和FoldConstantsRule||\/
projectForOrder // 此時的的邏輯計劃為 LogicalPlan(OptExprBuilder(LogicalProjectOperator(CallOperator(CURRENT_TIMESTAMP)))
現在來重點關注 DEFAULT_REWRITE_RULES 中涉及到的 ImplicitCastRule FoldConstantsRule 規則:
首先是 ImplicitCastRule 規則(這里主要是visitCall方法):
這個規則主做:
1. 如果表達式需要的類型和該表達式對應的子表達式的參數輸出的類型如果不一致的話,則會給表達式的子表達式加上一個CastOperator操作
2. 對每一個子表達式都遞歸一遍1步驟
date_format(current_time(), '%Y-%m-%d %H:%i:%S')
就會命中這個規則
再次 是 FoldConstantsRule 規則(這里主要是visitCall/visitCastOperator方法):
這個主要是做:
1. 主要是計算表達式為常量,即把CallOperator變成 ConstantOperator
2. 根據ScalarOperatorFunctions 和 MetaFunctions 函數中標注為 ConstantFunction 的函數,來看是否能夠計算為常量
在這里能夠找到 CURRENT_TIMESTAMP() 函數,但是找不到 current_time() 函數
CURRENT_TIMESTAMP()
就會命中這個規則
以上的 都在 “Transformer” 階段完成的。
至于 PartitionPruneRule 則會在“Optimizer” 階段完成 ,也就是optimizer.optimize
方法中, 具體的實現,可以細看 PartitionPruneRule對應的方法,也就是在這個規則里會對涉及到的謂詞來過濾出對應的分區,很顯然因為CURRENT_TIMESTAMP
是常量,所以能夠裁剪到對應的分區中去,而date_format(current_time(), '%Y-%m-%d %H:%i:%S')
不能計算出來,所以掃描了全表。
其他
trace輸出信息的怎么回事
首先在g4文件中
queryStatement: (explainDesc | optimizerTrace) ? queryRelation outfile?;
有對應的optimze語句 也就是 TRACE LOGS
這個在解析的時候 AstBuilder.visitQueryStatement 中會調用 queryStatement.setIsTrace
方法:
public void setIsTrace(Tracers.Mode mode, String module) {this.isExplain = true;this.traceMode = mode;this.traceModule = module;}
此時 isExplain 設置為了true
之后在 StmtExecutor.execute方法中:
} else if (parsedStmt.isExplain()) {String explainString = buildExplainString(execPlan, ResourceGroupClassifier.QueryType.SELECT,parsedStmt.getExplainLevel());if (executeInFe) {explainString = "EXECUTE IN FE\n" + explainString;}
這里的方法buildExplainString
就會組裝對應的explain信息:
if (parsedStmt.getTraceMode() == Tracers.Mode.TIMER) {explainString += Tracers.printScopeTimer();} else if (parsedStmt.getTraceMode() == Tracers.Mode.VARS) {explainString += Tracers.printVars();} else if (parsedStmt.getTraceMode() == Tracers.Mode.TIMING) {explainString += Tracers.printTiming();} else if (parsedStmt.getTraceMode() == Tracers.Mode.LOGS) {explainString += Tracers.printLogs();} else if (parsedStmt.getTraceMode() == Tracers.Mode.REASON) {explainString += Tracers.printReasons();} else {explainString += execPlan.getExplainString(explainLevel);}
所以在執行trace LOGS
命令的時候會輸出對應信息