- 人工智能
- 机器学习
- 后端
- 编译器
【免费下载链接】sqlflow
Brings SQL and AI together.
本文以 SQLFlow 官方 Code Walkthrough 为骨架,结合当前仓库源码,系统梳理 SQLFlow 的整体架构与核心执行链路:用户如何在 Jupyter Notebook 或命令行中编写TO TRAIN/TO PREDICT扩展 SQL 语句,gRPC 服务端如何依次经过解析、校验、代码生成与执行四个环节,最终把 SQL 程序翻译成调用 TensorFlow / XGBoost 的 Python 程序,并探讨其可扩展设计与作业管理方案的演进(含 Argo 工作流模式)。读完本文,你将掌握 SQLFlow 端到端的工作原理,并能在源码层面定位每一步对应的实现文件。
用户体验:用 SQL 直接写机器学习
SQLFlow 允许用户在 Jupyter Notebook(通过 SQLFlow magic 插件)或命令行工具中,编写带扩展语法的 SQL 程序。扩展语法的精髓在于:把机器学习任务写成一条 SQL 语句。例如,下面这条语句训练一个名为DNNClassifier的 TensorFlow 模型(DNNClassifier是派生自tf.estimator.Estimator的 Python 类):
SELECT * FROM a_table TO TRAIN DNNClassifier WITH learning_rate=0.01 INTO sqlflow_models.my_model;而下面这条语句则使用训练好的模型进行预测,并把结果写入b_table.predicted_label列:
SELECT * FROM b_table TO PREDICT b_table.predicted_label USING sqlflow_models.my_model;README 中给出了更完整的示例(参见 README.md):
sqlflow> SELECT * FROM iris.train TO TRAIN DNNClassifier WITH model.n_classes = 3, model.hidden_units = [10, 20] COLUMN sepal_length, sepal_width, petal_length, petal_width LABEL class INTO sqlflow_models.my_dnn_model; ... Training set accuracy: 0.96721 Done trainingsqlflow> SELECT * FROM iris.test TO PREDICT iris.predict.class USING sqlflow_models.my_dnn_model; ... Done predicting. Predict table : iris.predict这里有一个非常关键的设计点,也是理解 SQLFlow 系统实现的基础:上述语句中TO TRAIN、TO PREDICT等扩展关键字之前的全部内容,本身就是一条标准 SQL 语句。正是这一设计,大幅简化了 SQLFlow 系统的实现——标准的 SQL 部分可以被原样交给 SQL 引擎去解析和执行,SQLFlow 只需要处理扩展关键字的语义。
系统实现总览
SQLFlow 对两类语句采取完全不同的处理路径:
- 标准 SQL 语句:SQLFlow 直接把它转发(proxy)给 SQL 引擎执行,并把查询结果重定向回用户端。
- 扩展语法语句:SQLFlow 把它翻译成一个 Python 程序(即文档中所称的 submitter / code generator 产物)。
在 MVP(最小可行产品)阶段,生成的 Python 程序做两件事:把标准 SQL 部分(SELECT)交给 MySQL,然后在一个训练循环里读取查询结果并训练模型。文档明确提出了后续的扩展方向:接入更多 SQL 引擎(如 Oracle、Hive、SparkSQL),并生成调用分布式 TensorFlow、PyTorch、XGBoost 等更多机器学习框架的程序。当前仓库中这些方向大多已经落地(详见下文"可扩展性"一节)。
SQLFlow 是一个 gRPC 服务
从架构上看,SQLFlow 本身是一个 gRPC 服务器,可同时连接多个客户端。文档中提到的典型客户端包括:
- pysqlflow:Jupyter Notebook 服务器的 SQLFlow 插件(magic 命令);
- 命令行客户端:即仓库中的 go/cmd/sqlflow/main.go,它通过 gRPC 与服务端交互,支持交互式 REPL 和
-e(执行字符串)、-f(执行文件)等模式。
两者的交互模型如下:
Jupyter Notebook ---(SQL statements)--> SQLFlow gRPC server (SQLFlow magic command) <--(a stream of messages)--gRPC 服务的 protobuf 定义位于 go/proto/sqlflow.proto,核心服务接口为:
rpc Run (Request) returns (stream Response):执行 SQL 语句,返回一个Response流。每个Response要么是表头(Head)、一行数据(Row),要么是一条日志消息(Message),还可能是EndOfExecution(一次执行结束)或Job(Argo 模式下提交的工作流 ID)。rpc Fetch (FetchRequest) returns (FetchResponse):以轮询方式获取作业的阶段与日志(Argo 工作流模式使用)。
表头和行通常来自标准 SQL 语句(如SELECT、DESCRIBE),日志消息通常来自生成的 Python 程序运行过程。在 go/proto/response.go 中可以看到Head、Row的编码细节:每一行数据通过google.protobuf.Any承载,覆盖 nil、bool、各类整型、浮点、字符串、字节数组与时间戳等类型。
从命令行客户端的实现(go/cmd/sqlflow/main.go)可以看到完整的客户端视角:
- 通过
-s/--sqlflow-server指定服务端地址(默认读取环境变量SQLFLOW_SERVER),默认端口50051; - 通过
-d/--data-source指定数据源(默认读取SQLFLOW_DATASOURCE); - 通过
-c/--cert-file指定证书(默认读取SQLFLOW_CA_CRT),支持 TLS; - 客户端调用
client.Run(ctx, req)获得响应流,由 tablewriter 渲染成 ASCII 表格输出。
SQLFlow gRPC 服务端的执行流水线
当 gRPC 服务端通过一次Run调用收到一批 SQL 语句后(对应实现见 go/sqlflowserver/sqlflowserver.go 的Run方法),会对每条语句依次执行以下四个步骤:
- parser(解析器):语法定义见 go/parser/extended_syntax_parser.y,生成解析结果;
- verifier(校验器):语义校验见 go/verifier/verifier.go,根据解析结果验证语义;
- code generator(代码生成器):见 go/codegen,从解析结果生成一个 Python 程序,即 submitter;
- executor(执行器):见 go/sql/executor_ir.go,在本地运行这个 submitter。
其中第 3、4 步仅针对扩展语法语句;对于标准语法语句,服务端直接把它代理给 SQL 引擎执行。
第 1 步:协作式解析
解析是整个链路的第一步。SQLFlow 的解析器采用"协作式"设计:标准 SQL 部分交给第三方解析器,扩展语法部分由自己的语法文件处理。
go/parser/sqlflow_parser.go 中的Parse函数体现了这一机制:它循环调用thirdPartyParse解析标准 SQL 段(依据 dialect 选择 MySQL、Hive、MaxCompute 等第三方解析器,见 go/parser/external),一旦遇到TO TRAIN、TO PREDICT等扩展关键字,再调用parseFirstSQLFlowStmt解析扩展语法部分,并将两者拼接为一条完整的SQLFlowStmt。该结构体(定义于同一文件)同时保存Original(完整原始语句)与SQLFlowSelectStmt(扩展部分解析结果);IsExtendedSyntax()用于判断语句是否包含扩展语法。
go/parser/extended_syntax_parser.y(bison/yacc 语法文件)则定义了扩展语法的全部子句,从源码结构看,扩展语法目前支持:
TrainClause(TO TRAIN,含 Estimator、训练属性、COLUMN 子句、LABEL、USING预训练模型、INTO保存位置);PredictClause(TO PREDICT)、ExplainClause(TO EXPLAIN)、EvaluateClause(TO EVALUATE);OptimizeClause(TO MAXIMIZE/MINIMIZE,数学规划)、RunClause(TO RUN,自定义任务)、ShowTrainClause(SHOW TRAIN)。
第 2 步:语义校验
校验器位于 go/verifier/verifier.go。核心函数Verify的做法是:把标准 SELECT 部分真实下发到数据库执行,取回 1 行样本(FetchSamples,必要时自动追加LIMIT),借助rows.ColumnTypes()获取字段类型,返回FieldTypes(字段名 → 数据库类型名)映射,并检查是否有重名字段。VerifyColumnNameAndType还负责校验预测语句的字段与类型必须与训练语句一致(除 label 外),为预测的正确性提供保障。
从 go/sql/executor_ir.go 的initializeAndCheckAttributes还可以看到另一层"属性校验":对 Train 语句会按模型类型分别调用xgboost.InitializeAttributes、pai.InitializeKMeansAttributes或tensorflow.InitializeAttributes来初始化并检查超参数(如train.batch_size、train.epoch、validation.metrics等,定义见 go/codegen/tensorflow/codegen.go)。
第 3 步与第 4 步:代码生成与执行
ResolveSQLProgram(见 go/sql/executor_ir.go)把解析结果转换为 IR(中间表示,见 go/ir):扩展语法按语句类型生成TrainStmt、PredictStmt、ExplainStmt、EvaluateStmt、OptimizeStmt、RunStmt、ShowTrainStmt,标准语句则包装为NormalStmt。
随后由 executor 执行。执行器的接口定义在 go/executor/executor.go 的Executor接口中,覆盖ExecuteQuery、ExecuteTrain、ExecutePredict、ExecuteExplain、ExecuteEvaluate、ExecuteOptimize、ExecuteRun、ExecuteShowTrain等全部语句类型;New()依据环境变量SQLFLOW_submitter或 Session 中的 submitter 字段选择具体实现(default/local对应pythonExecutor,另有pai、pai_local、alisa、alps等平台执行器)。
执行器调用 Go 标准库os/exec启动 submitter 进程,捕获其 stdout 与 stderr,并通过管道把结果回传给 gRPC 客户端。这里值得注意的设计是:打印日志是 submitter 自身的职责——它需要把日志打到 stdout/stderr 上。Go 端通过 go/pipe/pipe.go 提供的进程内同步管道把日志逐行写入 gRPC 响应流,logChanWriter(见 go/executor/executor.go)负责按行切分并转发。
运行 Python 程序的具体方式见 go/executor/cmd.go 的sqlflowCmd,它按环境自动选择:
- 本地已安装 Python + TensorFlow + 数据库驱动时,直接以
python -u运行; - 否则若存在 Docker,则以
docker run --rm挂载工作目录并执行sqlflow/sqlflow镜像; - 仅有 Python 时直接运行(适用于预装 Python 的服务器镜像)。
最小可行产品(MVP):SQLFlow 实际做了什么
文档指出,MVP 中代码生成器生成的 Python 程序由两部分组成:
- 把扩展语句中的标准 SELECT 部分通过 ODBC 抛给 MySQL;
- 一个循环,读取 SELECT 语句的运行输出,训练模型(或用训练好的模型做预测)。
训练部分调用 TensorFlow 来更新TO TRAIN子句中指定模型的参数。这一设计在当前仓库的代码生成模板中有清晰体现:
- go/codegen/tensorflow/template_train.go 生成训练 Python 程序,其结构为:
import tensorflow as tf、从runtime.tensorflow.train导入train、导入DNNClassifier等 estimator、构造feature_column_names/feature_metas/label_meta等元数据,最终调用train(...); - go/codegen/tensorflow/template_pred.go 生成预测程序,同样携带特征列元数据,调用
runtime.tensorflow.predict的pred。
执行训练时(pythonExecutor.ExecuteTrain,见 go/executor/executor.go):按模型类型调用xgboost.Train或tensorflow.Train生成代码 →runProgram运行 →SaveModel把模型保存到INTO指定的位置。预测时(ExecutePredict)还会先用createPredictionResultTable建好结果表,再运行生成的预测代码。
标准查询语句则由ExecuteQuery(见 go/executor/sql_stmt.go 的runNormalStmt)处理:通过isQuery粗略判断语句是查询(SELECT无INTO、SHOW CREATE/DATABASES/TABLES、DESC、EXPLAIN)还是写操作,分别走runQuery与runExec路径。
可扩展性:用更多代码生成器接入更多引擎与框架
文档指出,通过编写更多代码生成器,SQLFlow 可以扩展支持更多 SQL 引擎(如 Hive、Oracle)和机器学习工具包(如 PyTorch、XGBoost),并运行在各种计算平台上。文档当时设想的代码生成器示例包括:
codegen_distributed_tf.go:生成类似 MVP 但运行分布式 TensorFlow 训练作业的 submitter 程序;codegen_kubernetes_tf.go:在 Kubernetes 集群上启动分布式 TensorFlow 作业,而不是在 SQLFlow gRPC 服务所在容器内本地运行;codegen_gcloud_pytorch.go:生成在 Google Cloud 上调用 PyTorch 而非 TensorFlow 训练的程序。
从当前仓库的源码结构看,这一可扩展设计已经大规模落地:
- 代码生成器目录 go/codegen 下已存在
tensorflow、xgboost、pai(阿里云 PAI 平台,含 KMeans、随机森林等)、optimize(数学规划)、experimental(Couler / 新 IR 路径)等多个代码生成器; - 执行器层面支持
default/local、pai、pai_local、alisa、alps等 submitter(go/executor/executor.go); - 数据库接入层面支持 MySQL、Hive、MaxCompute 等(go/database、go/sql/session.go);
- Python 运行时 python/runtime 下提供了 tensorflow、xgboost、optimize 等对应的执行模块。
作业管理(Job Management)与 Argo 工作流模式
文档指出了 gRPC 接口设计中的一个关键假设:客户端(如 Jupyter Notebook)与 SQLFlow 服务端之间的连接,在训练程序运行期间必须保持存活。这个假设在多数场景下是合理的——即使数据科学家关闭浏览器、断开与 Jupyter Notebook 服务器的连接,Jupyter 到 SQLFlow 的连接仍可能保持。但若 Jupyter Notebook 服务器运行在用户笔记本上且进程被杀,gRPC 服务端就无法再把消息流回客户端,导致 submitter 失败。
文档提出的解决方案是:把 gRPC 接口改为——一个"提交作业并立即返回"的方法,以及另一个"给定作业 ID 获取最近一批消息"的方法。这一设计在当前仓库中已经实现,即 go/proto/sqlflow.proto 中的Fetch接口与Job消息,配合 Argo 工作流模式:
- 服务端入口 go/cmd/sqlflowserver/main.go 通过
-argo-mode标志选择注册SubmitWorkflow(而不是本地执行路径RunSQLProgram),监听端口默认50051,可通过-port调整,并支持-ca-crt/-ca-key启用 TLS; SubmitWorkflow(见 go/sqlflowserver/sqlflowserver.go)把 SQL 程序编译为 Argo workflow YAML(或 Couler 提交代码),通过 go/workflow/argo 的Submit提交到 Kubernetes,随后立即把工作流 ID 作为Job消息返回给客户端;- 客户端随后以轮询方式调用
Fetch,go/workflow/argo/fetch.go 的Fetch根据 Job ID 读取 Argo 工作流状态与 Pod 日志,输出形如Step [1/3] Execute Code: ...、Step [1/3] Status: Running的进度消息,直至工作流完成。
详细的 Argo 工作流设计参见 doc/design/argo_workflow_on_sqlflow.md。这一演进正好呼应了文档中"We will make a design for that soon"的规划,也让 SQLFlow 从"必须保持长连接"的本地执行模型,走向了可在 Kubernetes 上可靠运行、可断点拉取日志的工作流模式。
- 人工智能
- 机器学习
- 后端
- 编译器
【免费下载链接】sqlflow
Brings SQL and AI together.
相关推荐
TiDB 源码阅读之 Executor:从 SQL 语句到执行器的完整链路解析
TiDB 源码阅读之 Executor:从 SQL 语句到执行器的完整链路解析 本文是 Go 夜读第 47 期「TiDB 源码阅读之 Executor」分享的深
文档教程从 SELECT 到训练流水线:SQLFlow 扩展 SQL 语法的设计与实现详解
从 SELECT 到训练流水线:SQLFlow 扩展 SQL 语法的设计与实现详解 本文以 SQLFlow 仓库中的总体设计文档 doc/design/synt
人工智能机器学习后端编译器MyBatis 动态 SQL 源码解析:从 DynamicSqlSource 到 SQL 执行全链路
MyBatis 动态 SQL 源码解析:从 DynamicSqlSource 到 SQL 执行全链路 MyBatis 的 <if 、 <trim 、 <wher
文档教程知识库
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考