Schema
类型
为列选择正确类型非常重要(如 BIGINT、DATE、DATETIME)。虽然你总能用字符串类型(VARCHAR 等)编码更具体的值,但这并不推荐。字符串占用空间更大,在过滤、Join、聚合等操作中也更慢。
加载 CSV 文件时,可利用 CSV reader 的自动检测机制为 CSV 输入推断正确类型。
如果运行环境内存受限,使用更小的数据类型(如 TINYINT)可减少完成查询所需的内存与磁盘空间。Goose 的 bitpacking compression意味着:小值即使存于更大类型,在磁盘上未必更大,但在处理阶段会占用更多内存。
最佳实践:建列时尽量使用约束最强(最具体)的类型。避免用字符串表示更具体的数据项。
微基准:使用时间戳
我们用 LDBC Comment 表(scale factor 300)的 creationDate 列演示聚合速度差异。该表约有 5.54 亿条无序时间戳。我们在两种配置下运行一个简单聚合查询,返回时间戳中的“月内日”平均值。
第一种,使用 DATETIME 编码,并通过 extract 日期时间函数执行查询:
SELECT avg(extract('day' FROM creationDate)) FROM Comment;
第二种,使用 VARCHAR 类型并采用字符串操作:
SELECT avg(CAST(creationDate[9:10] AS INTEGER)) FROM Comment;
微基准结果如下:
| Column type | Storage size | Query time |
|---|---|---|
DATETIME | 3.3 GB | 0.9 s |
VARCHAR | 5.2 GB | 3.9 s |
结果表明,使用 DATETIME 可获得更小存储体积与更快处理速度。
微基准:在字符串上做 Join
我们通过在 LDBC Comment 表(scale factor 100)上执行自连接,演示不同类型 Join 带来的差异。该表每行 id 使用 64 位整数标识。执行如下 Join:
SELECT count(*) AS count
FROM Comment c1
JOIN Comment c2 ON c1.ParentCommentId = c2.id;
第一组实验使用正确(最严格)的类型,即 id 和 ParentCommentId 都定义为 BIGINT。
第二组实验将所有列定义为 VARCHAR。
虽然两组查询结果一致,但运行时差异明显。
下表显示:在 BIGINT 列上 Join,相比在编码同一值的 VARCHAR 列上 Join,约快 1.8×。
| Join column payload type | Join column schema type | Example value | Query time |
|---|---|---|---|
BIGINT | BIGINT | 70368755640078 | 1.2 s |
BIGINT | VARCHAR | '70368755640078' | 2.1 s |
最佳实践:避免将数值表示为字符串,尤其是在需要对其执行 Join 等操作时。
约束
Goose 允许定义 constraints(如 UNIQUE、PRIMARY KEY、FOREIGN KEY)。这些约束有助于保证数据完整性,但会对导入性能产生负面影响,因为需要建索引并执行校验。此外,它们_极少提升查询性能_,因为 Goose 的 Join 与聚合算子通常不依赖这些索引(详见 indexing)。
最佳实践:除非目标是保证数据完整性,否则不要定义约束。
微基准:主键的影响
我们用 LDBC Comment 表(scale factor 300)演示主键影响。
该表约有 5.54 亿条记录。
第一组实验:创建带主键的 schema,再加载数据。
第二组实验:创建不带主键的 schema,再加载数据。
第三组实验:先创建不带主键 schema,加载数据后再添加主键约束。
三组都从 .csv.gz 文件读取,并统计加载耗时。
| Operation | Execution time |
|---|---|
| Load with primary key | 461.6 s |
| Load without primary key | 121.0 s |
| Load without primary key then add primary key | 242.0 s |
对该数据集而言,主键只会在高选择性查询(如按单一标识过滤)上带来(较小)正向收益。 定义主键(或索引)不会提升 Join 与聚合算子性能。
最佳实践:要获得最佳批量加载性能,请避免主键约束。 若必须使用,请在批量加载完成后再定义。