---
date: 2026-05-02
title: Prisma 迁移 squash 落地的三个非显而易见点
tags: [prisma, migrations, squash, ffoa, deploy]
related-issues: [213]
related-prs: [222, 223]
---

## 背景

Issue #213 给 FFAI workspace 做迁移历史 squash（68 个旧迁移 → 1 个 baseline）。
真实落地时撞到三个不在 issue 文档里的坑。

## 1. `_archive/` 必须放在 `prisma/migrations/` 之外

第一直觉是把旧迁移移到 `prisma/migrations/_archive/`。**会炸**：

```
2 migrations found in prisma/migrations
Error: P3015
Could not find the migration file at migration.sql.
```

Prisma 一级 scan `prisma/migrations/<dir>/`，看到 `_archive/` 也当成迁移目录候选 →
里面没有 `migration.sql` → P3015。前缀下划线、点号、双下划线都不豁免。

**修法**：移到 `prisma/_migrations_archive/`（migrations 同级），Prisma 不扫。
仓库可读性等价，git 仍然识别为 rename。

## 2. UAT/PRD 的 `_prisma_migrations` 几乎一定有"漂移"

实测 develop 当前 archive 是 68 个 distinct migration，但：
- UAT：74 总行 / 66 distinct，4 类有重复（`fix_performance_schema_drift × 6`）
- PRD：67 总行 / 66 distinct，1 类重复（`cleanup_deprecated_models × 2`）

成因：项目历史上靠 `db push` 同步 schema、`migrate resolve --applied` 标记历史
（CLAUDE.md 写明的回避用法），多次 resolve 同一名字会留下重复行。develop
还可能后来删过某些 mig 文件（如 `cleanup_deprecated_models`），但环境上的应用
记录留着。所以**不能用 `count = N` 当 squash 的硬 guard**。

```sql
-- ❌ 严格匹配，UAT/PRD 直接拒
IF total_rows <> 68 THEN RAISE EXCEPTION ...;

-- ✅ drift-tolerant
IF baseline_squash 已存在 THEN abort;     -- 防重复执行
IF total_rows = 0       THEN abort;       -- 防空表
-- 不限制 total / distinct
```

squash 本质是**元数据替换**：DELETE all + INSERT baseline。它**不依赖**历史
metadata 行数；schema 真实状态由独立的 `db push` 维护。所以 guard 只防"已 squash"
和"环境本身有问题"两件事就够了。

## 3. PRD 写操作走 SSH stdin 流，不在远端落盘文件

CLAUDE.md 生产铁律说"禁止写文件"。但 squash 又要执行 SQL。
解法：本地 SSH 时把 SQL 走 stdin 流入远端 docker exec 的 psql：

```bash
ssh srvadmin@43.130.6.44 \
  "docker exec -i ffoa-pro-postgres psql -U ffws_pro -d ffws_pro -v ON_ERROR_STOP=1" \
  < scripts/ops/squash/apply-squash.sql
```

`-i` 让 docker exec 接受 stdin，外层 ssh 把本地文件当 stdin 推进去。**远端
没有任何文件落盘**，但 psql 完整执行了 SQL（含 `DO $$ ... $$;` 多行块）。

备份用反向：远端 pg_dump 到 stdout → ssh 流回 → scp 拉一份本地副本：

```bash
ssh srv... "docker exec ffoa-pro-postgres pg_dump ... -t _prisma_migrations --data-only --inserts > ~/backup-...sql"
scp srv...:~/backup-...sql /tmp/  # 本地多备一份，不依赖远端持续可达
```

## 验证

UAT (74 → 1) + PRD (67 → 1) 两边 squash 后：
- `prisma migrate status` 见 1 条已应用 baseline
- 应用 uptime 不变（PM2 没重启），健康检查 200
- 业务表抽样读取正常

零业务影响。

## 触发条件 / 适用范围

下次有任何"重写 `_prisma_migrations` 元数据"的运维操作，三条都用得上：
- _archive 位置坑
- drift-tolerant guard
- SSH stdin 流（任何禁止落盘的远端环境通用，不限 PRD）
