DuckDB绝对值得一试!

DuckDB绝对值得一试!

八月 01, 2025

在上一篇文章中,简要的介绍了一下 DuckDB。这里用一句话再描述一下,它是一个:

  • open-source
  • portable
  • analytical
  • in-process

  • 开源的
  • 便捷的
  • 分析型的
  • 进程内的

数据库。

近来,也算是有了一点点 DuckDB 的使用体验,所以打算跟各位分享一下,相信它一定能在你的日常工作中派上用场。有趣的地方我先告诉你,希望能引起你接下来的阅读兴趣。

  • DuckDB 非常轻量,它完全可以取代你手头上正在使用的 mysql、postgres 命令行
  • DuckDB 可以用来跨数据源查询,也就是一般所说的联邦查询
  • DuckDB 可以在你想对你手上的csv、excel 文件做一些查询、统计的时候很有用,尤其是在你不太喜欢 Python 世界的 pandas 之类的工具时

Fire in the hole!

安装

通过访问 DuckDB 官网的 安装文档,可以非常直观的获取到它的各种安装方式,只需要选择符合你的情况的选项,照着做便万事大吉:

接下来,我将展示以上有趣的地方,其他稀松平常的 CRUD SQL 就不展示了。

我有点意思:跨源查询

为了使用跨源查询,我们首先需要在 DuckDB 之内连接上多个数据源,比如,我这里用到了 2 个源:

  • localhost MySQL
  • 一个原本由 SQLite 维护的 db

为了方便我们在日常工作中更好的使用这些源,而不是每次都去连接这些源,DuckDB 提供了通过 ~/.duckdbrc 来加载初始化命令的功能:

比如,我的配置文件是这样的:

1
2
3
4
$ cat ~/.duckdbrc
.cd /Users/viclau/project/DuckDBFiles

.read open_databases.sql

意思是:

  • 通过 .cd 切换命令行的工作目录到 /Users/viclau/project/DuckDBFiles . 原因是,我把数据文件方在这个目录下的,希望在这里统一对 DuckDB 使用到的文件进行管理。
  • 通过 .read 加载并执行 当前工作目录下的 open_databases.sql 文件. 没错,你可以猜到,我将在这个文件内建立起于不同的数据源的连接

接下来,我们看一下这个文件:

1
2
3
4
5
$ cat open_databases.sql 
ATTACH '' AS mysql_localhost (TYPE mysql, SECRET mysql_localhost);
ATTACH 'socialbinding.db' (TYPE sqlite);

# .open xxx.duckdb

意思是:

  • 第一个 ATTACH: 建立到我本地 mysql 的连接,通过已经配置好的 mysql_localhost 这个 SECRET (访问 Configuration / Secrets Manager 习得此技能)。当然,不通过 secret,也是可以建立连接的,只是作为例子,我更倾向于告诉大家更好更安全的方式。
  • 第二个 ATTACH: 从当前工作目录下加载文件 socialbinding.db, 然后建立 sqlite 类型的连接
  • 注释掉的 open 命令: 这里是说如果想在启动时加载 DuckDB 本身的单文件db, 可以这样做

好了,我们看看效果:

1
2
3
4
5
6
$ duckdb
-- Loading resources from /Users/viclau/.duckdbrc
DuckDB v1.3.2 (Ossivalis) 0b83e5d2f6
Enter ".help" for usage hints.
Connected to a transient in-memory database.
Use ".open FILENAME" to reopen on a persistent database.

从提示可以看到,它成功加载了 ~/.duckdbrc,接着,再看看目前建立好的多个数据源:

1
2
3
D .databases
mysql_localhost:
socialbinding: socialbinding.db

最后,针对这个示例,简单演示下跨源查询。

第一个数据源:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
D use mysql_localhost.test;
D show tables;
┌────────────────────────────────────────────┐
│ name │
│ varchar │
├────────────────────────────────────────────┤
│ xyz │
| ... |
├────────────────────────────────────────────┤
│ 25 rows │
└────────────────────────────────────────────┘

D desc xyz;
┌─────────────┬─────────────┬─────────┬─────────┬─────────┬─────────┐
│ column_name │ column_type │ null │ key │ default │ extra │
│ varchar │ varchar │ varchar │ varchar │ varchar │ varchar │
├─────────────┼─────────────┼─────────┼─────────┼─────────┼─────────┤
│ id │ INTEGER │ NO │ NULL │ NULL │ NULL │
│ key │ BIGINT │ YES │ NULL │ NULL │ NULL │
└─────────────┴─────────────┴─────────┴─────────┴─────────┴─────────┘

D from mysql_localhost.test.xyz limit 10;
┌───────┬───────┐
│ id │ key │
│ int32 │ int64 │
├───────┼───────┤
│ 1 │ -1 │
└───────┴───────┘

