spark3 spark-sql explain 命令的执行过程

1. SparkSQLDriver

对于每个 SQL 语句,除了 CommandFactory 定义的,如 dfs之外,都创建一个 SparkSQLDriver 对象,然后调用 他的 init方法和 run 方法。

scala 复制代码
override def run(command: String): CommandProcessorResponse = {
    try {
      val substitutorCommand = SQLConf.withExistingConf(context.conf) {
        new VariableSubstitution().substitute(command)
      }
      context.sparkContext.setJobDescription(substitutorCommand)
      val execution = context.sessionState.executePlan(context.sql(command).logicalPlan)
      hiveResponse = SQLExecution.withNewExecutionId(execution) {
        hiveResultString(execution.executedPlan)
      }
      tableSchema = getResultSetSchema(execution)
      new CommandProcessorResponse(0)
    } catch {
        // 
    }
  }

最重要的是

scala 复制代码
val execution = context.sessionState.executePlan(context.sql(command).logicalPlan)

首先执行 context.sql(command)

Context.sql 方法如下

scala 复制代码
def sql(sqlText: String): DataFrame = sparkSession.sql(sqlText)

sparkSession.sql

plan 是解析后的 Unsolved Logical plan.

scala 复制代码
 def sql(sqlText: String): DataFrame = withActive {
    val tracker = new QueryPlanningTracker
    val plan = tracker.measurePhase(QueryPlanningTracker.PARSING) {
      sessionState.sqlParser.parsePlan(sqlText)
    }
    Dataset.ofRows(self, plan, tracker)
  }

DataSet.ofRows

ofRows 的 qe.assertAnalyzed() 对 plan 进行解析

scala 复制代码
 def ofRows(sparkSession: SparkSession, logicalPlan: LogicalPlan, tracker: QueryPlanningTracker)
    : DataFrame = sparkSession.withActive {
    val qe = new QueryExecution(sparkSession, logicalPlan, tracker)
    qe.assertAnalyzed()
    new Dataset[Row](qe, RowEncoder(qe.analyzed.schema))
  }

context.sql(command).logicalPlan 是 DataSet 的 logicalPlan,代码如下:

scala 复制代码
  @transient private[sql] val logicalPlan: LogicalPlan = {
    val plan = queryExecution.commandExecuted
    if (sparkSession.sessionState.conf.getConf(SQLConf.FAIL_AMBIGUOUS_SELF_JOIN_ENABLED)) {
      val dsIds = plan.getTagValue(Dataset.DATASET_ID_TAG).getOrElse(new HashSet[Long])
      dsIds.add(id)
      plan.setTagValue(Dataset.DATASET_ID_TAG, dsIds)
    }
    plan
  }

QueryExecution

DataSet 的 logicalPlan 用到了 queryExecution.commandExecuted 字段,该字段是 lazy 的,第1次用到的时候初始化,里面有用到 analyzed,也是 lazy 的。

analyzed 把 Unsolved Execution Plan 转为 Resolved Execution Plan。 commandExecuted 执行eagerlyExecuteCommands(analyzed)。

第一次访问 commandExecuted 生成 CommandResult 对象,以后再访问DataSet 的 logicalPlan,还是返回 CommandResult 对象。

scala 复制代码
lazy val analyzed: LogicalPlan = executePhase(QueryPlanningTracker.ANALYSIS) {
  // We can't clone `logical` here, which will reset the `_analyzed` flag.
  sparkSession.sessionState.analyzer.executeAndCheck(logical, tracker)
}

lazy val commandExecuted: LogicalPlan = mode match {
  case CommandExecutionMode.NON_ROOT => analyzed.mapChildren(eagerlyExecuteCommands)
  case CommandExecutionMode.ALL => eagerlyExecuteCommands(analyzed)
  case CommandExecutionMode.SKIP => analyzed
}

eagerlyExecuteCommands 返回 CommandResult 对象

scala 复制代码
private def eagerlyExecuteCommands(p: LogicalPlan) = p transformDown {
    case c: Command =>
      val qe = sparkSession.sessionState.executePlan(c, CommandExecutionMode.NON_ROOT)
      val result = SQLExecution.withNewExecutionId(qe, Some(commandExecutionName(c))) {
        qe.executedPlan.executeCollect()
      }
      CommandResult(
        qe.analyzed.output,
        qe.commandExecuted,
        qe.executedPlan,
        result)
    case other => other
  }

SparkSQLDriver.run

继续回到主流程。context.sessionState.executePlan 参数是 CommandResult 对象。

scala 复制代码
val execution = context.sessionState.executePlan(context.sql(command).logicalPlan)
hiveResponse = SQLExecution.withNewExecutionId(execution) {
        hiveResultString(execution.executedPlan)

sessionState.executePlan

默认 mode 为 CommandExecutionMode.ALL。plan 是 CommandResult 对象。

scala 复制代码
 def executePlan(
      plan: LogicalPlan,
      mode: CommandExecutionMode.Value = CommandExecutionMode.ALL): QueryExecution =
    createQueryExecution(plan, mode)
}
scala 复制代码
protected def createQueryExecution:
    (LogicalPlan, CommandExecutionMode.Value) => QueryExecution =
      (plan, mode) => new QueryExecution(session, plan, mode = mode)

所以 val execution = context.sessionState.executePlan(context.sql(command).logicalPlan) 执行后,execution 是 QueryExecution 对象。

SparkSQLDriver.run

scala 复制代码
hiveResponse = SQLExecution.withNewExecutionId(execution) {
  hiveResultString(execution.executedPlan)
}
tableSchema = getResultSetSchema(execution)
相关推荐
RestCloud8 小时前
借助ETL工具,实现AI智能体+数据的落地
数据仓库·人工智能·sql·etl·etlcloud·数据集成平台·java脚本
KaMeidebaby9 小时前
卡梅德生物技术快报|抗体亲和力成熟工业化调控新机制:差异性浆细胞增殖工艺优化思路
java·开发语言·人工智能·算法·机器学习·架构·spark
上海云盾-小余10 小时前
网站频繁遭遇 SQL 注入溯源与原生漏洞修复全流程总结
数据库·sql
_Jimmy_14 小时前
SQLAlchemy 复杂 SQL 执行指南与模板库
python·sql
2601_9549165916 小时前
ChatGPT 5.6 深度实践:用 AI 重构云成本治理体系,而不是简单“降本”
人工智能·sql·chatgpt·重构·测试用例
肖永威16 小时前
金仓数据库麒麟环境SQL终端使用入门实践(增删改查篇)
数据库·sql·金仓数据库
山峰哥17 小时前
数据库工程与SQL优化实战指南‌
数据库·sql·oracle·深度优先·宽度优先
观远数据1 天前
ChatBI选型对比:从意图识别到SQL修复,六个维度打分决定是否值得投产
数据库·人工智能·sql
嘉&年华1 天前
【第008篇】通过dexp和dimp命令导出和导入dmp文件(适用于达梦数据库)
数据库·sql
xieliyu.2 天前
MySQL 六大基础约束详解:NOT NULL/DEFAULT/UNIQUE/ 主键 / 外键 / CHECK
android·数据库·sql·mysql