用 BigQuery Identity Columns 简化数据管道中的主键生成

2026-09-03 33 预计阅读时间: 1 分钟
来源: cloud.google.com AI 摘要 Original link

Disclaimer: This article is an AI-assisted summary. Read it together with the original source when precision matters. The summary may omit context, version differences, or edge cases and is not official documentation.

预计阅读时间:8 分钟

数据写入流程中,一个看似简单的任务经常会制造额外复杂度:为每一行生成唯一的数值 ID。过去,这项工作可能由应用代码、ETL 工具或单独的序列服务完成,既增加了维护成本,也让数据导入链路变得更脆弱。

BigQuery 现在支持 identity columns,可以在表中定义自动生成的 INT64 标识列。插入新数据时,BigQuery 会负责生成 ID,数据工程师则可以把精力放在数据质量、转换逻辑和分析模型上。

Identity Column 解决了什么问题

Identity column 的核心价值,是把代理键生成从数据管道中移交给 BigQuery。应用或 ETL 流程不再需要提前查询最大 ID、维护计数器,或者在并发写入时自行协调唯一键。

这会直接减少几类常见的工程负担:

  • 简化数据摄取:写入业务字段即可,不必在上游预计算唯一 ID。
  • 减少样板代码:ID 生成逻辑由表定义承载,SQL 和应用代码更简洁。
  • 统一 DML 行为:通过 INSERTMERGE 写入的新行,都可以自动获得标识值。
  • 降低并发风险:不需要依赖“查询当前最大值再加一”这类容易产生竞争条件的方案。

Identity column 适合代理键、导入批次中的数值标识,以及不需要由业务系统直接决定的内部行 ID。它不应替代订单号、用户编码等具有业务含义的标识符。

两种生成策略

BigQuery 提供两种主要定义方式:

定义方式 行为
GENERATED ALWAYS AS IDENTITY BigQuery 自动管理标识值,写入时不允许手动覆盖该列。
GENERATED BY DEFAULT AS IDENTITY 默认由 BigQuery 自动生成,但在需要时允许手动提供 ID。

如果 ID 完全属于数据库内部管理,并且不希望上游覆盖它,通常可以选择 GENERATED ALWAYS AS IDENTITY。如果需要迁移历史数据、回放数据或保留外部系统中的既有 ID,则可以评估 GENERATED BY DEFAULT AS IDENTITY

选择 BY DEFAULT 时要额外约束调用方,避免不同系统随意写入重复或冲突的 ID。自动生成机制解决的是默认生成问题,并不意味着业务上所有来源的手动 ID 都天然合法。

创建表并写入数据

下面的示例创建一个订单表,让 order_id 从 1 开始、按 1 递增。运行前请将项目和数据集名称替换成自己的 BigQuery 资源;同时确认当前账号拥有创建表和写入数据的权限。

CREATE TABLE `my_project.my_dataset.orders` (
  order_id INT64 GENERATED ALWAYS AS IDENTITY (
    START WITH 1
    INCREMENT BY 1
  ),
  customer_name STRING,
  order_date DATE
);

INSERT INTO `my_project.my_dataset.orders` (
  customer_name,
  order_date
)
VALUES
  ('Joe Doe', CURRENT_DATE()),
  ('Jane Smith', CURRENT_DATE());

SELECT order_id, customer_name, order_date
FROM `my_project.my_dataset.orders`
ORDER BY order_id;

写入时不需要在 INSERT 列表中包含 order_id。BigQuery 会为新增行生成标识值,应用只需要提交 customer_nameorder_date 等业务字段。

在批量同步场景中,也可以继续使用 MERGE。例如,下面的语句根据客户名称更新已有订单,否则插入新订单:

MERGE `my_project.my_dataset.orders` AS target
USING (
  SELECT 'Joe Doe' AS customer_name, CURRENT_DATE() AS order_date
) AS source
ON target.customer_name = source.customer_name
WHEN MATCHED THEN
  UPDATE SET order_date = source.order_date
WHEN NOT MATCHED THEN
  INSERT (customer_name, order_date)
  VALUES (source.customer_name, source.order_date);

对于 WHEN NOT MATCHED 分支,只需写入非 identity 字段即可。表结构负责 ID 生成,MERGE 逻辑则继续专注于匹配和更新规则。

使用时需要确认的边界

自动生成的数值 ID 很适合做内部代理键,但数据管道仍然需要明确它的语义和约束。

  1. 不要把代理键当作业务时间或事件顺序。批量写入、并发任务和重试场景下,ID 适合作为唯一标识,不应被用来推断严格的业务发生顺序。
  2. 保留业务主键或幂等键。例如订单号、事件 ID 仍应由业务系统或上游数据提供,用于去重、重放和 MERGE 匹配。
  3. 谨慎选择手动覆盖模式。只有在确实需要导入外部 ID 或迁移历史数据时,才考虑 GENERATED BY DEFAULT AS IDENTITY
  4. 在下游模型中记录来源字段。代理键解决的是表内标识问题,数据血缘、批次号和源系统 ID 仍然值得单独保存。

可以这样设计一张更适合生产使用的表:identity column 负责内部数值键,source_order_id 负责业务幂等,ingestion_batch_id 负责追踪导入批次。

CREATE TABLE `my_project.my_dataset.order_events` (
  order_event_id INT64 GENERATED ALWAYS AS IDENTITY,
  source_order_id STRING NOT NULL,
  ingestion_batch_id STRING NOT NULL,
  event_type STRING NOT NULL,
  event_date DATE NOT NULL
);

这种分工可以避免把“数据库生成的行 ID”和“业务世界中的稳定标识”混为一谈。

是否应该立即采用

如果现有管道只是为了生成代理键而维护额外逻辑,identity columns 可以显著缩短写入路径,尤其适合新建的事实表、维度表和落地表。迁移已有表时,则应检查下游报表、数据导出和增量同步是否依赖原有 ID 的生成规则。

落地前可以检查以下事项:

  • 新表是否确实需要数值型代理键。
  • 上游是否可以停止生成该字段。
  • 是否需要保留外部业务 ID 和幂等键。
  • 历史数据迁移是否要求手动保留 ID。
  • 下游是否错误地把 ID 大小当作时间顺序。

将 ID 生成交给 BigQuery,并不意味着数据建模和幂等设计可以省略。更合理的做法是让 identity column 负责一项清晰的职责:为表中的新行提供由平台管理的数值标识,同时保留业务键、批次信息和去重逻辑,让数据管道更短,也更容易维护。


相关推荐