13 KiB
生产 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:
shierfeng-fastapi-backend/sql/patches/20261008_prod_existing_tunnel_repair_mysql.sql:新增专用补丁,保留已存在隧道表,补12个关联字段、5个外键、可空产线、入隧事件和4行菜单。shierfeng-fastapi-backend/sql/patches/20261008_shipment_receipt_mysql.sql:复用现有补丁,补收货和在途状态;旧确认单新增LEGACY标识,不改变旧库存、批号、编号和数量。shierfeng-fastapi-backend/sql/patches/20261008_usage_fifo_mysql.sql:复用现有补丁,补可空原因及推荐快照。shierfeng-fastapi-backend/sql/checks/20261008_prod_material_flow_baseline.sql:只读,前后原字段快照。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 都必须保持同一套参数。不要改变项目名或数据卷名称。
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 和其他写入来源。
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 需有经批准的写入权限和足够磁盘空间;权限不足时停止,不自动改权限:
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节打印的目录;重新登录终端时由维护人员明确填入并核对,禁止猜测路径。
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、遇错继续的图形客户端及把三份脚本拼起来盲跑。建议在专用维护终端操作,保留退出状态。
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,无错误才执行:
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收货菜单;无错误才执行:
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. 验收与恢复服务
在启动应用前执行:
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。历史日期保持原值;新隧道、快照、收货时间等历史字段为空。
任何一项失败都不要启动。确认运行镜像已是这次适配的新版本后,再恢复原容器(不改变卷/配置、不拉取新镜像):
docker compose start shierfeng-backend-my
docker compose ps
docker compose logs --since=5m --tail=100 shierfeng-backend-my
确认后端健康、没有缺列/约束错误,再执行:
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节的独立恢复演练及生产审批。