示例说明
线上加列直接 NOT NULL 会锁表并拒绝已有行,标准做法是三段式:先加可空列让写入路径兼容,再按业务规则回填历史数据,最后收紧非空约束。Drizzle 的 sql 模板让手写步骤与类型安全表定义共存,drizzle-kit generate 还能从 schema 差异自动生成迁移文件供评审。
技术要点
- 可空先行避免锁表与写失败
- 回填单独成步可控可重跑
- 收紧约束前数据已完整
代码解读
三个 sql 片段对应三步:加列无默认不重写历史行;回填把到期日设为创建日加三十天并限定 NULL 条件支持幂等重跑;最后 ALTER 设非空并补索引。applySteps 顺序执行后追加索引创建,IF NOT NULL 兜底重复执行。
使用建议
大表回填要分批提交控制事务大小与锁窗口。迁移文件进版本库并禁止手改已执行过的历史。收紧约束前用检查查询确认无残留 NULL。
代码示例
import { pgTable, text, integer, timestamp, index } from 'drizzle-orm/pg-core';
import { sql } from 'drizzle-orm';
// 初始 schema(drizzle/0000_init.sql 由 drizzle-kit generate 产出)
const invoices = pgTable('invoices', {
id: integer('id').primaryKey(),
customer: text('customer').notNull(),
createdAt: timestamp('created_at').defaultNow(),
});
// 增量迁移:新增可空列 → 回填 → 收紧约束
const ADD_COLUMN = sql`
ALTER TABLE invoices
ADD COLUMN due_date timestamp;
`;
const BACKFILL = sql`
UPDATE invoices
SET due_date = created_at + interval '30 days'
WHERE due_date IS NULL;
`;
const TIGHTEN = sql`
ALTER TABLE invoices
ALTER COLUMN due_date SET NOT NULL;
`;
async function applySteps(db, steps: ReturnType<typeof sql>[]) {
for (const step of steps) {
await db.execute(step);
}
await db.execute(sql`
CREATE INDEX IF NOT EXISTS invoices_due_date_idx
ON invoices (due_date);
`);
}
console.log('three-step migration prepared: add, backfill, tighten');