Files
ShiErFeng/shierfeng-fastapi-backend/sql/production-compose-material-flow-migration-20261008.md
T

162 lines
13 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 生产 Compose 物料流转增量迁移
## 状态、授权与适用范围
本交付**只生成文件**,未连接任何数据库,未执行本地或生产迁移,未重启服务、构建镜像或改动 Compose/环境变量。下列命令供维护人员审核后在**生产服务器**执行,不要在本机开发目录运行。生成脚本的确认不等于已完成生产执行审批。
依据用户提供的生产 `shierfeng-fastapi` / MySQL 8.0.46 结构:
- `biz_test_batch`、`biz_tunnel` 已存在;隧道外键使用 ORM 自动名称,保留不改。
- `biz_filling_record.production_date/expiry_date` 及日期 CHECK 已存在,保留现有数据。
- 库存、使用、退回、事件表的隧道关联未升级,使用记录 `line_id` 仍非空。
- 收货字段、FIFO 原因/快照不存在;隧道及收货菜单尚不存在。
这是一条**特定起点、单次执行、遇错即停**的升级路线,不是通用幂等修复器。生产结构有变化必须重新评估,不能删掉检查后强行执行。
## 文件顺序
以下路径相对生产服务器项目根目录,必须带齐五份 SQL:
1. `shierfeng-fastapi-backend/sql/patches/20261008_prod_existing_tunnel_repair_mysql.sql`:新增专用补丁,保留已存在隧道表,补12个关联字段、5个外键、可空产线、入隧事件和4行菜单。
2. `shierfeng-fastapi-backend/sql/patches/20261008_shipment_receipt_mysql.sql`:复用现有补丁,补收货和在途状态;旧确认单新增 `LEGACY` 标识,不改变旧库存、批号、编号和数量。
3. `shierfeng-fastapi-backend/sql/patches/20261008_usage_fifo_mysql.sql`:复用现有补丁,补可空原因及推荐快照。
4. `shierfeng-fastapi-backend/sql/checks/20261008_prod_material_flow_baseline.sql`:只读,前后原字段快照。
5. `shierfeng-fastapi-backend/sql/checks/20261008_prod_material_flow_verify.sql`:只读,迁移后验收。
**不要执行**原隧道补丁 `20261005_tunnel_material_flow_mysql.sql`(会重复建表),不要重复日期/测试批次补丁,不要执行全量 `shierfeng-fastapi.sql`。不要把这些补丁挂到 MySQL 初始化目录,也不要删除/更换 MySQL 数据卷来修复。
## 1. 审核目标与维护窗口(尚不改库)
进入生产服务器上实际部署的 Compose 目录,使用原部署的 `-f`、`--env-file`、`-p` 参数(如有),下文每条 `docker compose` 都必须保持同一套参数。不要改变项目名或数据卷名称。
```bash
docker compose ps
docker compose exec shierfeng-backend-my python -c "import os; print({k: os.getenv(k) for k in ['APP_ENV', 'DB_HOST', 'DB_PORT', 'DB_DATABASE']})"
docker compose exec -T shierfeng-mysql sh -c 'MYSQL_PWD="$MYSQL_ROOT_PASSWORD" exec mysql --user=root --database="$MYSQL_DATABASE" --execute="SELECT DATABASE(), VERSION(), @@hostname, @@port;"'
```
要求后端 `DB_HOST=shierfeng-mysql`、`DB_DATABASE=shierfeng-fastapi`,与 MySQL 查询一致。若使用外部数据库、不同服务名、空输出或其他库名,停止并调整方案,不能把命令照搬到另一个库。
命令利用现有 MySQL 容器环境中的密码,不将密码放进命令参数、终端输出或仓库;不要启用 shell `set -x`,也不要贴完整 `.env`/`docker compose config`。缺少 `MYSQL_ROOT_PASSWORD` 时请 DBA 使用受控凭据,不要把密码改写进文档。
获得维护窗口、停写和生产迁移审批,确认备份保留位置、恢复负责人、可接受恢复时间。先备好一致的新代码版本及上一个可用版本;本操作不执行拉取或构建。
## 2. 停写、备份与原数据基线
以下开始影响生产服务,必须在审批后的维护窗口由维护人员执行。通知用户退出操作,停止所有外部导入、定时任务、独立 worker 和其他写入来源。
```bash
docker compose stop shierfeng-frontend shierfeng-backend-my
docker compose ps
docker compose exec -T shierfeng-mysql sh -c 'MYSQL_PWD="$MYSQL_ROOT_PASSWORD" exec mysql --user=root --database="$MYSQL_DATABASE" --execute="SELECT ID, USER, HOST, DB, COMMAND, TIME FROM information_schema.PROCESSLIST WHERE DB=DATABASE() AND ID<>CONNECTION_ID(); SELECT EVENT_NAME, STATUS FROM information_schema.EVENTS WHERE EVENT_SCHEMA=DATABASE() AND STATUS=\"ENABLED\"; SELECT TABLE_NAME, ENGINE FROM information_schema.TABLES WHERE TABLE_SCHEMA=DATABASE() AND TABLE_TYPE=\"BASE TABLE\" AND ENGINE<>\"InnoDB\";"'
```
不得继续:仍有业务连接/事务或启用的事件,或存在非 InnoDB 表。不要擅自 kill 会话、禁用事件或转换表;由 DBA 查明并停写。MySQL/Redis 保持运行,不执行全栈停止或删除卷。
在同一个 Bash 会话保存备份路径,`/var/backups` 需有经批准的写入权限和足够磁盘空间;权限不足时停止,不自动改权限:
```bash
set -euo pipefail
umask 077
prod_backup_dir=$(mktemp -d /var/backups/shierfeng-prod-20261008.XXXXXX)
printf '生产备份目录:%s\n' "$prod_backup_dir"
docker compose exec -T shierfeng-mysql sh -c 'MYSQL_PWD="$MYSQL_ROOT_PASSWORD" exec mysqldump --user=root --single-transaction --quick --routines --events --triggers --hex-blob --set-gtid-purged=OFF --no-tablespaces --column-statistics=0 --default-character-set=utf8mb4 "$MYSQL_DATABASE"' > "$prod_backup_dir/before.sql"
test -s "$prod_backup_dir/before.sql"
sha256sum "$prod_backup_dir/before.sql" > "$prod_backup_dir/before.sha256"
docker compose exec -T shierfeng-mysql sh -c 'MYSQL_PWD="$MYSQL_ROOT_PASSWORD" exec mysql --user=root --database="$MYSQL_DATABASE" --default-character-set=utf8mb4 --batch --raw --skip-column-names' < shierfeng-fastapi-backend/sql/checks/20261008_prod_material_flow_baseline.sql > "$prod_backup_dir/original-fields.before.tsv"
```
备份不使用 `--databases`,避免把原库名的 `CREATE DATABASE/USE` 写入文件,便于在另一个隔离实例恢复。备份覆盖全部表和触发器、过程、事件;快照只用于比较本次7张关联业务表的全部原字段,不能代替完整备份。空业务库的快照可能为空,但完整备份不能为空。任何命令失败都停止,不能拿半份文件继续。
## 3. 恢复演练门禁
将备份复制到受控的**独立验收 MySQL 实例**,不是本机开发库,也不是业务生产库。由 DBA 建立空的同名 `shierfeng-fastapi` 库(新补丁核对库名;实例必须隔离),恢复完整备份并核对所有表及原字段快照,再按第4节顺序试跑三份脚本和第5节验收。
不得为演练启用应用写入,需检查恢复的存储过程/事件/触发器及其 DEFINER;演练实例应禁用事件调度。本交付没有该隔离实例的地址及凭据,因此不预填可能误指向生产的恢复命令。需 DBA 留存恢复目标、命令、成功结果、原字段一致性、耗时和脚本 SHA-256。
**没有完成恢复及补丁试跑,就不能进入生产执行。** 文件非空、哈希正确或 SQL 静态检查通过不等于恢复成功。迁移期间持续停写;若中途恢复业务,原备份/基线不再代表执行起点,必须重新安排窗口和备份。
## 4. 生产逐份执行(仍保持停写)
重新确认当前终端属于生产服务器、Compose 实例与第1节一致、备份已验证可恢复、三个脚本与演练使用版本哈希相同。`prod_backup_dir` 必须仍是第2节打印的目录;重新登录终端时由维护人员明确填入并核对,禁止猜测路径。
```bash
test -d "$prod_backup_dir"
sha256sum -c "$prod_backup_dir/before.sha256"
sha256sum shierfeng-fastapi-backend/sql/patches/20261008_prod_existing_tunnel_repair_mysql.sql shierfeng-fastapi-backend/sql/patches/20261008_shipment_receipt_mysql.sql shierfeng-fastapi-backend/sql/patches/20261008_usage_fifo_mysql.sql
```
**每次只执行一份,检查退出码和日志后才进入下一份。** 下列块依赖第2节 `set -euo pipefail`,禁止 `--force`、遇错继续的图形客户端及把三份脚本拼起来盲跑。建议在专用维护终端操作,保留退出状态。
```bash
docker compose exec -T shierfeng-mysql sh -c 'MYSQL_PWD="$MYSQL_ROOT_PASSWORD" exec mysql --user=root --database="$MYSQL_DATABASE" --default-character-set=utf8mb4 --show-warnings --init-command="SET @shierfeng_prod_migration_approved=1"' < shierfeng-fastapi-backend/sql/patches/20261008_prod_existing_tunnel_repair_mysql.sql > "$prod_backup_dir/01-tunnel.log" 2>&1
```
第一份应输出12个新增隧道字段和1个 `line_id=YES`,以及4个隧道菜单。检查 `01-tunnel.log`,无错误才执行:
```bash
docker compose exec -T shierfeng-mysql sh -c 'MYSQL_PWD="$MYSQL_ROOT_PASSWORD" exec mysql --user=root --database="$MYSQL_DATABASE" --default-character-set=utf8mb4 --show-warnings' < shierfeng-fastapi-backend/sql/patches/20261008_shipment_receipt_mysql.sql > "$prod_backup_dir/02-receipt.log" 2>&1
```
检查 `02-receipt.log`:历史 `CONFIRMED` 单为 `LEGACY`、`DRAFT` 为 `PENDING`,新增1119收货菜单;无错误才执行:
```bash
docker compose exec -T shierfeng-mysql sh -c 'MYSQL_PWD="$MYSQL_ROOT_PASSWORD" exec mysql --user=root --database="$MYSQL_DATABASE" --default-character-set=utf8mb4 --show-warnings' < shierfeng-fastapi-backend/sql/patches/20261008_usage_fifo_mysql.sql > "$prod_backup_dir/03-fifo.log" 2>&1
```
第三份成功不一定有结果集,必须确认退出码0且日志无错误,再做完整验收。这三份脚本都不允许重复执行。
## 5. 验收与恢复服务
在启动应用前执行:
```bash
docker compose exec -T shierfeng-mysql sh -c 'MYSQL_PWD="$MYSQL_ROOT_PASSWORD" exec mysql --user=root --database="$MYSQL_DATABASE" --default-character-set=utf8mb4 --batch --raw --skip-column-names' < shierfeng-fastapi-backend/sql/checks/20261008_prod_material_flow_baseline.sql > "$prod_backup_dir/original-fields.after.tsv"
cmp "$prod_backup_dir/original-fields.before.tsv" "$prod_backup_dir/original-fields.after.tsv"
docker compose exec -T shierfeng-mysql sh -c 'MYSQL_PWD="$MYSQL_ROOT_PASSWORD" exec mysql --user=root --database="$MYSQL_DATABASE" --default-character-set=utf8mb4 --table' < shierfeng-fastapi-backend/sql/checks/20261008_prod_material_flow_verify.sql > "$prod_backup_dir/verify.log"
```
全部验收条件:
- `cmp` 退出0,七张表全部原字段逐行一致,包含库存、批号、日期和历史事件。
- 验收文件25行字段(含原日期和产线)均为 `OK`,6行外键均为 `OK`;不能只看 mysql 命令退出码,因为 SELECT 会正常返回 `MISSING/MISMATCH`。
- 三个 CHECK 均存在且 `ENFORCED=YES`;库存枚举含 `IN_TRANSIT`,事件枚举含 `TUNNEL_ENTERED`、`SHIPMENT_RECEIVED`;收货唯一索引 `non_unique=0` 且列顺序为 `target_site_id,receipt_idempotency_key`。
- 恰有菜单520/1116/1117/1118/1119,父ID为121/520/520/520/510,权限分别为 `master:tunnel:list/query/add/edit` 和 `business:shipment:receive`。
- `unexpected_*` 和 `remaining_migration_routines` 均为0。历史日期保持原值;新隧道、快照、收货时间等历史字段为空。
任何一项失败都不要启动。确认运行镜像已是这次适配的新版本后,再恢复原容器(不改变卷/配置、不拉取新镜像):
```bash
docker compose start shierfeng-backend-my
docker compose ps
docker compose logs --since=5m --tail=100 shierfeng-backend-my
```
确认后端健康、没有缺列/约束错误,再执行:
```bash
docker compose start shierfeng-frontend
docker compose ps
```
使用超级管理员重新登录检查库存/使用/退回报表及隧道资料页面。新菜单不会自动授予普通角色,需要按既有角色管理配置菜单和现场范围,另行审批;不要直接修改角色关联表或清空 Redis。新流程用明确授权的测试数据验收,不用正式库存随意试扣。
前述 Web Crypto/HTTPS 问题与本迁移无关;此交付不修改加密开关、证书或反向代理。
## 失败与恢复原则
- 保持后端及其他写入来源停止,保留备份、日志和实际已落库结构。
- MySQL DDL 会隐式提交,`ROLLBACK` 不能撤销前面已完成的 ALTER;不能盲目重跑、跳过错误或用反向删列 SQL“回滚”。
- 存储过程报错后可能保留临时过程名称,这是失败现场的一部分;不要直接 DROP 后重试,先由 DBA 确定失败阶段。
- 由 DBA 依据实际提交状态决定经过审核的续修,或从已验证完整备份恢复,并匹配已验证的应用版本。恢复目标、方式及可能覆盖的数据必须再次确认,文档不提供自动覆盖生产库命令。
- 不删除数据卷,不重跑初始化,不把开发库覆盖到生产;备份和快照需转存到受控备份系统,遵循保留期限,不提交仓库。
## 本次生成的验证边界
本轮仅执行不连接数据库的离线 SQL 契约检查和文件自检:6项契约检查通过,10段 Shell 命令经 `bash -n` 语法检查;非空历史字段、误写角色表、误删隧道表3种内存变异均被契约检查拒绝,未把变异写入文件。当前 SQLGlot 不能完整解析存储过程及部分 MySQL ALTER 语法,因此检查采用新增列/查询的 AST 加原 ALTER token 契约,不能称为完整 MySQL 语法验证。
没有执行 MySQL 存储过程、DDL、DML、备份恢复或生产验收;静态检查不能证明这些运行操作成功。MySQL/Compose 操作仍需第3节的独立恢复演练及生产审批。