- 安装
- 文档
- 入门
- 连接
- 数据导入与导出
- 湖仓格式
- 客户端 API
- 概览
- 第三方客户端
- ADBC
- C
- C++
- CLI
- Dart
- Go
- Java (JDBC)
- Julia
- Node.js (已弃用)
- Node.js (Neo)
- ODBC
- PHP
- Python
- R
- Rust
- Swift
- Wasm
- SQL
- 介绍
- 语句
- 概览
- ANALYZE
- ALTER TABLE
- ALTER VIEW
- ATTACH 和 DETACH
- CALL
- CHECKPOINT
- COMMENT ON
- COPY
- CREATE INDEX
- CREATE MACRO
- CREATE SCHEMA
- CREATE SECRET
- CREATE SEQUENCE
- CREATE TABLE
- CREATE VIEW
- CREATE TYPE
- DELETE
- DESCRIBE
- DROP
- EXPORT 和 IMPORT DATABASE
- INSERT
- LOAD / INSTALL
- MERGE INTO
- PIVOT
- 性能分析
- SELECT
- SET / RESET
- SET VARIABLE
- SHOW 与 SHOW DATABASES
- SUMMARIZE
- 事务管理
- UNPIVOT
- UPDATE
- USE
- VACUUM
- 查询语法
- SELECT
- FROM 和 JOIN
- WHERE
- GROUP BY
- GROUPING SETS
- HAVING
- ORDER BY
- LIMIT 和 OFFSET
- SAMPLE
- 展开嵌套
- WITH
- WINDOW
- QUALIFY
- VALUES
- FILTER
- 集合操作
- 预处理语句
- 数据类型
- 表达式
- 函数
- 概览
- 聚合函数
- 数组函数
- 位字符串函数
- Blob 函数
- 日期格式化函数
- 日期函数
- 日期部分函数
- 枚举函数
- 间隔函数
- Lambda 函数
- 列表函数
- 映射函数
- 嵌套函数
- 数值函数
- 模式匹配
- 正则表达式
- 结构体函数
- 文本函数
- 时间函数
- 时间戳函数
- 带时区时间戳函数
- 联合函数
- 实用函数
- 窗口函数
- 约束
- 索引
- 元查询
- DuckDB 的 SQL 方言
- 示例
- 配置
- 扩展
- 核心扩展
- 概览
- 自动补全
- Avro
- AWS
- Azure
- Delta
- DuckLake
- 编码
- Excel
- 全文搜索
- httpfs (HTTP 和 S3)
- Iceberg
- ICU
- inet
- jemalloc
- Lance
- MySQL
- PostgreSQL
- 空间
- SQLite
- TPC-DS
- TPC-H
- UI
- Unity Catalog
- Vortex
- VSS
- 指南
- 概览
- 数据查看器
- 数据库集成
- 文件格式
- 概览
- CSV 导入
- CSV 导出
- 直接读取文件
- Excel 导入
- Excel 导出
- JSON 导入
- JSON 导出
- Parquet 导入
- Parquet 导出
- 查询 Parquet 文件
- 使用 file: 协议访问文件
- 网络和云存储
- 概览
- HTTP Parquet 导入
- S3 Parquet 导入
- S3 Parquet 导出
- S3 Iceberg 导入
- S3 Express One
- GCS 导入
- Cloudflare R2 导入
- 通过 HTTPS / S3 使用 DuckDB
- Fastly 对象存储导入
- 元查询
- ODBC
- 性能
- Python
- 安装
- 执行 SQL
- Jupyter Notebooks
- marimo Notebooks
- Pandas 上的 SQL
- 从 Pandas 导入
- 导出到 Pandas
- 从 Numpy 导入
- 导出到 Numpy
- Arrow 上的 SQL
- 从 Arrow 导入
- 导出到 Arrow
- Pandas 上的关系型 API
- 多个 Python 线程
- 与 Ibis 集成
- 与 Polars 集成
- 使用 fsspec 文件系统
- SQL 编辑器
- SQL 功能
- 代码片段
- 故障排除
- 术语表
- 离线浏览
- 操作手册
- 概览
- DuckDB 的占用空间
- 安装 DuckDB
- 日志
- 保护 DuckDB 安全
- 非确定性行为
- 限制
- DuckDB Docker 容器
- 开发
- 内部结构
- 站点地图
- 在线演示
类型
使用正确的类型对列进行编码(例如 BIGINT、DATE、DATETIME)非常重要。虽然总是可以使用字符串类型(VARCHAR 等)来编码更具体的值,但这并不推荐。字符串会占用更多空间,且在过滤、连接 (join) 和聚合等操作中处理速度较慢。
在加载 CSV 文件时,您可以利用 CSV 读取器的 自动检测机制 来获取 CSV 输入的正确类型。
如果您在内存受限的环境中运行,使用较小的数据类型(例如 TINYINT)可以减少完成查询所需的内存和磁盘空间。DuckDB 的 位压缩 (bitpacking compression) 意味着存储在较大类型中的小数值在磁盘上不会占用更大的空间,但在处理过程中它们会占用更多内存。
最佳实践:在创建列时,尽量使用最严格的类型。避免使用字符串来编码更具体的数据项。
微基准测试:使用时间戳
我们使用 规模因子 (scale factor) 为 300 的 LDBC Comment 表中的 creationDate 列 来展示聚合速度的差异。该表约有 5.54 亿个无序的时间戳值。我们运行一个简单的聚合查询,计算两种配置下时间戳中每月对应的平均日期。
首先,我们使用 DATETIME 对值进行编码,并使用 extract 日期时间函数 运行查询。
SELECT avg(extract('day' FROM creationDate)) FROM Comment;
其次,我们使用 VARCHAR 类型并使用字符串操作。
SELECT avg(CAST(creationDate[9:10] AS INTEGER)) FROM Comment;
微基准测试结果如下:
| 列类型 | 存储大小 | 查询时间 |
|---|---|---|
DATETIME |
3.3 GB | 0.9 秒 |
VARCHAR |
5.2 GB | 3.9 秒 |
结果表明,使用 DATETIME 值可获得更小的存储空间和更快的处理速度。
微基准测试:基于字符串的连接
我们通过计算 规模因子为 100 的 LDBC Comment 表 的自连接,来说明不同类型对连接操作的影响。该表使用 64 位整数标识符作为每一行的 id 属性。我们执行以下连接操作:
SELECT count(*) AS count
FROM Comment c1
JOIN Comment c2 ON c1.ParentCommentId = c2.id;
在第一个实验中,我们使用正确的(最严格的)类型,即将 id 和 ParentCommentId 列都定义为 BIGINT。在第二个实验中,我们将所有列定义为 VARCHAR 类型。虽然两个实验的查询结果相同,但运行时间差异显著。以下结果显示,在 BIGINT 列上进行连接的速度比在编码相同值的 VARCHAR 类型列上进行相同连接的速度快约 1.8 倍。
| 连接列载荷类型 | 连接列模式类型 | 示例值 | 查询时间 |
|---|---|---|---|
BIGINT |
BIGINT |
70368755640078 |
1.2 秒 |
BIGINT |
VARCHAR |
'70368755640078' |
2.1 秒 |
最佳实践:避免将数值表示为字符串,特别是当您打算对其执行连接操作时。
约束
DuckDB 允许定义 约束 (constraints),如 UNIQUE、PRIMARY KEY 和 FOREIGN KEY。这些约束有助于确保数据完整性,但会对加载性能产生负面影响,因为它们需要构建索引并执行检查。此外,它们 极少能提高查询性能,因为 DuckDB 在进行连接和聚合操作时并不依赖这些索引(有关详细信息,请参阅 索引)。
最佳实践:除非您的目标是确保数据完整性,否则请勿定义约束。
微基准测试:主键的影响
我们使用 规模因子为 300 的 LDBC Comment 表 来展示使用主键的效果。该表约有 5.54 亿条条目。在第一个实验中,我们创建 带有 主键的模式,然后加载数据。在第二个实验中,我们创建 不带 主键的模式,然后加载数据。在第三个实验中,我们创建 不带 主键的模式,加载数据,然后添加主键约束。在所有情况下,我们都从 .csv.gz 文件中提取数据,并测量执行加载所需的时间。
| 操作 | 执行时间 |
|---|---|
| 带主键加载 | 461.6 秒 |
| 不带主键加载 | 121.0 秒 |
| 不带主键加载后添加主键 | 242.0 秒 |
对于此数据集,主键仅在高度选择性的查询(例如根据单个标识符进行过滤)上会有(微小的)正面影响。定义主键(或索引)不会对连接和聚合算子产生影响。
最佳实践:为了获得最佳批量加载性能,请避免使用主键约束。如果确实需要,请在批量加载完成后再定义它们。