author: DuckDB Team
source: https://duckdb.org/2024/09/09/announcing-duckdb-110.html

DuckDB 团队很高兴宣布,今天我们发布了 DuckDB 1.1.0 版本,代号为“Eatoni”!
要安装新版本,请访问 安装指南[1]。
有关详细的更新内容,请参见 发布页面[2]。
一些软件包(如 R、Java)由于发布流程中的审核要求,可能需要多几天才能发布。
今天,我们自豪地推出了 DuckDB 1.1.0,这是继三个月前发布 1.0.0 后的首个版本。此次版本的代号为 “Eatoni”,名字来源于 Eaton’s pintail (Anas eatoni)[3],这是一种仅生活在南印度洋两个偏远岛屿群上的潜水鸭。
1.1.0 有哪些新功能?
这次更新包含了大量的改进,无法一一详述,但我们挑选了其中一些特别有趣的功能进行介绍!
下面是这些新功能的摘要和示例。
• SQL 语法重要变化
• 社区扩展
• 更友好的 SQL
• 自动解压列
•
query
和query_table
函数• 性能提升
• 通过 Join 的动态过滤推送
• CTE 自动物化
• 并行流式查询
• 按列名称并行合并
• ART 树重构(外键加载加速)
• 窗口函数优化
• 空间数据支持
• GeoParquet 支持
• R-Tree 数据结构
• 最后总结
SQL 语法重要变化
IEEE-754 分母为零的处理[4]:根据 IEEE-754 浮点数标准[5],在分母为零的情况下应返回 inf
(无穷大)。之前,DuckDB 在这种情况下返回 NULL
。从本版本开始,DuckDB 将改为返回 inf
。
SELECT 1 / 0 AS division_by_zero;
复制
┌──────────────────┐
│ division_by_zero │
│ double │
├──────────────────┤
│ inf │
└──────────────────┘复制
如果需要恢复之前的行为,可以将 ieee_floating_point_ops
设置为 false
:
SET ieee_floating_point_ops = false;
SELECT 1 / 0 AS division_by_zero;复制
┌──────────────────┐
│ division_by_zero │
│ double │
├──────────────────┤
│ NULL │
└──────────────────┘复制
标量子查询返回多个值时的错误处理[6]:标量子查询指的是返回一个值的子查询。在以前,如果子查询返回多行,DuckDB 会随机选择一行返回,这样容易导致混淆。从本次更新开始,当子查询返回多行时,DuckDB 会返回错误提示,与 PostgreSQL 的处理方式保持一致。想要返回所有结果,可以使用 ARRAY
包装子查询。
SELECT (SELECT unnest(range(10)));
复制
输入错误:作为表达式使用的子查询返回了多行,标量子查询只能返回单行数据。
复制
要返回所有结果,可以这样修改:
SELECT ARRAY(SELECT unnest(range(10))) AS subquery_result;
复制
┌────────────────────────────────┐
│ subquery_result │
│ int64[] │
├────────────────────────────────┤
│ [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] │
└────────────────────────────────┘复制
如果希望恢复之前的行为,可以将 scalar_subquery_error_on_multiple_rows
设置为 false
。
SET scalar_subquery_error_on_multiple_rows = false;
SELECT (SELECT unnest(range(10))) AS result;复制
┌────────┐
│ result │
│ int64 │
├────────┤
│ 0 │
└────────┘复制
社区扩展
最近,我们推出了 社区扩展 功能。它允许任何开发者为 DuckDB 构建扩展,我们会负责编译和发布。从那时起,社区扩展列表[7] 一直在增长。
在本次更新中,我们致力于让社区扩展的构建和发布更加简单。此次更新包含了一种使用 C API[8] 注册扩展的新方法,并增加了对 C API 的多项扩展,允许开发者定义 标量函数[9]、聚合函数[10] 和 自定义数据类型[11]。这些改进不仅让扩展更小、更易于维护,还能跨多个版本兼容。此外,这些改进还为未来在其他编程语言中构建扩展提供了更多可能性。
性能提升
动态过滤推送优化
本次更新为表连接操作带来了极大的优化:DuckDB 现在可以在执行连接操作时自动为较大的表创建过滤条件[12]。假设我们有两个表 A
和 B
,其中 A
表有 100 行数据,而 B
表有 100 万行。我们使用它们的共同列 i
进行连接。如果 i
上有过滤条件,DuckDB 之前已经可以将过滤条件提前应用到数据扫描阶段,从而显著减少查询成本。但现在,我们不仅要在 i
列上过滤,还需要基于 A
表中的另一列 j
进行过滤:
CREATE TABLE A AS SELECT range i, range j FROM range(100);
CREATE TABLE B AS SELECT a.range i FROM range(100) a, range(10_000) b;
SELECT count(*) FROM A JOIN B USING (i) WHERE j > 90;复制
在这种情况下,DuckDB 会通过在较小的表 A
上构建哈希表,并使用较大表 B
的内容来探测哈希表中的数据。在哈希表构建的过程中,DuckDB 会观察到 i
列的值,并生成一个基于这些值的最小-最大范围过滤器。这个过滤器会自动应用到 B
表的 i
列上,从而提前移除大量无关数据。在这个例子中,大表 B
中的 90% 数据会被提前剔除,从而使查询速度提升了 10 倍左右。你可以通过运行 EXPLAIN ANALYZE
查看这一优化的详细信息。
自动 CTE 物化
通用表表达式 (CTE) 是一种常用的查询技术,能让复杂的查询变得更加简洁。例如,下面的查询使用了一个简单的 CTE:
WITH my_cte AS (SELECT range AS i FROM range(10))
SELECT i FROM my_cte WHERE i > 5;复制
有时候,CTE 在同一个查询中会被多次引用。以前,DuckDB 会在每次引用 CTE 时重复计算它的结果,这在某些情况下可能导致性能问题。如果计算 CTE 的过程非常耗时,最好是缓存其结果,而不是每次都重新计算。然而,在不同的引用中可能会应用不同的过滤条件,这会影响计算成本,因此手动决定是否缓存也并不总是合适。
本次更新新增了自动决定是否物化 CTE 的功能。DuckDB 现在会根据一些规则自动决定[13]是否缓存 CTE 结果。例如,当一个 CTE 包含聚合操作且被多次引用时,DuckDB 会自动缓存其结果,避免重复计算。未来我们还会进一步改进这一决策逻辑。
并行流式查询
DuckDB 提供了两种获取查询结果的方式:物化和流式。物化方式会一次性获取所有数据,而流式方式则允许逐步获取数据。在处理大数据集时,流式查询非常重要,因为它不需要将整个结果集加载到内存中。然而,在之前的版本中,流式查询只能由单个线程处理,限制了查询速度。
为了提升性能,本次更新新增了并行流式查询[14]支持。DuckDB 现在可以使用所有可用的 CPU 线程来处理查询结果,显著提高了查询性能。数据将被分批加载到一个有限大小的缓冲区中,线程会不断填充缓冲区,直到数据全部处理完毕。用户可以通过 streaming_buffer_size
参数来调整缓冲区的大小。
以下是一个使用 ontime.parquet
数据集的性能测试,展示了 Python 接口中流式查询性能的提升:
import duckdb
duckdb.sql("SELECT * FROM 'ontime.parquet' WHERE flightnum = 6805;").fetchone()复制
v1.0 | v1.1 |
1.17 s | 0.12 s |
通过并行流式处理,查询速度提升了约 10 倍。
按名称并行合并
union_by_name
参数允许将具有相同列但列顺序不同的文件进行合并处理。本次更新为 union_by_name
提供了并行处理支持[15],当处理多个文件时,性能会有显著提升。
外键加载速度提升
本次更新大幅提升了外键插入和删除的性能。通常情况下,我们会直接将行标识符嵌入到索引树结构中,但对于包含大量重复值的外键索引,这种方式并不适用。因此,我们为每个外键创建了一个嵌套的索引结构,从而提升了性能。以往,插入 100 万行引用外键的数据大约需要 10 秒,而现在只需要 0.2 秒,性能提升了 50 倍!
窗口函数优化
窗口函数在 DuckDB 中非常常用,因此我们一直在努力提升其性能。
DISTINCT
[16] 和 FILTER
[17] 修饰符现在可以在流式模式下执行。这意味着窗口函数在处理数据时不再需要完全收集和缓冲所有输入数据,极大地提升了处理大数据集的效率。例如,以下查询现在会使用流式窗口操作符:
SELECT
sum(DISTINCT i)
FILTER (i % 3 = 0)
OVER (ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
FROM range(10) tbl(i);复制
我们还为正向 lead
偏移实现了流式处理。
此外,现在我们可以通过窗口函数的分区列将过滤器直接推送到数据扫描阶段。例如,以下查询:
CREATE TABLE tbl2 AS SELECT range i FROM range(10);
SELECT i
FROM (SELECT i, SUM(i) OVER (PARTITION BY i) FROM tbl)
WHERE i > 5;复制
在以前,i
列上的过滤器无法被推送到 tbl
表的扫描中,但现在 DuckDB 可以识别到这种优化机会,并自动将过滤条件应用到扫描阶段。这可以通过 EXPLAIN
查看其优化细节。
┌─────────────────────────────┐
│┌───────────────────────────┐│
││ Physical Plan ││
│└───────────────────────────┘│
└─────────────────────────────┘
…
┌─────────────┴─────────────┐
│ WINDOW │
│ ──────────────────── │
│ Projections: │
│ sum(i) OVER (PARTITION BY │
│ i) │
└─────────────┬─────────────┘
┌─────────────┴─────────────┐
│ SEQ_SCAN │
│ ──────────────────── │
│ tbl │
│ │
│ Projections: i │
│ │
│ Filters: │
│ i>5 AND i IS NOT NULL │
│ │
│ ~2 Rows │
└───────────────────────────┘复制
对于非流式的窗口操作符,DuckDB 现在也支持并行处理,从而大幅降低了内存使用。
更多信息请参见 Richard 在 DuckCon #5 上的演讲[18]。
空间功能
GeoParquet
GeoParquet 是一种扩展的 Parquet 文件格式,它标准化了如何在 Parquet 文件中存储地理信息,比如地图中的多边形和点数据。借助这种格式,我们可以更加高效地存储地理数据。当你安装并加载 spatial
扩展[19] 后,DuckDB 可以通过其内置的 Parquet 读取器,自动将 GeoParquet 文件中的地理数据列转换为 GEOMETRY
类型。例如:
INSTALL spatial;
LOAD spatial;
FROM 'https://blobs.duckdb.org/data/geoparquet-example.parquet'
SELECT GEOMETRY g
LIMIT 10;复制
┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
│ g │
│ geometry │
├────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
│ MULTIPOLYGON (((180 -16.067132663642447, 180 -16.555216566639196, 179.36414266196414 -16.801354076946883, 17… │
│ POLYGON ((33.90371119710453 -0.95, 34.07261999999997 -1.059819999999945, 37.69868999999994 -3.09698999999994… │
│ POLYGON ((-8.665589565454809 27.656425889592356, -8.665124477564191 27.589479071558227, -8.684399786809053 2… │
│ MULTIPOLYGON (((-122.84000000000003 49.000000000000114, -122.97421000000001 49.00253777777778, -124.91024 49… │
│ MULTIPOLYGON (((-122.84000000000003 49.000000000000114, -120 49.000000000000114, -117.03121 49, -116.04818 4… │
└────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘复制
R-Tree 索引
本次更新还为 DuckDB 的空间功能加入了 “R-Tree” 索引支持。R-Tree 是一种树形数据结构,用来快速查找某个地理区域内的对象。它通过把每个地理对象的边界框存储在树形结构的节点中,快速判断哪些几何体与某个特定区域相交。这种索引非常适合在大量地理数据中查找某个感兴趣的区域。
R-Tree 空间索引的支持是长期以来的需求,本次更新终于实现了这一功能,为未来的更多应用场景打开了大门。目前,R-Tree 索引主要用于加速某些特定查询,特别是那些带有空间谓词的查询,比如判断某个地理区域内的点或多边形是否与另一几何区域相交。对于包含大量地理数据的表,R-Tree 索引可以避免全表扫描,仅查询与特定区域相关的数据。例如,下面的查询展示了如何通过 RTREE_INDEX_SCAN
操作符加速查询:
INSTALL spatial;
LOAD spatial;
-- 创建一个包含 10_000_000 个随机点的表
CREATE TABLE t1 AS SELECT point::GEOMETRY AS geom
FROM st_generatepoints(
{min_x: 0, min_y: 0, max_x: 10_000, max_y: 10_000}::BOX_2D,
10_000_000,
1337
);
-- 为表创建 R-Tree 索引
CREATE INDEX my_idx ON t1 USING RTREE (geom);
-- 执行带有“空间谓词”的查询
-- 这里 ST_MakeEnvelope 用于创建一个矩形区域
SELECT count(*)
FROM t1
WHERE ST_Within(geom, ST_MakeEnvelope(450, 450, 650, 650));复制
3986
复制
R-Tree 索引与 DuckDB 的内置 ART 索引功能相似。它们可以管理数据的缓冲、支持持久化,并且从磁盘懒加载。此外,还支持对基表的插入、更新和删除操作,但目前无法用于约束检查。
总结
以上只是一些亮点——本次发布还带来了更多新功能和改进。你可以在 GitHub[20] 查看完整的发布说明。
我们要再次感谢社区用户对 DuckDB 的支持、贡献与反馈,你们的参与对我们至关重要!
引用链接
[1]
安装指南: https://duckdb.org/docs/installation/index.html[2]
发布页面: https://github.com/duckdb/duckdb/releases/tag/v1.1.0[3]
Eaton’s pintail (Anas eatoni): https://en.wikipedia.org/wiki/Eaton%27s_pintail[4]
IEEE-754 分母为零的处理: https://github.com/duckdb/duckdb/pull/13493[5]
IEEE-754 浮点数标准: https://en.wikipedia.org/wiki/IEEE_754[6]
标量子查询返回多个值时的错误处理: https://github.com/duckdb/duckdb/pull/13514[7]
社区扩展列表: https://community-extensions.duckdb.org/list_of_extensions.html[8]
C API: https://github.com/duckdb/duckdb/pull/12682[9]
标量函数: https://github.com/duckdb/duckdb/pull/11786[10]
聚合函数: https://github.com/duckdb/duckdb/pull/13229[11]
自定义数据类型: https://github.com/duckdb/duckdb/pull/13499[12]
自动为较大的表创建过滤条件: https://github.com/duckdb/duckdb/pull/12908[13]
自动决定: https://github.com/duckdb/duckdb/pull/12290[14]
并行流式查询: https://github.com/duckdb/duckdb/pull/11494[15]
为 union_by_name
提供了并行处理支持: https://github.com/duckdb/duckdb/pull/12957[16]
DISTINCT
: https://github.com/duckdb/duckdb/pull/12311[17]
FILTER
: https://github.com/duckdb/duckdb/pull/12250[18]
Richard 在 DuckCon #5 上的演讲: https://www.youtube.com/watch?v=QubE0u8Kq7Y&list=PLzIMXBizEZjhbacz4PWGuCUSxizmLei8Y&index=8[19]
spatial
扩展: https://duckdb.org/docs/extensions/spatial.md[20]
GitHub: https://github.com/duckdb/duckdb/releases/tag/v1.1.0