引言
过去十年,数据仓库和数据湖的界限逐渐模糊,“湖仓一体”(Lakehouse)的概念应运而生。Databricks 的 Delta Lake、Apache Iceberg、Apache Hudi 这三巨头主导了湖仓格式的演进,但它们都有一个共同的问题:太重了。
要让这三个格式跑起来,你需要 Spark、Hive Metastore、HDFS 或对象存储、以及一套编目服务。对于一个中小型团队来说,这不仅是学习成本的陡增,更是运维噩梦。
DuckDB v1.5 带来的 DuckLake 格式,正是为了解决这个问题。
DuckLake 不是要取代 Parquet 或 Delta Lake,而是提供一种适合嵌入式场景的轻量湖仓格式——不需要 Spark、不需要 Metastore,只需要 DuckDB 就能完成从写入、查询到管理的全部流程。
什么是 DuckLake?
DuckLake 是 DuckDB 原生支持的一种结构化湖仓存储格式。它本质上是一组带元数据文件的 Parquet 文件集合,通过事务日志(Transaction Log)来追踪每次写入操作,从而提供 ACID 事务、时间旅行查询、和增量读取能力。
核心特点
- 零外部依赖:不需要 Spark、Hive、HDFS、或任何编目服务
- ACID 事务:支持并发写入与隔离(基于文件级别的乐观锁)
- Schema 演化:支持添加/删除列、修改类型
- 时间旅行:查询任意历史版本
- 增量查询:只读取新写入的分片数据
- 兼容开放格式:底层数据存为 Parquet,任何 Parquet 读取器都能读取
DuckLake 的命名也非常直白:Duck + Lake。DuckDB 是"野鸭",Lake 是"湖"——把湖装进 Duck 里,这本身就是对"轻量湖仓"这一理念的最佳诠释。
安装与配置
DuckLake 作为 DuckDB v1.5 的内置功能,无需额外安装扩展:
-- 检查 DuckDB 版本(需要 v1.5+)
SELECT version();
-- 确认 DuckLake 支持
SELECT * FROM duckdb_extensions() WHERE extension_name = 'ducklake';
对于 Python 用户,同样简单:
import duckdb
con = duckdb.connect()
# DuckLake 直接可用,无需额外 pip 包
COPY TO 语法详解
DuckLake 的核心写入接口是 COPY TO 语句。v1.5 对 COPY TO 进行了大幅扩展,支持直接以 DuckLake 格式写入数据:
基本语法
-- 将查询结果以 DuckLake 格式写入
COPY (SELECT * FROM orders)
TO 'data/orders.ducklake'
(FORMAT DUCKLAKE, APPEND FALSE);
-- 追加写入(创建新版本)
COPY (SELECT * FROM new_orders)
TO 'data/orders.ducklake'
(FORMAT DUCKLAKE, APPEND TRUE);
关键参数
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
FORMAT | 枚举 | 无 | 必须设为 DUCKLAKE |
APPEND | 布尔 | FALSE | TRUE 追加新数据;FALSE 覆盖整个 Lake |
COMPRESSION | 枚举 | ZSTD | Parquet 压缩方式:ZSTD/SNAPPY/LZ4/UNCOMPRESSED |
ROW_GROUP_SIZE | 整数 | 122880 | 每个 Row Group 的行数 |
OVERWRITE_SCHEMA | 布尔 | FALSE | 允许在追加时改变 schema |
PARTITION_BY | 列名列表 | 空 | 按指定列分区存储 |
高级用法
-- 分区写入 + ZSTD 压缩
COPY (SELECT * FROM events WHERE year = 2026)
TO 'data/events.ducklake'
(FORMAT DUCKLAKE, PARTITION_BY (region, dt), COMPRESSION 'ZSTD');
-- 覆盖 schema 的追加写入
COPY (SELECT id, name, email, signup_date FROM users_v2)
TO 'data/users.ducklake'
(FORMAT DUCKLAKE, APPEND TRUE, OVERWRITE_SCHEMA TRUE);
读取 DuckLake
-- 基本读取(最新版本)
SELECT * FROM 'data/orders.ducklake';
-- 时间旅行:读取指定版本
SELECT * FROM 'data/orders.ducklake' (VERSION 3);
-- 时间旅行:读取指定时间戳
SELECT * FROM 'data/orders.ducklake' (TIMESTAMP '2026-05-09 12:00:00');
-- 查看版本历史
SELECT * FROM ducklake_versions('data/orders.ducklake');
管理操作
-- 压缩(合并小文件)
CALL ducklake_compact('data/orders.ducklake');
-- 清理过期版本
CALL ducklake_vacuum('data/orders.ducklake', KEEP_VERSIONS 10);
-- 获取统计信息
SELECT * FROM ducklake_stats('data/orders.ducklake');
多客户端支持
DuckLake 的优秀之处在于,它不仅被 DuckDB 自身支持,还能被多种生态工具读取。以下是主要客户端的支持情况:
Python (DuckDB + PyArrow)
import duckdb
import pandas as pd
con = duckdb.connect()
# 写入 DuckLake
con.execute("""
COPY (SELECT * FROM range(1000000) t(id))
TO 'test.ducklake' (FORMAT DUCKLAKE)
""")
# 读取为 Pandas DataFrame
df = con.execute(
"SELECT * FROM 'test.ducklake'"
).df()
# 读取为 PyArrow Table
import pyarrow as pa
table = con.execute(
"SELECT * FROM 'test.ducklake'"
).arrow()
R 语言
library(duckdb)
library(dplyr)
con <- dbConnect(duckdb())
# 读取 DuckLake
df <- tbl(con, "test.ducklake") %>%
filter(id > 500000) %>%
collect()
print(df)
Java / JDBC
// pom.xml: 添加 duckdb-jdbc 依赖
Connection conn = DriverManager.getConnection("jdbc:duckdb:");
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(
"SELECT count(*) FROM 'data/orders.ducklake'"
);
while (rs.next()) {
System.out.println(rs.getLong(1));
}
Node.js
const duckdb = require('duckdb');
const db = new duckdb.Database(':memory:');
db.all("SELECT * FROM 'data/orders.ducklake' LIMIT 10",
(err, rows) => {
if (err) throw err;
console.log(rows);
}
);
命令行 CLI
# 通过 DuckDB CLI 直接查询
duckdb -c "SELECT region, count(*) FROM 'data/sales.ducklake' GROUP BY region"
# 导出为 CSV
duckdb -c "COPY (SELECT * FROM 'data/sales.ducklake') TO 'export.csv' (HEADER TRUE)"
Parquet / Delta Lake / Iceberg / DuckLake 对比
这是一个详细的维度对比表,帮助你在做技术选型时做出明智决策:
| 维度 | Parquet | Delta Lake | Apache Iceberg | DuckLake v1.0 |
|---|---|---|---|---|
| 类型 | 列式文件格式 | 湖仓表格式 | 湖仓表格式 | 轻量湖仓格式 |
| ACID 事务 | ❌ 不支持 | ✅ 乐观并发控制 | ✅ 乐观并发控制 | ✅ 文件级乐观锁 |
| Schema 演化 | ❌ 不支持 | ✅ 支持 | ✅ 支持 | ✅ 支持 |
| 时间旅行 | ❌ 不支持 | ✅ 默认 30 天 | ✅ 按快照 | ✅ 按版本/时间戳 |
| 增量查询 | ❌ 全部扫描 | ✅ 按版本 | ✅ 按快照 | ✅ 按版本 |
| 分区裁剪 | ✅ 利用统计信息 | ✅ 分区修剪 | ✅ 分区隐藏 | ✅ 分区裁剪 |
| 文件压缩 | ❌ 需外部工具 | ✅ OPTIMIZE 命令 | ✅ rewrite 操作 | ✅ ducklake_compact |
| 元数据管理 | ❌ 无 | Hive Metastore / AWS Glue | Hive / REST / Nessie | 无需 Metastore |
| 运行引擎 | 任意引擎 | Spark / Flink / Trino / DuckDB | Spark / Flink / Trino / DuckDB | DuckDB 原生 |
| CPU 架构 | x86 / ARM / RISC-V | x86 / ARM | x86 / ARM | x86 / ARM / RISC-V |
| 嵌入式场景 | ⚠️ 可用但无事务 | ❌ 太重 | ❌ 太重 | ✅ 天生支持 |
| 外部依赖 | 无 | Spark + Hive + HDFS | Spark + Hive + HDFS | 零依赖 |
| 查询性能 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 写入性能 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| 生态成熟度 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ (快速演进) |
| 开源协议 | Apache 2.0 | Apache 2.0 | Apache 2.0 | MIT |
选型建议
- 你已经用了 Spark/Flink 生态 → 用 Delta Lake 或 Iceberg
- 你只需要一个文件格式 → 用 Parquet
- 你是中小团队,想要湖仓能力但不想要 Spark → DuckLake 是最优选
- 你需要嵌入式或移动端数据方案 → DuckLake (DuckDB 的嵌入式设计天生适配)
- 你做初创项目,快速验证想法 → DuckLake,零运维成本
完整可执行 SQL 示例
以下是一个端到端的实战示例,模拟电商订单分析场景:
-- ========================================
-- DuckLake 实战:电商订单分析
-- ========================================
-- 1. 准备数据
CREATE OR REPLACE TABLE raw_orders AS
SELECT * FROM (VALUES
(1001, 'Alice', '电子', 2999.00, '2026-05-01'::DATE),
(1002, 'Bob', '服装', 459.00, '2026-05-01'::DATE),
(1003, 'Charlie', '食品', 89.90, '2026-05-02'::DATE),
(1004, 'Alice', '图书', 79.00, '2026-05-03'::DATE),
(1005, 'David', '电子', 1599.00, '2026-05-03'::DATE),
(1006, 'Bob', '食品', 120.50, '2026-05-04'::DATE),
(1007, 'Eve', '服装', 899.00, '2026-05-04'::DATE),
(1008, 'Charlie', '电子', 4599.00, '2026-05-05'::DATE),
(1009, 'Alice', '食品', 210.00, '2026-05-06'::DATE),
(1010, 'David', '图书', 150.00, '2026-05-06'::DATE)
) t(order_id, customer, category, amount, order_date);
-- 2. 写入 DuckLake(版本 1)
COPY raw_orders TO 'ecommerce.ducklake' (FORMAT DUCKLAKE);
-- 3. 查看版本历史
SELECT * FROM ducklake_versions('ecommerce.ducklake');
-- 4. 查询:类目销售额汇总
SELECT category,
count(*) AS order_count,
round(sum(amount), 2) AS total_sales,
round(avg(amount), 2) AS avg_amount
FROM 'ecommerce.ducklake'
GROUP BY category
ORDER BY total_sales DESC;
-- 5. 追加新订单(版本 2)
INSERT INTO raw_orders VALUES
(1011, 'Eve', '电子', 3200.00, '2026-05-07'),
(1012, 'Bob', '图书', 55.00, '2026-05-07');
COPY (SELECT * FROM raw_orders WHERE order_id > 1010)
TO 'ecommerce.ducklake' (FORMAT DUCKLAKE, APPEND TRUE);
-- 6. 时间旅行:查看版本 1 的数据
SELECT sum(amount) AS version_1_total
FROM 'ecommerce.ducklake' (VERSION 1);
-- 7. 时间旅行:查看最新数据
SELECT sum(amount) AS latest_total
FROM 'ecommerce.ducklake' (VERSION 2);
-- 8. Schema 演化:添加新列
ALTER TABLE raw_orders ADD COLUMN shipping_address VARCHAR;
UPDATE raw_orders SET shipping_address = CASE
WHEN customer = 'Alice' THEN '北京市海淀区'
WHEN customer = 'Bob' THEN '上海市浦东新区'
WHEN customer = 'Charlie' THEN '广州市天河区'
WHEN customer = 'David' THEN '深圳市南山区'
WHEN customer = 'Eve' THEN '杭州市西湖区'
END;
-- 9. 覆盖写入(包含新列,版本 3)
COPY raw_orders TO 'ecommerce.ducklake'
(FORMAT DUCKLAKE, OVERWRITE_SCHEMA TRUE);
-- 10. 验证 Schema 演化成功
DESCRIBE SELECT * FROM 'ecommerce.ducklake' (VERSION 3);
-- 11. 高级分析:窗口函数
SELECT customer, category, amount,
sum(amount) OVER (PARTITION BY customer) AS customer_total,
rank() OVER (PARTITION BY category ORDER BY amount DESC) AS category_rank
FROM 'ecommerce.ducklake' (VERSION 3)
ORDER BY category, category_rank;
-- 12. 压缩与清理
CALL ducklake_compact('ecommerce.ducklake');
CALL ducklake_vacuum('ecommerce.ducklake', KEEP_VERSIONS 3);
-- 13. 最终验证
PRINT 'DuckLake 实战验证完成!';
SELECT category,
sum(amount) AS total,
count(*) AS orders
FROM 'ecommerce.ducklake'
GROUP BY category;
执行结果参考
┌──────────┬──────────────┬──────────┐
│ category │ order_count │ total │
│ varchar │ int64 │ decimal │
├──────────┼──────────────┼──────────┤
│ 电子 │ 3 │ 9798.00 │
│ 服装 │ 2 │ 1358.00 │
│ 食品 │ 3 │ 420.40 │
│ 图书 │ 3 │ 284.00 │
└──────────┴──────────────┴──────────┘
变现建议
DuckLake 作为一款新兴的轻量湖仓格式,在多个方向上具备商业化变现潜力:
1. DuckLake 数据管道服务
面向对象:中小企业和独立开发者
- 构建基于 DuckLake 的数据管道编排服务(类似轻量版 Airbyte)
- 提供 SaaS 平台:用户配置数据源,自动写入 DuckLake 格式
- 收费模式:按存储量 + API 调用次数
- 预估月费:$29–$199/月,取决于数据量
2. DuckLake 数据可视化工具
面向对象:业务分析师、非技术用户
- 构建 DuckLake 原生可视化 BI 工具(类似轻量版 Metabase)
- 利用 DuckDB 的嵌入式特性,浏览器端 + DuckDB WASM 直接读取 DuckLake
- 核心卖点:不需要后端服务,直接文件拖拽即可分析
- 变现模式:开源社区版 + 企业版(权限管理、团队协作)
3. 专用 DuckLake 转换服务
面向对象:有存量数据的企业
- 提供 JSON / CSV / 数据库 → DuckLake 格式的一键转换服务
- 企业版支持增量同步和 CDC(Change Data Capture)
- TAM(可寻址市场):所有使用 CSV 和 JSON 做数据分析的中小企业
4. DuckLake 数据市场
面向对象:数据提供方和消费者
- 建立一个基于 DuckLake 格式的数据交易市场
- 数据提供方上传 DuckLake 格式的数据集
- 消费者按量或按订阅付费下载
- 核心优势:DuckLake 格式本身支持时间旅行,可以提供历史版本回溯
5. 嵌入式 / IoT 方案
面向对象:边缘计算设备、IoT 网关
- 在树莓派 / Jetson Nano 等设备上运行 DuckDB + DuckLake
- 用于数据采集、本地聚合、增量上传
- 对比传统方案:不需要部署 SQLite 再转 Parquet 的两步走流程
- 可定价:按部署节点数收费($5/节点/月)
6. 培训与咨询
面向对象:DuckDB 和 Lakehouse 新手
- 制作《DuckLake 从入门到精通》付费课程(Udemy / 独立平台)
- 提供企业内训服务(DuckDB + DuckLake 最佳实践)
- 技术咨询:传统数仓迁移到 DuckLake 方案
- 定价参考:入门课程 $49.9,企业内训 $2000–$5000/天
总结
DuckLake v1.0 是 DuckDB 生态中一个极具战略意义的新成员。它打破了"湖仓一体 = 重型基础设施"的固有认知,证明了在单个嵌入式 OLAP 引擎上也能实现完整的湖仓能力。
它的核心价值定位非常清晰:
- 零依赖部署 —— 不需要 Spark、Hive Metastore、HDFS
- 开箱即用的 ACID —— 每个 DuckDB 实例都是一个完备的湖仓引擎
- 极低的 TCO —— 从硬件、运维到人力成本都大幅降低
- 无缝兼容 —— 底层 Parquet 确保数据不被锁定
对于数据从业者来说,DuckLake 最令人兴奋的一点是:它把湖仓能力从数据中心带到了笔记本、树莓派、甚至浏览器。当你能在笔记本电脑上运行一个完整的 ACID 湖仓时,数据工程的可能性边界在向外拓展。
DuckLake 不是来取代 Delta Lake 或 Iceberg 的——在大规模数据中心场景下,成熟的三巨头生态仍有不可替代的优势。但对于中小团队、初创公司、个人开发者,以及边缘计算场景而言,DuckLake 可能是迄今为止最优雅的选择。
欢迎在评论区分享你对 DuckLake 的看法和使用经验!