类型
使用正确的类型来编码列(例如,BIGINT
、DATE
、DATETIME
)是很重要的。虽然总是可以使用字符串类型(VARCHAR
等)来编码更具体的值,但这并不推荐。字符串占用更多空间,并且在过滤、连接和聚合等操作中处理速度较慢。
加载CSV文件时,您可以利用CSV阅读器的自动检测机制来获取CSV输入的正确类型。
如果您在内存受限的环境中运行,使用较小的数据类型(例如,TINYINT
)可以减少完成查询所需的内存和磁盘空间。DuckDB的位打包压缩意味着存储在较大数据类型中的小值不会在磁盘上占用更大的空间,但在处理过程中会占用更多的内存。
最佳实践 在创建列时使用最严格的类型。避免使用字符串来编码更具体的数据项。
微基准测试:使用时间戳
我们使用creationDate
列在LDBC评论表上的比例因子300来说明聚合速度的差异。该表大约有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
值可以产生更小的存储大小和更快的处理速度。
微基准测试:字符串连接
我们通过计算LDBC评论表在比例因子100下的自连接来说明不同类型连接引起的差异。该表具有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 允许定义诸如 UNIQUE
、PRIMARY KEY
和 FOREIGN KEY
的 约束。这些约束对于确保数据完整性非常有益,但它们会对加载性能产生负面影响,因为它们需要构建索引并执行检查。此外,它们很少能提高查询性能,因为 DuckDB 在连接和聚合操作中并不依赖这些索引(更多详情请参见 索引)。
最佳实践 除非您的目标是确保数据完整性,否则不要定义约束。
微基准测试:主键的影响
我们通过使用LDBC Comment表在规模因子300的情况下来说明使用主键的效果。该表大约有5.54亿条记录。我们首先创建一个没有主键的模式,然后加载数据。在第二个实验中,我们创建一个带有主键的模式,然后加载数据。在这两种情况下,我们从.csv.gz
文件中获取数据,并测量执行加载所需的时间。
操作 | 执行时间 |
---|---|
无主键加载 | 92.2 s |
使用主键加载 | 286.8 秒 |
在这种情况下,主键只会对高度选择性的查询(例如在单个标识符上过滤时)产生(较小的)积极影响。它们对连接和聚合操作符没有影响。
最佳实践 为了获得最佳的批量加载性能,尽可能避免定义主键约束。