Apache DataFusion 快速入门与集成示例
Apache DataFusion 快速入门,从环境准备、首个查询与结果解读入手,给出 Python 集成 DataFusion 分析 CSV/Parquet、与 pandas/Arrow 互操作及注册自定义函数的样例代码与实测输出。
Apache DataFusion 快速入门与集成示例
Apache DataFusion 是用 Rust 实现、基于 Apache Arrow 的可扩展查询引擎,可在应用中嵌入并以 SQL 分析 CSV/Parquet/JSON 等数据。本文带你从零完成第一个查询,并给出在 Python 应用中集成的完整示例,所有输出均来自 Apache DataFusion 55.2.0 / Python 绑定 48.0.0 实测环境。
一、快速入门
1.1 环境准备
安装 Python 绑定(以 Ubuntu 22.04 为例,CentOS 8 需用 python3.9 -m pip):
$ pip3 install -i https://pypi.tuna.tsinghua.edu.cn/simple "datafusion==48.0.0"
Successfully installed datafusion-48.0.0 pyarrow-25.0.1 typing-extensions-4.16.0
验证:
$ python3 -c "import datafusion; print(datafusion.__version__)"
48.0.0
如需命令行体验,可另行 cargo install datafusion-cli(见安装指南),版本为独立的 55.2.0。
1.2 第一个查询
准备数据文件 /root/employees.csv:
id,name,dept_id,salary
1,Alice,10,5000
2,Bob,20,6000
3,Carol,10,7000
4,David,30,8000
运行:
from datafusion import SessionContext
ctx = SessionContext()
ctx.register_csv("employees", "/root/employees.csv", has_header=True)
ctx.sql("SELECT dept_id, COUNT(*) AS cnt, AVG(salary) AS avg_salary "
"FROM employees GROUP BY dept_id ORDER BY dept_id").show()
真实输出:
+---------+-----+------------+
| dept_id | cnt | avg_salary |
+---------+-----+------------+
| 10 | 2 | 6000.0 |
| 20 | 1 | 6000.0 |
| 30 | 1 | 8000.0 |
+---------+-----+------------+
1.3 输出解读
ctx.sql(...)返回一个惰性DataFrame,只有调用show()/collect()/to_pandas()时才真正执行。- 结果以 Arrow
RecordBatch承载,按列式排列;COUNT/AVG等聚合由引擎自动并行执行。 - 表头中的
dept_id、cnt、avg_salary对应 SQL 的投影列与别名。
二、核心概念
2.1 SessionContext、DataFrame 与 RecordBatch
- SessionContext:会话入口,持有 catalog、注册表与配置,负责解析并规划 SQL。
- DataFrame:逻辑查询计划的高层封装,支持
select/filter/join/aggregate等链式算子。 - RecordBatch:Arrow 的列式数据批次,是算子间数据传递的基本单元(默认每批 8192 行)。
2.2 逻辑计划与物理计划
EXPLAIN 展示计划树。Python 中实测逻辑计划:
$ python3 -c "..." # EXPLAIN SELECT dept_id, COUNT(*) FROM employees GROUP BY dept_id
plan_type: logical_plan
Projection: employees.dept_id, count(Int64(1)) AS count(*)
Aggregate: groupBy=[[employees.dept_id]], aggr=[[count(Int64(1))]]
TableScan: employees projection=[dept_id]
在 datafusion-cli 中 EXPLAIN 默认输出物理计划(含 AggregateExec、RepartitionExec、DataSourceExec 等算子)。
三、集成示例:CSV 数据分析
对一个 CSV 做过滤、聚合、排序与 JOIN:
from datafusion import SessionContext
ctx = SessionContext()
ctx.register_csv("employees", "/root/employees.csv", has_header=True)
ctx.register_csv("depts", "/root/depts.csv", has_header=True)
# 过滤 + 排序
print(ctx.sql("SELECT name, salary FROM employees WHERE salary > 5500 ORDER BY salary")
.to_pandas().to_string(index=False))
# JOIN
ctx.sql("SELECT e.name, d.dept_name FROM employees e JOIN depts d "
"ON e.dept_id = d.dept_id ORDER BY e.name").show()
实测输出:
name salary
0 Bob 6000
1 Carol 7000
2 David 8000
+-------+-------------+
| name | dept_name |
+-------+-------------+
| Alice | Engineering |
| Bob | Sales |
| Carol | Engineering |
| David | Marketing |
+-------+-------------+
3.1 结果落地为表(CTAS)
ctx.sql("CREATE TABLE high_paid AS SELECT name, salary FROM employees WHERE salary >= 7000")
ctx.sql("SELECT * FROM high_paid ORDER BY salary").show()
+-------+--------+
| name | salary |
+-------+--------+
| Carol | 7000 |
| David | 8000 |
+-------+--------+
四、集成示例:Parquet 与 Arrow/pandas 互操作
4.1 读取 Parquet 并统计
import pyarrow as pa, pyarrow.parquet as pq
from datafusion import SessionContext
tbl = pa.table({"city": ["bj", "sh", "gz"], "sales": [100, 200, 150]})
pq.write_table(tbl, "/root/sales.parquet")
ctx = SessionContext()
ctx.register_parquet("sales", "/root/sales.parquet")
print(ctx.sql("SELECT city, sales FROM sales ORDER BY sales DESC")
.to_pandas().to_string(index=False))
print("total:", ctx.sql("SELECT SUM(sales) AS total FROM sales").collect()[0].to_pydict())
实测输出:
city sales
sh 200
gz 150
bj 100
total: {'total': [450]}
4.2 从 pandas / Arrow 直接建表
import pandas as pd, pyarrow as pa
from datafusion import SessionContext
pdf = pd.DataFrame({"k": ["a", "b", "c"], "v": [1, 2, 3]})
ctx = SessionContext()
ctx.register_record_batches("t", [pa.Table.from_pandas(pdf).to_batches()])
print(ctx.sql("SELECT k, v*10 AS v10 FROM t ORDER BY k").to_pandas().to_string(index=False))
k v10
a 10
b 20
c 30
也可用 ctx.from_pandas(pdf)、ctx.from_arrow(tbl) 直接构造 DataFrame,用 df.to_pandas()、df.to_arrow_table() 回到 Python 生态,实现零拷贝式的列式互操作。
五、集成示例:在应用中注册自定义函数(UDF)
将 Python 函数注册为 SQL 可调用的标量函数,扩展引擎能力:
import pyarrow as pa
from datafusion import SessionContext, udf
def double_it(x):
return pa.compute.multiply(x, 2)
ctx = SessionContext()
# 签名:udf(func, input_types, return_type, volatility, name)
ctx.register_udf(udf(double_it, [pa.int64()], pa.int64(), "immutable", "double_it"))
ctx.register_record_batches("nums", [pa.table({"n": [1, 2, 3]}).to_batches()])
print(ctx.sql("SELECT n, double_it(n) AS doubled FROM nums ORDER BY n")
.to_pandas().to_string(index=False))
实测输出:
n doubled
1 2
2 4
3 6
除标量 UDF(register_udf)外,还可用 register_udaf(聚合)、register_udtf(表函数)、register_udwf(窗口函数)扩展引擎。
六、常见问题
Q1:为什么 SHOW 或 information_schema 查询报错?
未开启 information_schema 时,SHOW [VARIABLE] 会报 SHOW [VARIABLE] is not supported unless information_schema is enabled。用 SessionConfig().with_information_schema(True) 创建会话即可。
Q2:EXPLAIN 通过 Python show() 为什么报 "Explain must be root of the plan"?
show() 会在计划外围再包一层算子,而 EXPLAIN 必须位于计划根部。改用 ctx.sql("EXPLAIN ...").collect() 读取结果,或使用 DataFrame.explain()。
Q3:cargo install datafusion-cli 很慢怎么办?
首次需从源码编译整棵依赖树(12 核约 15 分钟)。可配置 crates.io 镜像加速下载,编译只需一次。
Q4:CentOS 8 安装时 pyarrow 报 cmake 缺失?
说明命中了源码构建。固定带 cp39 预编译包的 pyarrow==17.0.0 即可,无需安装 cmake。