一个不含数据的 DuckDB 数据库
DuckDB 团队
2026-10-07 | 6 分钟
摘要:DuckDB 数据库文件不必包含任何数据。它可以只存储指向对象存储上 Parquet 文件的视图定义,因此几百千字节的文件就能像一个覆盖整个数据湖的共享目录一样运作。本文展示如何构建这样的文件、托管它,并以只读方式附加。
DuckDB 数据库文件不必包含任何数据。它可以只存储指向存储在其他地方的文件(例如对象存储上的 Parquet)的视图定义。无论它所描述的数据有多大,该文件都保持几百千字节,任何附加它的人都会看到一组已命名的关系,随时可以查询。
Nikolas Goebel 在《DuckDB 不需要数据》 中描述了这个想法,该文认为现代数据库已经将数据存储位置与读取方式分离,以至于大多数数据库不再需要保存自己的任何数据。从这个角度来看,DuckDB 成为数据云的浏览器,你通过 URL 而不是自己磁盘上的副本来访问数据集。
在这篇博客文章中,我们将这个想法变成一个小的、可共享的目录。
想法
DuckDB 表将其行存储在数据库文件内部。视图只存储查询的文本。当你读取视图时,DuckDB 运行该查询并返回当前结果。如果查询读取远程 Parquet 文件,数据就存在于对象存储中,而视图只保存如何读取它的指令。
完全由这类视图构成的数据库文件不包含自己的任何行。它是一个目录:一组已命名的、随时可查询的关系,建立在保持在其原始位置和格式的数据之上。你可以将该目录作为数据湖上的精选语义层发布,其中视图定义编码了消费者应使用的连接、过滤器和列名,而无需复制或移动底层数据。
构建视图目录
这些示例使用 DuckDB 的公开列车服务数据集,该数据集为荷兰铁路列车停靠的每一站记录一行。数据来自 Rijden de Treinen("列车在运行吗?")应用程序发布的开放数据集,你可以直接从其 URL 读取。
首先,安装 httpfs 扩展,以便 DuckDB 可以通过网络读取。这只需运行一次。
sql
INSTALL httpfs;
现在创建一个持久数据库文件,并在远程数据上定义一个视图。使用完全限定的位置、远程 URL 或绝对路径,以便视图无论客户端的工作目录如何都能解析。
sql
ATTACH 'rail_catalog.duckdb' AS rail;
USE rail;
CREATE VIEW services AS
SELECT *
FROM 'https://blobs.duckdb.org/train_services.parquet';
该目录现在公开一个 services 关系,每当查询它时都会从远程文件读取。
sql
SELECT departure_time, station_name, type
FROM rail.services
LIMIT 3;
| departure_time | station_name | type |
|---|---|---|
| 2023-05-15 00:00:00 | Rotterdam Centraal | Intercity |
| 2023-05-15 00:13:00 | Delft | Intercity |
| 2023-05-15 00:29:00 | Den Haag HS | Intercity |
由于文件只存储视图定义,无论所引用数据集增长到多大,它都保持很小。train_services.parquet 文件有 380,959 行,但读取它的目录只有几百千字节。
托管目录
将 rail_catalog.duckdb 上传到 DuckDB 可以通过网络读取的任何位置,例如 s3:// 存储桶或 https:// 端点。已经存放 Parquet 文件的同一个存储桶是放置它的便利位置。
共享目录然后意味着共享一个 URL,而不是一长串单独的文件路径。接收者不需要知道每个视图背后有多少文件或它们存储在哪里,只需要目录的地址。
以只读方式附加目录
消费者以只读方式附加目录,并像查询本地表一样查询视图。每个查询都从引用的文件读取当前数据,因此结果反映对象存储中那一刻的内容。
sql
ATTACH 'https://example.com/rail_catalog.duckdb' AS rail (READ_ONLY);
SELECT station_name, count(*) AS calls
FROM rail.services
GROUP BY station_name
ORDER BY calls DESC
LIMIT 3;
| station_name | calls |
|---|---|
| Utrecht Centraal | 7663 |
| Amsterdam Centraal | 7591 |
| Zwolle | 5013 |
消费者需要 httpfs 扩展,对于 s3:// 源,还需要授予对底层数据访问权限的凭据。DuckDB 只读取查询所需的列和行组,因此对大型远程数据集的过滤或聚合查询不会下载整个数据集。
在数据布局变化中存活
由于查询读取的是视图而不是直接读取文件,发布者可以更改文件而不更改查询。消费者在前后运行相同的查询。
假设单个 Parquet 文件变得太大,发布者将其拆分为每年一个文件。将视图更新为读取文件目录是目录中的一行更改,消费者什么也不会注意到。
sql
CREATE OR REPLACE VIEW services AS
SELECT *
FROM read_parquet('s3://my-bucket/rail/services/year=*/*.parquet');
当文件更改时,视图还可以保持模式稳定。如果后来一批文件将 station_name 重命名为 station,视图可以将新列映射回消费者已经使用的名称。
sql
CREATE OR REPLACE VIEW services AS
SELECT
* EXCLUDE (station),
station AS station_name
FROM read_parquet('s3://my-bucket/rail/services/*.parquet');
如果你重新分区文件或将它们移动到新存储桶,你只需更新目录。任何查询它的人都可以继续运行与之前相同的查询。
何时使用它
当你想要共享数据集而不向接收者发送一长串单独的文件路径,并且数据已经采用 DuckDB 读取良好的格式(例如 Parquet)时,请使用此模式。读者获得一组已命名的关系来查询,发布者可以更改文件布局而不破坏这些查询。
注意以下限制:
- 消费者必须能够访问目录文件以及其视图引用的每个数据源。
- 通过 HTTPS 和 S3 API 的连接是只读的,因此目录无法就地修改。要更改视图,请编辑本地副本并再次上传。
- 查询性能取决于网络和所引用文件的布局。查询远程文件的提示适用。
结论
一个只包含视图的 DuckDB 文件是一个小目录,你可以作为单个 URL 共享。数据保持在其原始位置,发布者可以更改其存储方式,而读者获得一组稳定的关系来查询。
完整参考,请参阅《共享一个只存储视图的 DuckDB 数据库》指南。