第二个数据源:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
D use socialbinding;
D show tables;
┌─────────────────┐
│ name │
│ varchar │
├─────────────────┤
│ account │
│ chat_message │
│ sqlite_sequence │
└─────────────────┘
D desc chat_message;
┌───────────────────┬─────────────┬─────────┬─────────┬─────────┬─────────┐
│ column_name │ column_type │ null │ key │ default │ extra │
│ varchar │ varchar │ varchar │ varchar │ varchar │ varchar │
├───────────────────┼─────────────┼─────────┼─────────┼─────────┼─────────┤
│ id │ VARCHAR │ NO │ PRI │ NULL │ NULL │
│ role │ VARCHAR │ NO │ NULL │ NULL │ NULL │
│ content │ VARCHAR │ NO │ NULL │ NULL │ NULL │
│ conversation_id │ VARCHAR │ NO │ NULL │ NULL │ NULL │
│ parent_message_id │ VARCHAR │ YES │ NULL │ NULL │ NULL │
│ created_at │ BIGINT │ NO │ NULL │ NULL │ NULL │
│ updated_at │ BIGINT │ NO │ NULL │ NULL │ NULL │
│ is_deleted │ BIGINT │ YES │ NULL │ 0 │ NULL │
└───────────────────┴─────────────┴─────────┴─────────┴─────────┴─────────┘

D from socialbinding.chat_message select * exclude(content) limit 10;
┌───────────────────────────────────────────────┬───────────┬──────────────────────────────────────┬───────────────────────────────────────────────┬────────────┬────────────┬────────────┐
│ id │ role │ conversation_id │ parent_message_id │ created_at │ updated_at │ is_deleted │
│ varchar │ varchar │ varchar │ varchar │ int64 │ int64 │ int64 │
├───────────────────────────────────────────────┼───────────┼──────────────────────────────────────┼───────────────────────────────────────────────┼────────────┼────────────┼────────────┤
│ 01975798-d095-7ea9-92f4-67328389a92f │ user │ 1d3cedaa-c0b6-490a-9907-991aac9c12c4 │ NULL │ 1749521322 │ 1749521322 │ 0 │
│ chatcmpl-eb914dad-864e-9c53-9d94-3efed0eb349d │ assistant │ 1d3cedaa-c0b6-490a-9907-991aac9c12c4 │ 01975798-d095-7ea9-92f4-67328389a92f │ 1749521322 │ 1749521322 │ 0 │
│ 0197579c-89e2-7dbb-a083-44ce11ca5057 │ user │ 1d3cedaa-c0b6-490a-9907-991aac9c12c4 │ chatcmpl-eb914dad-864e-9c53-9d94-3efed0eb349d │ 1749521566 │ 1749521566 │ 1 │
│ chatcmpl-79584b9c-0bad-9a3d-898b-c871809e62ea │ assistant │ 1d3cedaa-c0b6-490a-9907-991aac9c12c4 │ 0197579c-89e2-7dbb-a083-44ce11ca5057 │ 1749521566 │ 1749521566 │ 1 │
│ 019758ac-8f87-7056-b2e3-caefa4aebe64 │ user │ 1d3cedaa-c0b6-490a-9907-991aac9c12c4 │ chatcmpl-eb914dad-864e-9c53-9d94-3efed0eb349d │ 1749539393 │ 1749539393 │ 1 │
│ chatcmpl-5e749300-9223-9930-9af2-ba409493efc2 │ assistant │ 1d3cedaa-c0b6-490a-9907-991aac9c12c4 │ 019758ac-8f87-7056-b2e3-caefa4aebe64 │ 1749539393 │ 1749539393 │ 1 │
│ 019758bc-e4f0-7f30-965f-e70fccb4d17d │ user │ 1d3cedaa-c0b6-490a-9907-991aac9c12c4 │ chatcmpl-eb914dad-864e-9c53-9d94-3efed0eb349d │ 1749540463 │ 1749540463 │ 0 │
│ chatcmpl-7ffab7ee-4989-9be3-a5d4-2d1a14e23fb9 │ assistant │ 1d3cedaa-c0b6-490a-9907-991aac9c12c4 │ 019758bc-e4f0-7f30-965f-e70fccb4d17d │ 1749540463 │ 1749540463 │ 0 │
│ 01978208-d311-7ca1-9b2a-7e577cd95e75 │ user │ 98f66982-fadc-47f5-abbe-7555a8d5344e │ NULL │ 1750233305 │ 1750233305 │ 0 │
│ chatcmpl-78c3a42f-deef-9193-ad0c-7a2b455a657f │ assistant │ 98f66982-fadc-47f5-abbe-7555a8d5344e │ 01978208-d311-7ca1-9b2a-7e577cd95e75 │ 1750233305 │ 1750233305 │ 0 │
├───────────────────────────────────────────────┴───────────┴──────────────────────────────────────┴───────────────────────────────────────────────┴────────────┴────────────┴────────────┤
│ 10 rows 7 columns │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

