跳到主要内容

Schema

类型

为列选择正确类型非常重要(如 BIGINTDATEDATETIME)。虽然你总能用字符串类型(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 typeStorage sizeQuery time
DATETIME3.3 GB0.9 s
VARCHAR5.2 GB3.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;

第一组实验使用正确(最严格)的类型,即 idParentCommentId 都定义为 BIGINT。 第二组实验将所有列定义为 VARCHAR。 虽然两组查询结果一致,但运行时差异明显。 下表显示:在 BIGINT 列上 Join,相比在编码同一值的 VARCHAR 列上 Join,约快 1.8×。

Join column payload typeJoin column schema typeExample valueQuery time
BIGINTBIGINT703687556400781.2 s
BIGINTVARCHAR'70368755640078'2.1 s

最佳实践:避免将数值表示为字符串,尤其是在需要对其执行 Join 等操作时。

约束

Goose 允许定义 constraints(如 UNIQUEPRIMARY KEYFOREIGN KEY)。这些约束有助于保证数据完整性,但会对导入性能产生负面影响,因为需要建索引并执行校验。此外,它们_极少提升查询性能_,因为 Goose 的 Join 与聚合算子通常不依赖这些索引(详见 indexing)。

最佳实践:除非目标是保证数据完整性,否则不要定义约束。

微基准:主键的影响

我们用 LDBC Comment 表(scale factor 300)演示主键影响。 该表约有 5.54 亿条记录。 第一组实验:创建主键的 schema,再加载数据。 第二组实验:创建不带主键的 schema,再加载数据。 第三组实验:先创建不带主键 schema,加载数据后再添加主键约束。 三组都从 .csv.gz 文件读取,并统计加载耗时。

OperationExecution time
Load with primary key461.6 s
Load without primary key121.0 s
Load without primary key then add primary key242.0 s

对该数据集而言,主键只会在高选择性查询(如按单一标识过滤)上带来(较小)正向收益。 定义主键(或索引)不会提升 Join 与聚合算子性能。

最佳实践:要获得最佳批量加载性能,请避免主键约束。 若必须使用,请在批量加载完成后再定义。