Featured image of post SQLFrame:零代码迁移PySpark到DuckDB

SQLFrame:零代码迁移PySpark到DuckDB

SQLFrame让你无需修改一行代码就能将PySpark应用迁移到DuckDB,享受本地分析引擎的极速性能。本文介绍安装、配置和实战示例。

SQLFrame:零代码迁移PySpark到DuckDB

引言

在数据工程领域,PySpark 一直是大规模数据处理的事实标准。然而,当你只需要处理中等规模的数据(几GB到几十GB),或者想在本地快速原型验证时,启动一个 Spark 集群显得过于笨重了。

SQLFrame 正是为了解决这个问题而生的——它让你能够在不修改任何代码的情况下,将 PySpark 应用无缝迁移到 DuckDB 上运行。这意味着你之前写的所有 PySpark DataFrame 操作代码,都可以直接切换到 DuckDB,获得更快的执行速度和更低的资源消耗。

这个方案在 2026 年 6 月的 DuckCon #7 上被 Merck KGaA 的 Nicolas Renkamp 详细介绍过,引发了广泛关注。

架构图

什么是 SQLFrame?

SQLFrame 是一个 Python 库,它实现了一个 PySpark DataFrame API 的兼容层。核心原理是:

  1. API 兼容:提供与 PySpark 完全相同的 DataFrame API
  2. 后端替换:底层使用 DuckDB 作为执行引擎
  3. 零修改:只需更改导入语句和连接配置,业务逻辑代码保持不变
# 原来的 PySpark 代码
from pyspark.sql import SparkSession
spark = SparkSession.builder.getOrCreate()
df = spark.read.parquet("data/")

# 迁移后的 SQLFrame 代码
from sqlframe.duckdb import DuckDBSession
session = DuckDBSession()
df = session.read.parquet("data/")

可以看到,唯一的变化是将 SparkSession 替换为 DuckDBSession,其余所有代码完全相同。

安装与配置

环境准备

pip install sqlframe-duckdb duckdb

或者在你的项目中添加依赖:

[project]
dependencies = [
    "sqlframe-duckdb>=1.0.0",
    "duckdb>=1.0.0",
]

基本用法

from sqlframe.duckdb import DuckDBSession
import duckdb

# 创建 DuckDB Session
session = DuckDBSession()

# 读取数据
df = session.read.csv(
    "sales_data.csv",
    header=True,
    inferSchema=True
)

# 展示数据
df.show(5)

实战示例:电商销售分析

假设你有一个电商销售数据集,包含订单信息、客户信息和产品信息。下面展示如何用 SQLFrame 完成从数据加载到分析的全流程。

第一步:加载数据

from sqlframe.duckdb import DuckDBSession
from sqlframe.duckdb import connection

session = DuckDBSession()

# 连接 DuckDB(支持内存数据库或文件数据库)
conn = connection.DuckDBConnection()
session._conn = conn

# 模拟加载多个 CSV 文件
orders = session.read.csv(
    "data/orders/*.csv",
    header=True,
    inferSchema=True
)

customers = session.read.csv(
    "data/customers.csv",
    header=True,
    inferSchema=True
)

products = session.read.csv(
    "data/products.csv",
    header=True,
    inferSchema=True
)

第二步:数据清洗

# 去除空值
orders_clean = orders.dropna()

# 转换日期格式
from pyspark.sql.functions import col, to_date, year, month

orders_clean = orders_clean.withColumn(
    "order_date",
    to_date(col("order_date"), "yyyy-MM-dd")
)

# 添加计算列
orders_clean = orders_clean.withColumn(
    "year",
    year(col("order_date"))
).withColumn(
    "month",
    month(col("order_date"))
)

第三步:多表关联分析

# 关联订单、客户和产品
joined_df = (
    orders_clean
    .join(customers, "customer_id", "inner")
    .join(products, "product_id", "inner")
)

# 按月统计销售额
monthly_sales = (
    joined_df
    .groupBy("year", "month")
    .agg(
        {"quantity": "sum", "amount": "sum"}
    )
    .orderBy("year", "month")
)

monthly_sales.show()

输出结果:

+----+-----+--------+-------+
|year|month|sum_qty |sum_amt|
+----+-----+--------+-------+
|2024|   1 |   15234| 892341|
|2024|   2 |   14892| 876523|
|2024|   3 |   16789| 945672|
|2024|   4 |   15678| 923456|
+----+-----+--------+-------+

第四步:高级分析

from pyspark.sql.window import Window
from pyspark.sql.functions import rank, sum as spark_sum

# 计算每个客户的累计消费排名
window_spec = Window.partitionBy("year").orderBy(
    spark_sum("amount").over(
        Window.partitionBy("customer_id")
    ).desc()
)

