ClickHouse 中的存储过程和查询参数
如果你之前使用的是传统关系型数据库,可能会在 ClickHouse 中寻找存储过程和预处理语句(prepared statements)。 本指南将解释 ClickHouse 对这些概念的处理方式,并提供推荐的替代方案。
ClickHouse 中存储过程的替代方案
ClickHouse 不支持带有控制流逻辑(IF/ELSE、循环等)的传统存储过程。
这是基于 ClickHouse 作为分析型数据库的架构而做出的刻意设计选择。
在分析型数据库中不鼓励使用循环,因为处理 O(n) 个简单查询通常比处理少量复杂查询更慢。
ClickHouse 针对以下场景进行了优化:
- 分析型工作负载 - 对大型数据集进行复杂聚合
- 批处理 - 高效处理大规模数据量
- 声明式查询 - 使用 SQL 查询描述要检索哪些数据,而不是如何处理这些数据
带有过程式逻辑的存储过程与这些优化方向相悖。为此,ClickHouse 提供了与其优势相匹配的替代方案。
用户定义函数(UDF)
用户定义函数无需使用控制流即可封装可复用的逻辑。ClickHouse 支持两种类型:
基于 Lambda 的 UDF
使用 SQL 表达式和 lambda 语法创建函数:
用于示例的样本数据
限制:
- 不支持循环或复杂的控制流
- 不能修改数据(
INSERT/UPDATE/DELETE) - 不允许使用递归函数
完整语法请参阅 CREATE FUNCTION。
可执行 UDF
对于更复杂的逻辑,可以使用可执行 UDF 函数来调用外部程序:
可执行 UDF 可以使用任意语言(Python、Node.js、Go 等)来实现任意逻辑。
有关详细信息,请参阅可执行 UDF。
参数化视图
参数化视图类似于返回数据集的函数。 它们非常适合带有动态过滤条件的可复用查询:
示例数据
常见用例
有关详细信息,请参阅参数化视图部分。
物化视图
物化视图非常适合用于预先计算那些在传统方案中通常由存储过程执行的高开销聚合操作。如果你有使用传统数据库的背景,可以把物化视图理解为一种 INSERT 触发器(trigger),它会在数据插入到源表时自动对其进行转换和聚合:
可刷新物化视图
用于计划的批处理任务(例如每晚运行的存储过程):
有关更高级的用法,请参阅 级联物化视图。
外部编排
对于复杂的业务逻辑、ETL 工作流或多步骤流程,可以在 ClickHouse 外部使用各类语言客户端来实现这些逻辑。
使用应用代码
下面是一个对比示例,展示了如何将 MySQL 存储过程改写为 ClickHouse 的应用代码:
- MySQL 存储过程
- ClickHouse 应用代码
以下示例使用了 ClickHouse 中的查询参数。 如果您尚不熟悉 ClickHouse 中的查询参数,请跳转至"ClickHouse 中预处理语句的替代方案"。
关键区别
- 控制流 - MySQL 存储过程使用
IF/ELSE、WHILE等循环结构。在 ClickHouse 中,应在应用程序代码(Python、Java 等)中实现这类逻辑。 - 事务 - MySQL 支持
BEGIN/COMMIT/ROLLBACK以实现 ACID 事务。ClickHouse 是为追加写入型工作负载优化的分析型数据库,而不是面向事务性更新。 - 更新 - MySQL 使用
UPDATE语句。对于可变数据,ClickHouse 更倾向于使用INSERT配合 ReplacingMergeTree 或 CollapsingMergeTree。 - 变量和状态 - MySQL 存储过程可以声明变量(
DECLARE v_discount)。在 ClickHouse 中,应在应用程序代码中管理状态。 - 错误处理 - MySQL 支持
SIGNAL和异常处理器。在应用程序代码中,应使用所用语言的原生错误处理机制(try/catch)。
何时采用各方案:
- OLTP 工作负载(订单、支付、用户账户)→ 使用带存储过程的 MySQL/PostgreSQL
- 分析型工作负载(报表、聚合、时序数据)→ 使用配合应用程序编排的 ClickHouse
- 混合架构 → 两者都用!将 OLTP 中的事务数据流式传输到 ClickHouse 用于分析
使用工作流编排工具
- Apache Airflow - 调度和监控由 ClickHouse 查询组成的复杂 DAG
- dbt - 使用基于 SQL 的工作流进行数据转换
- Prefect/Dagster - 现代的、基于 Python 的编排框架
- 自定义调度器 - Cron 作业、Kubernetes CronJob 等
使用外部编排的优势:
- 完整的编程语言特性
- 更完善的错误处理和重试逻辑
- 与外部系统集成(API、其他数据库)
- 版本控制和测试
- 监控与告警
- 更灵活的调度
ClickHouse 中预处理语句的替代方案
虽然 ClickHouse 不支持传统关系型数据库(RDBMS)中的“预处理语句(prepared statements)”,但它提供了具有相同作用的查询参数:通过安全的参数化查询来防止 SQL 注入。
语法
定义查询参数有两种方式:
方法 1:使用 SET
示例表和数据
方法 2:使用 CLI 参数
参数语法
参数使用以下语法进行引用:{parameter_name: DataType}
parameter_name- 参数名称(不包含param_前缀)DataType- 参数要转换成的 ClickHouse 数据类型
数据类型示例
示例所用的表和示例数据
- 字符串与数字
- 日期与时间
- 数组
- 映射
- 标识符
关于在语言客户端中使用查询参数,请参考相应语言客户端的文档。
查询参数的限制
查询参数不是通用的文本替换机制。它们有特定的限制:
- 它们主要面向 SELECT 语句——对 SELECT 查询的支持最佳
- 它们只能作为标识符或字面量使用——不能替换任意 SQL 片段
- 它们对 DDL 的支持有限——在
CREATE TABLE中受支持,但在ALTER TABLE中不受支持
可以正常工作的用法:
不支持的内容:
安全最佳实践
对所有用户输入一律使用查询参数:
校验输入类型:
MySQL 协议预处理语句
ClickHouse 的 MySQL 接口 对预处理语句(COM_STMT_PREPARE、COM_STMT_EXECUTE、COM_STMT_CLOSE)仅提供有限支持,主要用于兼容诸如 Tableau Online 这类会将查询包装为预处理语句的工具,以便建立连接。
主要限制:
- 不支持参数绑定 - 不能使用带绑定参数的
?占位符 - 查询在
PREPARE期间会被存储,但不会被解析 - 实现非常精简,仅用于特定 BI 工具的兼容性场景
以下示例是不可行的用法:
请改用 ClickHouse 的原生查询参数。 它们在所有 ClickHouse 接口中都提供完善的参数绑定支持、类型安全以及 SQL 注入防护:
有关更多详情,请参阅 MySQL 接口文档 和 关于 MySQL 支持的博客文章。
总结
ClickHouse 中用于替代存储过程的方案
| 传统存储过程模式 | ClickHouse 中的替代方案 |
|---|---|
| 简单计算和转换 | 用户自定义函数(UDF) |
| 可复用的参数化查询 | 参数化视图 |
| 预计算聚合 | 物化视图 |
| 定时批处理 | 可刷新的物化视图 |
| 复杂的多步骤 ETL 流程 | 链式物化视图或外部编排(Python、Airflow、dbt) |
| 带有控制流的业务逻辑 | 应用程序代码 |
查询参数的使用
查询参数可用于:
- 防止 SQL 注入
- 类型安全的参数化查询
- 在应用中进行动态过滤
- 可重用的查询模板
相关文档
CREATE FUNCTION- 用户定义函数CREATE VIEW- 视图,包括参数化视图和物化视图- SQL Syntax - Query Parameters - 参数语法的完整说明
- Cascading Materialized Views - 高级物化视图模式
- Executable UDFs - 可执行 UDF(外部函数执行)