Skip to main content
如果你习惯使用传统关系型数据库,可能会想在 ClickHouse 中寻找存储过程和预处理语句。 本指南将介绍 ClickHouse 对这些概念的处理方式,并提供推荐的替代方案。

ClickHouse 中存储过程的替代方案

ClickHouse 不支持包含控制流逻辑 (IF/ELSE、循环等) 的传统存储过程。 这是由 ClickHouse 作为分析型数据库的架构特性决定的有意设计选择。 对于分析型数据库,通常不建议使用循环,因为执行 O(n) 个简单查询往往比执行更少但更复杂的查询更慢。 ClickHouse 主要针对以下场景进行了优化:
  • 分析型工作负载 - 在大型数据集上执行复杂聚合
  • 批处理 - 高效处理海量数据
  • 声明式查询 - 只描述要检索哪些数据,而不是描述如何处理这些数据的 SQL 查询
带有过程式逻辑的存储过程与这些优化方向背道而驰。相应地,ClickHouse 提供了更符合其优势的替代方案。

用户自定义函数 (UDFs)

用户自定义函数可让您在不使用控制流的情况下封装可复用的逻辑。ClickHouse 支持两种类型:

基于 Lambda 的 UDF

使用 SQL 表达式和 Lambda 语法创建函数:
限制:
  • 不支持循环或复杂控制流
  • 不能修改数据 (INSERT/UPDATE/DELETE)
  • 不允许使用递归函数
完整语法请参见 CREATE FUNCTION

可执行 UDF

对于更复杂的逻辑,可使用调用外部程序的可执行 UDF:
可执行 UDF 可使用任何语言 (Python、Node.js、Go 等) 实现任意逻辑。 详情请参阅 可执行 UDF

参数化视图

参数化视图的行为类似于返回数据集的函数。 它们非常适合用于带动态过滤条件的可复用查询:

常见用例

更多信息,请参见参数化视图一节。

Materialized views

materialized views 非常适合对原本通常由存储过程完成的高成本聚合进行预计算。如果你使用的是传统数据库,可以将 materialized view 视为一种 插入触发器:它会在数据插入源表时自动进行转换和聚合:

可刷新materialized view

对于按计划执行的批处理 (例如每晚运行的存储过程) :
有关高级用法,请参阅 级联 materialized views

外部编排

对于复杂的业务逻辑、ETL 工作流或多步骤流程,始终可以借助各种编程语言的客户端,在 ClickHouse 外部实现相关逻辑。

使用应用代码

下面通过并排对比,展示如何将 MySQL 存储过程改写为借助 ClickHouse 和应用代码来实现:

关键差异

  1. 控制流 - MySQL 存储过程使用 IF/ELSEWHILE 循环。在 ClickHouse 中,这类逻辑应在应用代码 (Python、Java 等) 中实现
  2. 事务 - MySQL 支持用于 ACID 事务的 BEGIN/COMMIT/ROLLBACK。ClickHouse 是面向分析的数据库,针对只追加工作负载进行了优化,而非事务性更新
  3. 更新 - MySQL 使用 UPDATE 语句。对于可变数据,ClickHouse 更适合使用 INSERT,并配合 ReplacingMergeTreeCollapsingMergeTree
  4. 变量和状态 - MySQL 存储过程可以声明变量 (DECLARE v_discount) 。在 ClickHouse 中,状态应由应用代码管理
  5. 错误处理 - MySQL 支持 SIGNAL 和异常处理程序。在应用代码中,请使用所选编程语言的原生错误处理机制 (try/catch)
何时使用各自的方法:
  • OLTP 工作负载 (订单、支付、用户账户) → 使用带存储过程的 MySQL/PostgreSQL
  • 分析型工作负载 (报表、聚合、时间序列) → 使用 ClickHouse,并由应用程序进行编排
  • 混合架构 → 两者结合使用!将事务型数据从 OLTP 流式传输到 ClickHouse 进行分析

使用工作流编排工具

  • Apache Airflow - 调度并监控由 ClickHouse 查询构成的复杂 DAG
  • dbt - 使用基于 SQL 的工作流转换数据
  • Prefect/Dagster - 基于 Python 的现代编排工具
  • Custom schedulers - Cron 作业、Kubernetes CronJob 等
使用外部编排的优势:
  • 完整的编程语言能力
  • 更完善的错误处理和重试逻辑
  • 与外部系统集成 (API、其他数据库)
  • 版本控制和测试
  • 监控和告警
  • 更灵活的调度

ClickHouse 中预处理语句的替代方案

虽然 ClickHouse 没有传统关系型数据库意义上的“预处理语句”,但它提供了查询参数,可实现相同的目的:以安全的参数化查询方式防止 SQL 注入。

语法

定义查询参数有两种方式:

方法 1:使用 SET

方法 2:使用 CLI 参数

参数语法

参数通过以下语法引用:{parameter_name: DataType}
  • parameter_name - 参数名称 (不含 param_ 前缀)
  • DataType - 用于将参数转换为该类型的 ClickHouse 数据类型

数据类型示例


有关在编程语言客户端中使用查询参数的信息,请参阅相应编程语言客户端的文档。

查询参数的局限性

查询参数不是通用的文本替换机制。它们有以下特定限制:
  1. 它们主要用于 SELECT 语句 - 对 SELECT 查询的支持最完善
  2. 它们只能用作标识符或字面量 - 不能替换任意 SQL 片段
  3. 它们对 DDL 的支持有限 - 在 CREATE TABLE 中支持,但在 ALTER TABLE 中不支持
适用的情况:
以下方式行不通:

安全最佳实践

始终使用查询参数传递用户输入:
验证输入类型:

MySQL 协议预处理语句

ClickHouse 的 MySQL 接口 对预处理语句 (COM_STMT_PREPARECOM_STMT_EXECUTECOM_STMT_CLOSE) 仅提供有限支持,主要是为了让 Tableau Online 这类会将查询封装为预处理语句的工具能够连接。 主要限制:
  • 不支持参数绑定 - 不能将 ? 占位符与绑定参数配合使用
  • 查询会被存储,但在 PREPARE 阶段不会被解析
  • 该实现非常精简,仅用于兼容特定的 BI 工具
以下示例无法工作:
请改用 ClickHouse 的原生查询参数。 它们在所有 ClickHouse 接口中都提供完整的参数绑定支持、类型安全,以及 SQL 注入防护:
更多详情,请参阅 MySQL 接口文档关于 MySQL 支持的博文

摘要

ClickHouse 中存储过程的替代方案

查询参数的用途

查询参数可用于:
  • 防止 SQL 注入
  • 实现类型安全的参数化查询
  • 在应用程序中进行动态过滤
  • 复用查询模板
最后修改于 2026年7月3日