UDFs 用户自定义函数
ClickHouse 支持多种类型的用户自定义函数(UDF):
- 可执行 UDF 会启动一个外部程序或脚本(Python、Bash 等),并通过 STDIN / STDOUT 以数据块的形式向其进行流式传输。可以使用它们在不重新编译 ClickHouse 的情况下集成现有代码或工具。相比进程内选项,它们的单次调用开销更高,更适合较重的逻辑,或需要不同运行时的场景。
- SQL UDF 使用纯 SQL 的
CREATE FUNCTION定义。它们会被内联/展开到查询计划中(无进程边界),因此非常轻量,适合复用表达式逻辑或简化复杂的计算列。 - 实验性的 WebAssembly UDF 在服务器进程内的沙箱中运行编译为 WebAssembly 的代码。与外部可执行程序相比,它们具有更低的单次调用开销,同时比本地扩展具有更好的隔离性,适用于使用可编译为 WASM 的语言(例如 C/C++/Rust)编写的自定义算法。
可执行用户定义函数
此功能目前在 ClickHouse Cloud 中提供私有预览。 如需开通访问权限,请通过 https://clickhouse.cloud/support 联系 ClickHouse 支持。
ClickHouse 可以调用任意外部可执行程序或脚本来处理数据。
可执行用户定义函数的配置可以位于一个或多个 XML 文件中。
配置路径通过 user_defined_executable_functions_config 参数指定。
函数配置包含以下设置:
| Parameter | Description | Required | Default Value |
|---|---|---|---|
name | 函数名称 | Yes | - |
command | 要执行的脚本名称,或者在 execute_direct 为 false 时要执行的命令 | Yes | - |
argument | 参数描述,包含参数的 type 以及可选的参数 name。每个参数在单独的配置项中描述。如果参数名称是用户定义函数序列化格式(例如 Native 或 JSONEachRow)的一部分,则必须指定名称 | Yes | c + argument_number |
format | 向命令传递参数所使用的格式。命令的输出也必须使用相同的格式 | Yes | - |
return_type | 返回值的类型 | Yes | - |
return_name | 返回值名称。如果返回名称是用户定义函数序列化格式(例如 Native 或 JSONEachRow)的一部分,则必须指定返回名称 | Optional | result |
type | 可执行类型。如果 type 设置为 executable,则启动单个命令;如果设置为 executable_pool,则创建一个命令池 | Yes | - |
max_command_execution_time | 处理一块数据的最大执行时间(秒)。此设置仅对 executable_pool 命令有效 | Optional | 10 |
command_termination_timeout | 在管道关闭后,命令应在该秒数内结束。超过该时间后,将向执行该命令的进程发送 SIGTERM | Optional | 10 |
command_read_timeout | 从命令 stdout 读取数据的超时时间(毫秒) | Optional | 10000 |
command_write_timeout | 向命令 stdin 写入数据的超时时间(毫秒) | Optional | 10000 |
pool_size | 命令池的大小 | Optional | 16 |
send_chunk_header | 控制在发送要处理的数据块之前是否先发送行数 | Optional | false |
execute_direct | 如果 execute_direct = 1,则会在由 user_scripts_path 指定的 user_scripts 目录中搜索 command。可以通过空格分隔指定额外的脚本参数,例如:script_name arg1 arg2。如果 execute_direct = 0,则将 command 作为参数传递给 bin/sh -c | Optional | 1 |
lifetime | 函数的重新加载间隔(秒)。如果设置为 0,则函数不会被重新加载 | Optional | 0 |
deterministic | 是否为确定性函数(对相同输入返回相同结果) | Optional | false |
命令必须从 STDIN 读取参数,并将结果输出到 STDOUT。命令必须以迭代方式处理参数。也就是说,在处理完一块参数后,它必须等待下一块参数。
可执行用户定义函数
示例
使用内联脚本的 UDF
使用 XML 或 YAML 配置手动创建 test_function_sum,并将 execute_direct 显式设置为 0。
- XML
- YAML
文件 test_function.xml(在默认路径设置下路径为 /etc/clickhouse-server/test_function.xml)。
文件 test_function.yaml(在默认路径设置下路径为 /etc/clickhouse-server/test_function.yaml)。
来自 Python 脚本的 UDF
在本示例中,我们创建一个 UDF,它从 STDIN 读取一个值,并将其作为字符串返回。
使用 XML 或 YAML 配置创建 test_function。
- XML
- YAML
文件 test_function.xml(在默认路径设置下为 /etc/clickhouse-server/test_function.xml)。
文件 test_function.yaml(在默认路径设置下为 /etc/clickhouse-server/test_function.yaml)。
在 user_scripts 目录中创建脚本文件 test_function.py(在默认路径设置下为 /var/lib/clickhouse/user_scripts/test_function.py)。
从 STDIN 读取两个值并以 JSON 对象形式返回它们的和
使用具名参数和 JSONEachRow 格式,通过 XML 或 YAML 配置创建 test_function_sum_json。
- XML
- YAML
文件 test_function.xml(在默认路径配置下位于 /etc/clickhouse-server/test_function.xml)。
文件 test_function.yaml(在默认路径配置下位于 /etc/clickhouse-server/test_function.yaml)。
在 user_scripts 目录中创建脚本文件 test_function_sum_json.py(在默认路径配置下位于 /var/lib/clickhouse/user_scripts/test_function_sum_json.py)。
在 command 设置中使用参数
可执行类型的用户自定义函数可以在 command 设置中接受常量参数(这仅适用于 executable 类型的用户自定义函数)。
还需要启用 execute_direct 选项,以避免出现 shell 参数展开带来的安全漏洞。
- XML
- YAML
文件 test_function_parameter_python.xml(在默认路径设置下为 /etc/clickhouse-server/test_function_parameter_python.xml)。
文件 test_function_parameter_python.yaml(在默认路径设置下为 /etc/clickhouse-server/test_function_parameter_python.yaml)。
在 user_scripts 文件夹中创建脚本文件 test_function_parameter_python.py(在默认路径设置下为 /var/lib/clickhouse/user_scripts/test_function_parameter_python.py)。
基于 shell 脚本的 UDF
在本示例中,我们创建一个 shell 脚本,将每个值乘以 2。
- XML
- YAML
文件 test_function_shell.xml(默认路径为 /etc/clickhouse-server/test_function_shell.xml)。
文件 test_function_shell.yaml(默认路径为 /etc/clickhouse-server/test_function_shell.yaml)。
在 user_scripts 文件夹中创建脚本文件 test_shell.sh(默认路径为 /var/lib/clickhouse/user_scripts/test_shell.sh)。
错误处理
如果数据无效,某些函数可能会抛出异常。 在这种情况下,查询会被取消,并向客户端返回错误信息。 对于分布式处理,当某个服务器上发生异常时,其他服务器也会尝试中止该查询。
参数表达式的求值
在几乎所有编程语言中,对于某些运算符,其某个参数可能不会被求值。
常见的例子包括运算符 &&、|| 和 ?:。
在 ClickHouse 中,函数(运算符)的参数始终会被求值。
这是因为 ClickHouse 一次会对成块的列数据进行求值,而不是分别对每一行单独计算。
分布式查询处理中的函数执行
对于分布式查询处理,会尽可能多地在远程服务器上执行查询处理阶段,其余阶段(合并中间结果及其后的所有步骤)在请求方服务器上执行。
这意味着函数可以在不同的服务器上执行。
例如,在查询 SELECT f(sum(g(x))) FROM distributed_table GROUP BY h(y), 中:
- 如果
distributed_table至少有两个分片,函数g和h在远程服务器上执行,而函数f在请求方服务器上执行。 - 如果
distributed_table只有一个分片,则函数f、g和h都在该分片所在的服务器上执行。
函数的结果通常与其在哪台服务器上执行无关。但在某些情况下,这一点很重要。
例如,操作字典的函数会使用其运行所在服务器上的字典。
另一个例子是 hostName 函数,它返回其运行所在服务器的名称,以便在 SELECT 查询中按服务器进行 GROUP BY。
如果查询中的某个函数默认在请求方服务器上执行,但您需要在远程服务器上执行它,可以将其封装在 any 聚合函数中,或者将其加入到 GROUP BY 的键中。
SQL 用户自定义函数
可以使用 CREATE FUNCTION 语句,基于 lambda 表达式创建自定义函数。要删除这些函数,请使用 DROP FUNCTION 语句。
WebAssembly 用户自定义函数
WebAssembly 用户自定义函数(WASM UDF)允许在 ClickHouse 服务器进程中运行编译为 WebAssembly 的自定义代码。
快速入门
在 ClickHouse 配置中启用 WebAssembly 实验性支持:
将已编译好的 WASM 模块插入系统表:
使用 WASM 模块创建一个函数:
在查询中使用该函数:
更多信息
更多信息请参阅 WebAssembly 用户自定义函数 文档。