perf(db): 收入口径回填改为分批脚本

迁移 104 里对 order_items 的全表 UPDATE 实测 34.4 秒(375,447 行),
远超默认语句超时 15s,单事务迁移会拖住启动甚至直接失败。

- 104 只保留 DDL 与索引/约束,删除全表 UPDATE;
- 新增 scripts/backfill-revenue-basis.ts:按 id 分批、幂等、支持
  --dry-run / --batch-size / --max-batches(kebab 与 camel 两种写法);
- package.json 增加 revenue:backfill;
- migrate.ts 启动时做一次带索引计数,仍有 unknown 时打印告警并给出命令,
  不阻塞启动;
- 文档补充回填步骤与「不执行的后果」。

验证:
- 实测 37.5 万行全表更新 34.4s → 脚本按批执行,单批 5000 行;
- 功能验证:单条订单项置 unknown → 脚本回填 → 金额与 basis 恢复正确;
- 启动告警验证:保留 1 条 unknown 重启 → 日志出现告警;回填后计数归零;
- 全新库从 102 迁移到 107 仍成功(2s)。
This commit is contained in:
yml2213
2026-10-25 12:02:26 +08:00
parent f10fefc303
commit ad1e1bc884
5 changed files with 256 additions and 29 deletions
+26
View File
@@ -58,6 +58,32 @@ export async function runDatabaseMigrations() {
} satisfies RunnerOption)
await syncLegacyMigrationColumns()
await warnPendingRevenueBackfill()
}
/**
* 迁移 104 只建列、不再做全表收入回填(37.5 万行实测约 34 秒,会拖住启动)。
* 这里只做一次带索引的计数,发现仍有未固化的订单项就提示执行回填脚本,
* 不阻塞启动——脚本幂等,可以随时补跑。
*/
async function warnPendingRevenueBackfill() {
try {
const { rows } = await query<{ count: number }>(
`SELECT COUNT(*)::int AS count FROM order_items WHERE revenue_basis = 'unknown'`,
)
const pending = rows[0]?.count ?? 0
if (pending > 0) {
console.warn(
`[db:migrate] 还有 ${pending} 个订单项未固化收入口径,销售事实会偏低;` +
'请执行 npm run revenue:backfill(分批、幂等)',
)
}
} catch (error) {
// 列还不存在(迁移没跑到 104)或表缺失时跳过,不能影响启动。
console.warn(
`[db:migrate] 跳过收入口径检查: ${error instanceof Error ? error.message : String(error)}`,
)
}
}
function assertMigrationFilesOrdered() {