数据写入流程中,一个看似简单的任务经常会制造额外复杂度:为每一行生成唯一的数值 ID。过去,这项工作可能由应用代码、ETL 工具或单独的序列服务完成,既增加了维护成本,也让数据导入链路变得更脆弱。
BigQuery 现在支持 identity columns,可以在表中定义自动生成的 INT64 标识列。插入新数据时,BigQuery 会负责生成 ID,数据工程师则可以把精力放在数据质量、转换逻辑和分析模型上。
Identity Column 解决了什么问题
Identity column 的核心价值,是把代理键生成从数据管道中移交给 BigQuery。应用或 ETL 流程不再需要提前查询最大 ID、维护计数器,或者在并发写入时自行协调唯一键。
这会直接减少几类常见的工程负担:
- 简化数据摄取:写入业务字段即可,不必在上游预计算唯一 ID。
- 减少样板代码:ID 生成逻辑由表定义承载,SQL 和应用代码更简洁。
- 统一 DML 行为:通过
INSERT或MERGE写入的新行,都可以自动获得标识值。 - 降低并发风险:不需要依赖“查询当前最大值再加一”这类容易产生竞争条件的方案。
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_name 和 order_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 很适合做内部代理键,但数据管道仍然需要明确它的语义和约束。
- 不要把代理键当作业务时间或事件顺序。批量写入、并发任务和重试场景下,ID 适合作为唯一标识,不应被用来推断严格的业务发生顺序。
- 保留业务主键或幂等键。例如订单号、事件 ID 仍应由业务系统或上游数据提供,用于去重、重放和 MERGE 匹配。
- 谨慎选择手动覆盖模式。只有在确实需要导入外部 ID 或迁移历史数据时,才考虑
GENERATED BY DEFAULT AS IDENTITY。 - 在下游模型中记录来源字段。代理键解决的是表内标识问题,数据血缘、批次号和源系统 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 负责一项清晰的职责:为表中的新行提供由平台管理的数值标识,同时保留业务键、批次信息和去重逻辑,让数据管道更短,也更容易维护。