☰
使用 Drizzle ORM 创建 bigint 自增主键(Identity Column)完整指南
2026/10/4 1:51:21 网站建设 项目流程
  • 文档
  • 教程
  • 知识库

【免费下载链接】til

:memo: Today I Learned

项目地址:https://gitcode.com/gh_mirrors/ti/til
点击查看免费下载

在使用 Drizzle ORM 操作 PostgreSQL 时,如何为表定义一个既具备bigint数据类型、又使用现代 identity column 机制自动生成的主键?本文基于 drizzle/create-bigint-identity-column-for-primary-key.md 展开,给出可直接运行的 TypeScript 表定义、drizzle-kit generate生成的真实迁移 SQL,并结合本仓库中关于迁移日志与插入返回值的相关笔记,帮你彻底掌握这一常见建模场景。

完整示例:定义一张带 bigint 自增主键的表

假设我们要创建一张users表,主键id使用 PostgreSQL 的bigint类型,并通过 identity column 让数据库自动生成主键值。在 Drizzle 中,完整定义如下:

import { pgTable, bigint, text, timestamp, } from "drizzle-orm/pg-core"; // Users table export const users = pgTable("users", { id: bigint({ mode: 'bigint' }).primaryKey().generatedAlwaysAsIdentity(), email: text("email").unique().notNull(), name: text("name").notNull(), createdAt: timestamp("created_at").defaultNow().notNull(), });

这段代码的核心在于id这一列的链式调用:

  1. 导入bigint:从drizzle-orm/pg-core中导入bigint,用于声明这一列的数据类型为 PostgreSQL 的bigint;
  2. 指定主键:通过.primaryKey()将id声明为表的主键;
  3. 声明自增语义:通过.generatedAlwaysAsIdentity()将默认值语义声明为generated always as identity,即主键值由数据库侧的 identity 机制自动生成。

为什么bigint必须指定mode

这是本示例中最容易踩坑的一点:bigint必须显式传入mode配置,否则运行时会抛出如下错误:

TypeError: Cannot read properties of undefined (reading 'mode')

原因是 Drizzle 中的bigint()需要依赖mode来决定该列在 JavaScript / TypeScript 侧的序列化方式:

  • mode: 'bigint':该列的值在查询结果中会被还原为 JavaScript 的BigInt类型,适合表示超出Number.MAX_SAFE_INTEGER(9007199254740991)范围的整数;
  • mode: 'number':该列的值会被还原为 JavaScript 的Number类型,适合范围在安全整数之内的场景。

由于 PostgreSQL 的bigint是 8 字节有符号整数,取值范围为-9223372036854775808到9223372036854775807(参见仓库笔记 postgres/integers-in-postgres.md),上限远超过 JSNumber的安全范围。因此当主键可能增长到较大数值时,mode: 'bigint'是更稳妥的选择。选择number模式虽然代码上可行,但需要你自行确保取值不会溢出安全整数范围。

生成的迁移 SQL:drizzle-kit generate 的产物

当表结构定义好后,运行迁移生成命令:

npx drizzle-kit generate

Drizzle 会根据 schema 生成对应的 SQL 迁移文件,其中会包含类似如下的建表语句:

