Drizzle ORM 的沉默丢字段:schema 漂移让写操作无声消失
某天排查一个工作流任务系统:连续几十次失败的任务运行,runs.error 列全是 NULL。代码里的 catch 块明明把错误信息传进去了,console.log(dbPatch) 也打出了正确的值,手动 UPDATE runs SET error='test' WHERE id=1 也写进去了。那条错误信息究竟去了哪里?
排查花了半个小时,最终发现根因只有四个字:schema 漂移。
现象
runs.error列在 DB 中确实存在(SHOW COLUMNS FROM runs可以看到)- 代码 catch 块构建
{ status: 'failed', error: msg, finishedAt: now() }传给 patch helper - Helper 调用 Drizzle 的
client.update(runs).set(dbPatch).where(...)后返回成功 - 查 DB:
status写进去了,error和finished_at永远是 NULL - 没有任何异常、没有任何警告日志
根因
Drizzle ORM 的 update(table).set(patch) 在生成 SQL 时,只会把 patch 对象里同时出现在 drizzle schema 定义中的字段塞进 SET 子句。patch 里多余的 key 直接被忽略,不抛错,不警告。
这个行为是 Drizzle 有意为之的"宽容更新"设计——但它让 schema 漂移彻底隐形。
在这个案例中,runs.error 和 runs.finished_at 两列是几个月前通过原始 ALTER TABLE 手工加进去的,但 schema.ts 里的 mysqlTable('runs', { ... }) 块从未更新过。结果:
// schema.ts(漂移状态)
runs: mysqlTable('runs', {
id: int('id').primaryKey(),
status: varchar('status', { length: 32 }),
// error 和 startedAt 根本不在这里!
})-- Drizzle 实际生成的 SQL(error 和 finished_at 被静默丢弃)
UPDATE runs SET status = 'failed' WHERE id = ?直接对数据库执行 ALTER TABLE 添加列,然后在代码里往 patch 对象里放了这个 key —— Drizzle 不会告诉你它忽略了这个 key。update().set() 顺利执行,返回值看起来没问题,但那个列的值在 DB 里永远不会变。
这个坑最难察觉的地方:手动 SQL 写入没问题(证明列可写)、代码不报错(证明逻辑通了)、测试通过(断言通常只看 status 之类的主要字段)。三个"正常"叠在一起,让人自然怀疑是编码、字符集、事务等其他问题。
正解
两步确认漂移
# 步骤 1:确认 DB 有这列
mysql -e "SHOW COLUMNS FROM runs" | grep error
# 步骤 2:确认 drizzle schema 没有这列
grep -A 30 "runs: mysqlTable" src/storage/schema.ts | grep error步骤 1 有、步骤 2 没有 → 漂移确认,只需补 schema 声明,不需要新建 migration(列已经在 DB 里了)。
修复
// schema.ts(修复后)
runs: mysqlTable('runs', {
id: int('id').primaryKey(),
status: varchar('status', { length: 32 }),
error: text('error'), // 补上:之前被静默丢弃
startedAt: timestamp('started_at'), // 补上:之前被静默丢弃
finishedAt: timestamp('finished_at'),
})如果项目同时支持多个 driver(MySQL + SQLite + PostgreSQL),每个 schema 文件都要补。Drizzle 的静默丢弃行为在所有 driver 中一致。
往 mysqlTable / sqliteTable / pgTable 的列定义里加上缺失的列即可。Drizzle 从此会把这个 key 纳入 SET 子句,后续写入立即生效。历史行已经是 NULL 了,无法自动回填——那是丢失的数据。
长期预防
- 所有 schema 变更走
drizzle-kit generate,禁止裸ALTER TABLE - 每个 patch helper 写单测时断言目标列的值,不只断言"helper 没抛错"
- 有条件的话,在应用启动时对比
SHOW COLUMNS和 drizzle schema 的列集合,不一致就拒绝启动
一句话外卖
字段值永远是 NULL 但手动 SQL 能写进去?先查 drizzle schema,不是查代码逻辑。