
引言
在数据分析的日常工作中,我们经常需要从多个数据源获取数据——PostgreSQL 中的业务表、Parquet 格式的日志文件、Quack 协议的远程数据库,甚至是 S3 上的对象存储。DuckDB 一直通过 ATTACH 语句来连接这些外部数据源,但传统方式有一个痛点:你需要显式地附加数据库、执行查询、然后显式地断开连接。
随着 PR #23555 的合并,DuckDB 引入了一个全新的语法糖——CONNECT 'URI',它让跨数据库查询变得更加优雅和简洁。
传统方式 vs 新语法
传统 ATTACH/DISCONNECT 模式
在过去,你要连接一个 Quack 远程数据库并查询,需要这样写:
-- 附加远程数据库
ATTACH 'quack:localhost' AS remote_db (TOKEN 'my-secret-token');
-- 切换到远程数据库上下文
CONNECT remote_db;
-- 执行查询
SELECT * FROM orders WHERE amount > 1000;
-- 切回主数据库
CONNECT main;
-- 最后记得断开连接
DETACH remote_db;
这段代码虽然功能完整,但步骤繁琐。每次连接都需要四到五步,而且容易忘记最后的 DETACH,导致连接泄漏。
新的 CONNECT ‘URI’ 语法
现在,你可以用一条语句完成同样的操作:
-- 一次性连接、查询、断开
CONNECT 'quack:localhost' (TOKEN 'my-secret-token');
-- 执行查询(自动在远程数据库上下文中)
SELECT * FROM orders WHERE amount > 1000;
-- 完成
DISCONNECT;
或者更进一步,如果你只需要执行一条查询就断开:
-- 连接后立即查询
CONNECT 'quack:localhost' (TOKEN 'my-secret-token');
SELECT COUNT(*) FROM users WHERE active = true;
DISCONNECT;
关键在于,CONNECT 'URI' 是临时的——执行 DISCONNECT 后,不仅断开连接,还会自动清理所有资源,包括之前手动 ATTACH 时可能遗留的上下文状态。
底层工作原理
CONNECT 'URI' 语法在内部等价于以下两步操作的组合:
CONNECT 'quack:localhost' (TOKEN 'qwerty')
-- 等价于
ATTACH 'quack:localhost' AS <auto_name> (TOKEN 'qwerty');
CONNECT <auto_name>;
而 DISCONNECT 则等价于:
DISCONNECT
-- 等价于
DETACH <auto_name>;
这种设计让 DuckDB 在保持向后兼容的同时,为用户提供了一个更简洁的接口。对于开发者来说,这意味着:
- 无需管理数据库别名:系统自动生成临时名称
- 自动清理:
DISCONNECT同时执行DETACH,防止连接泄漏 - 上下文切换透明:
CONNECT后自动切换到目标数据库
支持的数据库类型
目前 CONNECT 'URI' 主要支持以下两种协议:
| 协议 | URI 格式 | 用途 |
|---|---|---|
| Quack | quack:host:port | DuckDB 远程协议,跨实例查询 |
| PostgreSQL | postgres://host/db | PostgreSQL 数据库直连 |
Quack 协议示例
-- 连接到远程 Quack 服务
CONNECT 'quack:analytics-server:5432' (TOKEN 'your-auth-token');
-- 查询远程数据库中的聚合数据
SELECT
DATE_TRUNC('month', order_date) AS month,
SUM(amount) AS total_sales,
COUNT(DISTINCT customer_id) AS customers
FROM sales
GROUP BY month
ORDER BY month;
-- 断开连接,自动清理
DISCONNECT;
PostgreSQL 直连示例
-- 连接到 PostgreSQL 数据库
CONNECT 'postgres://user:[email protected]/analytics' ();
-- 查询 PostgreSQL 表
SELECT
p.product_name,
SUM(o.quantity) AS total_sold
FROM products p
JOIN orders o ON p.id = o.product_id
GROUP BY p.product_name
ORDER BY total_sold DESC
LIMIT 10;
-- 断开
DISCONNECT;
与传统 ATTACH 方式的对比
| 特性 | ATTACH + CONNECT | CONNECT 'URI' |
|---|---|---|
| 代码行数 | 4-5 行 | 2-3 行 |
| 需要管理别名 | 是 | 否 |
| 自动清理资源 | 否(需手动 DETACH) | 是(DISCONNECT 自动清理) |
| 连接泄漏风险 | 高 | 低 |
| 可读性 | 一般 | 优秀 |
| 向后兼容 | 完全兼容 | 新增语法 |
| 适用场景 | 长期多数据库会话 | 短期单次查询 |
实际应用场景
场景一:临时数据导出
当你需要从远程数据库导出数据到本地进行分析时:
-- 连接远程数据库
CONNECT 'quack:remote-server:5432' (TOKEN 'export-token');
-- 导出数据到 Parquet
COPY (SELECT * FROM transactions WHERE year = 2025)
TO '/tmp/transactions_2025.parquet' (FORMAT PARQUET);
-- 断开
DISCONNECT;
-- 本地分析
SELECT
region,
AVG(amount) AS avg_transaction,
MAX(amount) AS max_transaction
FROM read_parquet('/tmp/transactions_2025.parquet')
GROUP BY region;
场景二:跨库联合查询
-- 连接外部数据源
CONNECT 'quack:inventory-server:5432' (TOKEN 'inv-token');
-- 联合查询(外部数据 + 本地数据)
SELECT
p.product_name,
i.stock_quantity,
s.recent_sales
FROM products p
LEFT JOIN (
SELECT product_id, SUM(quantity) AS recent_sales
FROM local_sales
GROUP BY product_id
) s ON p.id = s.product_id
LEFT JOIN inventory i ON p.id = i.product_id
WHERE i.stock_quantity < 10;
DISCONNECT;
场景三:自动化报告生成
import duckdb
conn = duckdb.ATTACH (':memory:')
# 一次性连接远程数据库获取数据
result = conn.execute("""
---
## 本文信息
| 项目 | 内容 |
|------|------|
| DuckDB 版本 | v1.5.x(部分功能基于 v2.0 Preview) |
| 最后验证 | 2026-09-12 |
| 测试环境 | Linux / x86_64 / 16GB RAM |
| 官方文档 | [DuckDB Documentation](https://duckdb.org/docs/) |
| GitHub | [pengzz9527/duckdb-blog](https://github.com/pengzz9527/duckdb-blog) |
如发现错误,欢迎通过 [GitHub Issue](https://github.com/pengzz9527/duckdb-blog/issues) 或邮件 [email protected] 反馈。