我在查询 chat_message 表示,排除掉了 content 这一列,因为它的内容太长了影响版面。由于这两张表其实没有任何在数据上的关联,所以这里以 full join 进行跨源查询演示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
D with msg as (from socialbinding.chat_message select * exclude ("content") limit 10)
select msg.*, xyz.* from msg full join mysql_localhost.test.xyz xyz on 1 = 1;
┌───────────────────────────────────────────────┬───────────┬──────────────────────────────────────┬───────────────────────────────────────────────┬────────────┬────────────┬────────────┬───────┬───────┐
│ id │ role │ conversation_id │ parent_message_id │ created_at │ updated_at │ is_deleted │ id │ key │
│ varchar │ varchar │ varchar │ varchar │ int64 │ int64 │ int64 │ int32 │ int64 │
├───────────────────────────────────────────────┼───────────┼──────────────────────────────────────┼───────────────────────────────────────────────┼────────────┼────────────┼────────────┼───────┼───────┤
│ 01975798-d095-7ea9-92f4-67328389a92f │ user │ 1d3cedaa-c0b6-490a-9907-991aac9c12c4 │ NULL │ 1749521322 │ 1749521322 │ 0 │ 1 │ -1 │
│ chatcmpl-eb914dad-864e-9c53-9d94-3efed0eb349d │ assistant │ 1d3cedaa-c0b6-490a-9907-991aac9c12c4 │ 01975798-d095-7ea9-92f4-67328389a92f │ 1749521322 │ 1749521322 │ 0 │ 1 │ -1 │
│ 0197579c-89e2-7dbb-a083-44ce11ca5057 │ user │ 1d3cedaa-c0b6-490a-9907-991aac9c12c4 │ chatcmpl-eb914dad-864e-9c53-9d94-3efed0eb349d │ 1749521566 │ 1749521566 │ 1 │ 1 │ -1 │
│ chatcmpl-79584b9c-0bad-9a3d-898b-c871809e62ea │ assistant │ 1d3cedaa-c0b6-490a-9907-991aac9c12c4 │ 0197579c-89e2-7dbb-a083-44ce11ca5057 │ 1749521566 │ 1749521566 │ 1 │ 1 │ -1 │
│ 019758ac-8f87-7056-b2e3-caefa4aebe64 │ user │ 1d3cedaa-c0b6-490a-9907-991aac9c12c4 │ chatcmpl-eb914dad-864e-9c53-9d94-3efed0eb349d │ 1749539393 │ 1749539393 │ 1 │ 1 │ -1 │
│ chatcmpl-5e749300-9223-9930-9af2-ba409493efc2 │ assistant │ 1d3cedaa-c0b6-490a-9907-991aac9c12c4 │ 019758ac-8f87-7056-b2e3-caefa4aebe64 │ 1749539393 │ 1749539393 │ 1 │ 1 │ -1 │
│ 019758bc-e4f0-7f30-965f-e70fccb4d17d │ user │ 1d3cedaa-c0b6-490a-9907-991aac9c12c4 │ chatcmpl-eb914dad-864e-9c53-9d94-3efed0eb349d │ 1749540463 │ 1749540463 │ 0 │ 1 │ -1 │
│ chatcmpl-7ffab7ee-4989-9be3-a5d4-2d1a14e23fb9 │ assistant │ 1d3cedaa-c0b6-490a-9907-991aac9c12c4 │ 019758bc-e4f0-7f30-965f-e70fccb4d17d │ 1749540463 │ 1749540463 │ 0 │ 1 │ -1 │
│ 01978208-d311-7ca1-9b2a-7e577cd95e75 │ user │ 98f66982-fadc-47f5-abbe-7555a8d5344e │ NULL │ 1750233305 │ 1750233305 │ 0 │ 1 │ -1 │
│ chatcmpl-78c3a42f-deef-9193-ad0c-7a2b455a657f │ assistant │ 98f66982-fadc-47f5-abbe-7555a8d5344e │ 01978208-d311-7ca1-9b2a-7e577cd95e75 │ 1750233305 │ 1750233305 │ 0 │ 1 │ -1 │
├───────────────────────────────────────────────┴───────────┴──────────────────────────────────────┴───────────────────────────────────────────────┴────────────┴────────────┴────────────┴───────┴───────┤
│ 10 rows 9 columns │
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘

好了,可以看到不同源的表已经 join 到了一起。