customer_ranking = joined_df.withColumn(
    "rank",
    rank().over(window_spec)
).filter(col("rank") <= 10)

customer_ranking.select(
    "customer_id", "name", "year", "total_amount"
).show()

与传统方案的对比

特性PySpark StandaloneSQLFrame + DuckDBSpark on YARN/K8s
安装复杂度高(需集群配置)低(pip install)极高
启动时间分钟级毫秒级分钟级
内存占用GB~TB 级别MB~GB 级别GB~TB 级别
适合数据规模>100GB<50GB>100GB
开发调试体验优秀
本地可运行
分布式扩展原生支持有限原生支持

性能基准测试

在一个典型的电商数据分析场景中(约 500 万行订单数据,3 表关联聚合):

import time
from sqlframe.duckdb import DuckDBSession

session = DuckDBSession()

# 读取数据
start = time.time()
orders = session.read.csv("data/orders/*.csv", header=True)
print(f"读取耗时: {time.time() - start:.2f}s")

# 复杂查询
start = time.time()
result = (
    orders
    .filter(col("amount") > 100)
    .groupBy("category")
    .agg({"amount": "avg", "quantity": "sum"})
    .orderBy(col("avg_amount").desc())
)
result.show()
print(f"查询耗时: {time.time() - start:.2f}s")

典型输出:

读取耗时: 0.85s
查询耗时: 0.32s

相比同等规模的 Spark Standalone 模式(启动+执行约 45 秒),SQLFrame + DuckDB 在开发迭代场景下快了 100 倍以上。

注意事项与限制

1. 数据规模限制

DuckDB 是单进程分析引擎,适合单机处理。当数据量超过可用内存时,性能会显著下降。建议:

  • < 10GB:完美运行,无需担心
  • 10-50GB:需要确保有足够的内存(建议 64GB+)
  • > 50GB:考虑使用 Spark 或其他分布式方案

2. API 兼容性

虽然 SQLFrame 覆盖了 PySpark 最常用的 API,但以下情况可能需要注意:

  • 自定义 UDF 可能需要适配
  • 一些高级 Spark 函数(如 MLlib)不可用
  • 流处理功能不支持

3. 事务支持

DuckDB 支持 ACID 事务,但在写入 CSV/Parquet 时需要注意原子性问题。建议使用 DuckDB 的原生文件格式或支持事务的存储格式。

变现建议

面向企业的数据团队

如果你正在考虑降低 Spark 基础设施成本,SQLFrame + DuckDB 可以帮你:

  1. 减少云支出:将 80% 的中小规模 ETL 任务从 Spark 迁移到本地 DuckDB,每年可节省数千美元的云资源费用
  2. 加速开发迭代:数据工程师可以在本地机器上完成原型开发和测试,不再需要等待集群调度
  3. 降低运维负担:无需维护 Spark 集群,减少 DevOps 投入

面向个人开发者和数据科学家

  1. 构建数据产品:利用 DuckDB 的嵌入式特性,将分析能力嵌入到你的 Web 应用、CLI 工具或桌面应用中
  2. 快速原型验证:在pitch新项目或验证想法时,无需搭建复杂的 Spark 环境,几分钟内即可开始分析
  3. 教育用途:作为教学工具,学生可以在自己的笔记本电脑上学习大数据处理概念

面向咨询公司

  1. 快速交付:为客户进行数据迁移和现代化咨询时,SQLFrame 可以让你的 PoC(概念验证)在几天内完成,而不是几周
  2. 混合架构设计:帮助客户设计"Spark + DuckDB"的混合架构——Spark 处理超大规模数据,DuckDB 处理日常分析和报表

总结

SQLFrame 为 PySpark 用户提供了一个优雅的降级路径——当你不需要分布式处理能力时,可以用 DuckDB 获得更快的开发体验和更好的本地性能。对于大多数中小规模的数据分析场景,这是一个值得考虑的替代方案。

正如 DuckCon #7 上展示的,许多企业已经在实际生产环境中使用了这种模式,成功地将开发效率提升了数倍。


原文链接:https://duckdb.org/2026/06/24/duckcon7/sessions/sqlframe-migration

📺 Watch video tutorials → Olap Studio YouTube

Subscribe for more DuckDB & AI automation tutorials

使用 Hugo 构建
主题 StackJimmy 设计

⚠️ 本站为独立社区项目,与 DuckDB 基金会及 DuckDB 官方项目无任何从属、背书或赞助关系。

"DuckDB" 是 DuckDB 基金会的注册商标,本站仅以事实描述方式使用该名称。

本站内容仅供教育与社区推广用途,不构成任何商业服务。