--> statement-breakpoint CREATE TABLE IF NOT EXISTS "users" ( "id" bigint PRIMARY KEY GENERATED ALWAYS AS IDENTITY (sequence name "users_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 9223372036854775807 START WITH 1 CACHE 1), "email" text NOT NULL, "name" text NOT NULL, "created_at" timestamp DEFAULT now() NOT NULL, CONSTRAINT "users_email_unique" UNIQUE("email") );

这份 SQL 可以逐项与上面的 TypeScript 定义对上号:

TypeScript 声明生成的 SQL 片段说明
bigint({ mode: 'bigint' })"id" bigint列数据类型为bigint(8 字节有符号整数)
.primaryKey()PRIMARY KEY列被声明为表的主键
.generatedAlwaysAsIdentity()GENERATED ALWAYS AS IDENTITY (sequence name "users_id_seq" INCREMENT BY 1 MINVALUE 1 MAXVALUE 9223372036854775807 START WITH 1 CACHE 1)由数据库自动生成主键值,并自动创建配套的序列users_id_seq
text("email").unique().notNull()"email" text NOT NULL+CONSTRAINT "users_email_unique" UNIQUE("email")非空且唯一
text("name").notNull()"name" text NOT NULL非空
timestamp("created_at").defaultNow().notNull()"created_at" timestamp DEFAULT now() NOT NULL默认取当前时间且非空

注意 identity 子句中的参数:INCREMENT BY 1 MINVALUE 1 MAXVALUE 9223372036854775807 START WITH 1 CACHE 1。这里的MAXVALUE 9223372036854775807恰好就是bigint类型的最大值上限,说明 PostgreSQL 为该 identity 列自动生成的序列直接对齐了bigint的完整取值范围。

为什么用 identity column 而不是 serial

主键自增在历史上最流行的写法是serial(或bigserial)。本仓库的 postgres/generate-modern-primary-key-columns.md 专门讨论了这一问题:PostgreSQL 官方 wiki 明确建议新应用不要使用serial,而应使用 identity columns,原因是 serial 类型在 schema、依赖和权限管理上存在一些绕不开的怪异行为。

因此,.generatedAlwaysAsIdentity()映射到的GENERATED ALWAYS AS IDENTITY正是当前推荐的现代做法。与之相对,Drizzle 也提供了.generatedByDefaultAsIdentity(),对应 SQL 中的GENERATED BY DEFAULT AS IDENTITY,二者的区别在于:

  • GENERATED ALWAYS AS IDENTITY:应用无法显式写入该列的主键值(除非使用OVERRIDING SYSTEM VALUE),保证值完全由数据库生成;
  • GENERATED BY DEFAULT AS IDENTITY:允许应用在插入时显式提供该列的值,仅在未提供时由数据库生成。

对于不希望业务代码干预主键生成的场景,本示例采用的ALWAYS语义是更严格、更安全的选择。

结合迁移与插入流程的完整实战闭环

定义好 identity 主键只是第一步,把整个流程串起来还需要理解两件事:迁移如何被跟踪,以及插入后如何拿回数据库自动生成的id。

迁移文件如何被记录

运行npx drizzle-kit generate生成 SQL 迁移文件后,还需要运行npx drizzle-kit migrate将其应用到数据库。仓库笔记 drizzle/drizzle-tracks-migrations-in-a-log-table.md 说明:Drizzle 会像其他 SQL 迁移工具一样,在数据库中使用一张日志表(默认名为__drizzle_migrations,位于drizzleschema 下)记录每个迁移文件的 SHA256 哈希和运行时间戳,从而判断哪些迁移已经执行、哪些还没有执行。所以上述建表 SQL 一旦被migrate应用,就会被登记在这张日志表中,后续重复运行不会再次执行。

插入后获取自动生成的主键值

由于id由数据库侧的 identity 机制生成,普通的insert返回值是QueryResult<never>,拿不到任何有用的数据。仓库笔记 drizzle/get-fields-for-inserted-row.md 给出了标准解法:在 insert 语句后追加.returning(),让 PostgreSQL 返回插入行的全部字段;如果只需要新行的id,还可以做部分返回:

await db .insert(users) .values({ email, name, }) .returning({ id: users.id })

这两篇仓库内的相关笔记分别对应 drizzle/drizzle-tracks-migrations-in-a-log-table.md 与 drizzle/get-fields-for-inserted-row.md,与本文主题共同构成"定义 identity 主键 → 生成并应用迁移 → 插入并回读主键"的完整实战链路。

小结

本文围绕 Drizzle ORM + PostgreSQL 的bigintidentity 主键,覆盖了从 TypeScript 表定义、mode参数的必要性、drizzle-kit generate生成的迁移 SQL 解读,到现代 identity column 相对serial的优势,以及迁移跟踪与插入返回值两个相邻环节。掌握这一组合,你就可以放心地为新表设计大规模、数据库自主生成的主键,并让 Drizzle 的 schema 定义、迁移与运行时查询保持一致。

更多相关主题可继续阅读仓库中的 Drizzle 分类 与 PostgreSQL 分类 笔记。

  • 文档
  • 教程
  • 知识库

【免费下载链接】til

:memo: Today I Learned

项目地址:https://gitcode.com/gh_mirrors/ti/til
点击查看免费下载
上一篇:Mac Mouse Fix系统更新后鼠标功能异常完整修复指南:诊断、修复、预防全攻略
下一篇:3步打造专业Golang终端应用:从开发到分发的完整指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询