我有点意思: 替代 mysql, postgresql, sqlite 命令行

通过上面的演示,我们看到了对多种数据源的支持,所以如果你愿意,完全可以在日常工作中使用 duckdb 命令行,通过自己的一点点实践才有更多体会。

当然,除了上面演示的两种源,其他还有很多源是被支持的。不同的源,在 DuckDB 中是以 extension 机制来做到的,以下是它目前支持的所有 core extension:

我有点意思: 快速访问 csv

我这里有个简单的csv, 当然, 为了便于加载, 我依然把它放在 ~/.duckdbrc 里面提到的那个目录下:

1
2
3
4
5
$ pwd
/Users/viclau/project/DuckDBFiles

$ ll *.csv
-rw-r--r-- 1 viclau staff 548 8 1 22:14 prices.csv

以及它的原始内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
cat prices.csv 
value,valid_from,valid_until
8.60,2020-11-01,2023-01-01
8.60,2023-01-01,2024-02-01
8.64,2020-10-01,2020-11-01
8.77,2020-09-01,2020-10-01
8.90,2020-08-01,2020-09-01
9.03,2020-07-01,2020-08-01
9.17,2020-06-01,2020-07-01
9.30,2020-05-01,2020-06-01
9.44,2020-04-01,2020-05-01
9.58,2020-03-01,2020-04-01
9.72,2020-02-01,2020-03-01
9.87,2020-01-01,2020-02-01
9.97,2019-12-01,2020-01-01
10.08,2019-11-01,2019-12-01
10.18,2019-10-01,2019-11-01
10.33,2019-09-01,2019-10-01
10.48,2019-08-01,2019-09-01
10.64,2019-07-01,2019-08-01
10.79,2019-06-01,2019-07-01

丝滑的访问如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
D from 'prices.csv';
┌────────┬────────────┬─────────────┐
│ value │ valid_from │ valid_until │
│ double │ date │ date │
├────────┼────────────┼─────────────┤
│ 8.6 │ 2020-11-01 │ 2023-01-01 │
│ 8.6 │ 2023-01-01 │ 2024-02-01 │
│ 8.64 │ 2020-10-01 │ 2020-11-01 │
│ 8.77 │ 2020-09-01 │ 2020-10-01 │
│ 8.9 │ 2020-08-01 │ 2020-09-01 │
│ 9.03 │ 2020-07-01 │ 2020-08-01 │
│ 9.17 │ 2020-06-01 │ 2020-07-01 │
│ 9.3 │ 2020-05-01 │ 2020-06-01 │
│ 9.44 │ 2020-04-01 │ 2020-05-01 │
│ 9.58 │ 2020-03-01 │ 2020-04-01 │
│ 9.72 │ 2020-02-01 │ 2020-03-01 │
│ 9.87 │ 2020-01-01 │ 2020-02-01 │
│ 9.97 │ 2019-12-01 │ 2020-01-01 │
│ 10.08 │ 2019-11-01 │ 2019-12-01 │
│ 10.18 │ 2019-10-01 │ 2019-11-01 │
│ 10.33 │ 2019-09-01 │ 2019-10-01 │
│ 10.48 │ 2019-08-01 │ 2019-09-01 │
│ 10.64 │ 2019-07-01 │ 2019-08-01 │
│ 10.79 │ 2019-06-01 │ 2019-07-01 │
├────────┴────────────┴─────────────┤
│ 19 rows 3 columns │
└───────────────────────────────────┘

在 DuckDB 中,快速探查它识别到的每一列的数据类型也很简单:

1
2
3
4
5
6
7
8
9
D desc from 'prices.csv';
┌─────────────┬─────────────┬─────────┬─────────┬─────────┬─────────┐
│ column_name │ column_type │ null │ key │ default │ extra │
│ varchar │ varchar │ varchar │ varchar │ varchar │ varchar │
├─────────────┼─────────────┼─────────┼─────────┼─────────┼─────────┤
│ value │ DOUBLE │ YES │ NULL │ NULL │ NULL │
│ valid_from │ DATE │ YES │ NULL │ NULL │ NULL │
│ valid_until │ DATE │ YES │ NULL │ NULL │ NULL │
└─────────────┴─────────────┴─────────┴─────────┴─────────┴─────────┘

这要归功于 DuckDB 的 describe on any relation 能力,所谓的 relation 也就是任何能作为 from 的东西,不轮来自于物理表,还是中间表。相比之下,大部分数据库只能 desc 物理表。

总结

我在这篇文章中为你展示了 DuckDB 跨源查询 和 快速访问 csv 的能力,这只是它的冰山一角。希望这能够给你带来一些不同的体验,不忙的时候,记得试一试呀!