---
date: 2026-05-19
type: error
tags: [prisma, db-push, p1014, multi-schema, schema-rewrite, test-env, deploy]
---

# `prisma db push` 在整模块 schema 大重构时报 P1014（即使没人引用待删表）

## 现象

robot-manager v3 整体重构（commit c5a5c83a）合进 develop 后，deploy-test workflow run 4517 在 `npm run db:push` 这步挂掉。

第一次失败（log line 161-171）—— `--accept-data-loss` 没传：

```
⚠️  There might be data loss when applying the changes:
  • RobotLifecycleStage enum 要移除值 [ORDERED,IN_TRANSIT,BONDED,...]
  • You are about to drop the `robot_field_defs` table, which is not empty (34 rows).
  • robot_units 要加 (organization_id, ffsn) 唯一约束
Error: Use the --accept-data-loss flag ...
```

直接在 test 服务器手动跑 `prisma db push --accept-data-loss`，本以为能过，结果换报：

```
Error: P1014
The underlying table for model `robot_manager.robot_unit_snapshots` does not exist.
```

`robot_unit_snapshots` 是新 schema 里新增的 model（v3 重构把 RobotUnit 拆成"不变属性 + Snapshot 当前状态物化"，CQRS read side）。**它本来就该被 db push 创建**，prisma 却在动手前先做 model↔table 对应校验，发现它在 DB 没对应表就直接 abort。

## 根因（候选）

`prisma db push` 在 `previewFeatures = ["multiSchema"]` + 目标 PG schema **已有部分老表**（不一致状态）时，diff 算法的某个 introspect 阶段把"待创建的新 model"也当成"应当已存在"来校验。具体触发条件未深挖，但实测：

- DB 的 `robot_manager` schema 完全空 → db push 顺利从零建出所有表
- DB 的 `robot_manager` schema 部分老表（schema 跟 prisma model 集差异大）→ P1014

可能跟新 model 有 1:1 `@relation` 引用老 model（`RobotUnitSnapshot.robotUnit → RobotUnit`）触发的关系完整性预校验有关，待复现确认。

## 解法（test 环境 / 数据可丢）

```bash
# 1. 在 test 服务器上 nuke 整个模块 schema，CASCADE 带掉所有老表和老 enum
docker exec ffws-test-postgres psql -U ffws_test -d ffws_test -c \
  "DROP SCHEMA robot_manager CASCADE; CREATE SCHEMA robot_manager;"

# 2. 重跑 db push --accept-data-loss——空 schema，prisma 从零建，秒过
cd /srv/apps/ffworkspace-test/backend
npx --yes dotenv -e .env -- prisma db push --accept-data-loss
# 🚀 Your database is now in sync with your Prisma schema. Done in 1.46s

# 3. rerun workflow run
curl -X POST -H "Authorization: token $GITEA_API_TOKEN" \
  http://43.130.59.228/api/v1/repos/FFAIWorkspace/workspace/actions/runs/<run_id>/rerun
```

## 解法（UAT/生产环境 / 数据不可丢）

**绝对不能** drop schema。必须走标准 migration：

1. 在主仓库本地新建一条 prisma migration（`prisma migrate dev --name robot_manager_v3`）
2. 检查生成的 SQL，对 enum 替换补 `USING old_col::text::new_enum_type` cast；对老数据搬迁/清理写显式 DML
3. 走 `staging → production` FF-only promote，让 `migrate deploy` apply

UAT/生产从不该跑 `db push`（CLAUDE.md 已明令）。

## 工程化保险

1. **deploy-test.yml 的 `npm run db:push` 加 `--accept-data-loss`**——workflow 注释明说"test 数据可丢"，但命令没传 flag，下次任何 destructive change 又会卡同样的位置。后续阶段 2 单独 PR 修。
2. **整模块 schema 大重构（drop 多张老表 + 大批新 model）merge develop 前，预先 SSH 上 test 做 DROP SCHEMA + CREATE**——比等 CI 失败再去救快得多。CLAUDE.md 第 13 条数据分层调整时尤其要注意（剥状态字段 → 新 Snapshot 表是典型场景）。
3. **未来若反复遇到 P1014**，考虑写一个 `db:push:nuke` 脚本（仅在 NODE_ENV=test 时允许）封装"列出 dirty schema → DROP CASCADE → CREATE → db push"流程。当前一次性故障不值得抽象。

## 教训

1. **`prisma db push --accept-data-loss` 不是万能** —— 它能放过"删表有数据"和"删 enum 值无引用"这两类警告，但 P1014（model↔table 对应校验）发生在 `db push` 主流程**之前**，flag 救不了。
2. **multiSchema + 大重构 = `db push` 行为脆弱** —— 不一致中间状态下，prisma 内部预校验可能比想象的严格。test 环境碰到了优先 nuke 模块 schema 而不是逐个调试。
3. **workflow 注释承诺 vs 命令行为要对齐** —— deploy-test.yml 的注释"test 数据可丢"是设计意图，但实际命令 `prisma db push` 默认拒绝 data loss，导致每次大重构都要人肉救。Pre-commit 或 CI 应该校验"注释承诺 ↔ 实际 flag"对齐。

## 关联

- [ERR-20260518-002-prisma-migrate-dev-drift-pollution.md](ERR-20260518-002-prisma-migrate-dev-drift-pollution.md) —— prisma 在不一致状态下的另一类陷阱（migrate dev 把无关漂移打包进 feature migration）。两者根因同一类：prisma diff 引擎对 actual DB state 假设过强。
- CLAUDE.md 第 13 条「数据分层与元数据策略」—— 本次 robot-manager v3 的 schema 大动正是按此条做（剥 L2 状态字段到 Snapshot + 删 L4 FieldDef），未来类似改造会重复遇到。
- `docs/ops/01-server-infrastructure.md` §2.1 Test 服务器配置 —— `db push` 是 test 环境标准做法的明文出处。
- 触发 commit：`c5a5c83a feat(robot-manager): v3 28-stage 整体重构 + 配套 6 主题（67 commits epic）`
- 失败 workflow run：http://43.130.59.228/FFAIWorkspace/workspace/actions/runs/4517
