Files
docs/数据库版本迁移与Jenkins自动发布方案.md
T
2026-09-23 17:47:52 +08:00

782 lines
68 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.
# 数据库版本迁移与 Jenkins 自动发布方案
## 1. 文档状态与目标
- 状态:已确认采用标准迁移机制与两人团队简化流程;阶段 B 的范围映射、自动编号和离线检查工具已在测试分支实现,Flyway 执行和 Jenkins 流水线尚未启用。
- 适用环境:开发环境、共享测试环境、国内生产环境、海外生产环境;各环境执行方式见第 10 节。
- 适用数据库:MySQL 8。
- 迁移工具:Flyway。
- 发布入口:Jenkins。
目标是让每次上线自动执行当前代码版本所需的数据库变更,人工只负责代码评审、发布审批和上线验收,不再登录服务器手工执行 SQL。
本方案只定义后续版本迁移机制,不重新执行现有初始化 SQL,不授权重置、清空或修复任何环境的数据。
### 1.1 生效边界
- 当前仍按未上线阶段维护最新初始化 SQL,遵循 [AI 工程执行与协作约束](AI工程执行与协作约束.md) 和 [区域数据库初始化说明](../baiyue-firstleg/deploy/sql/README.md)。本方案不要求现在为可丢弃的开发、测试存量补迁移或保留兼容实现。
- 用户明确确认需要保留的生产存量或首次上线范围后,再评审冻结基线、迁移机制启用日期和环境清单;同步调整相关工程指引,不能只靠本方案覆盖现行规则。
- 以下不可变迁移、生产兼容与共享测试演练要求,从上述启用点生效。文档修改不代表已实施、已部署或已完成环境验收。
### 1.2 开发便利性评审结论
- 当前未上线阶段不增加迁移编号,继续维护模块最终结构和区域初始化 SQL;不为可丢弃存量补升级链。
- 迁移机制启用后,取消 SQL 编号与业务发布版本的绑定,不另设需要人工递增的 `VERSION` 文件。一次数据库逻辑变更由工具自动生成一个单库序号,普通代码提交、重复构建、重复部署不增加 SQL 编号。
- 同一项变更在尚未进入任何保留历史的环境、尚未封装为正式候选制品前,可以持续修改同一文件;完成开发后再冻结。成功执行的文件以新增迁移修正;从未成功且未进入生产的失败候选仅可按第 13.2.1 节受控修订,原制品始终不可覆盖。
- 开发、测试、生产复用同一迁移文件身份与内容,环境之间只替换连接和凭据。测试通过的候选制品直接晋级生产,不重新生成 SQL、重排编号或重新构建镜像。
- 工具负责编号、路径、哈希、待执行列表与逐库目标;开发负责 SQL 语义、必要数据修正、依赖和前后断言。第一阶段不引入自动推断数据库差异并直接应用的工具,避免把改名误判为删列重建。
### 1.3 两人团队的最小工作流
日常只保留三个动作:开发新建并填写变更、另一人随代码检查、触发部署。没有数据库变化时直接走普通部署,不新建 SQL 文件或数据库版本。
| 参与者 | 需要处理的事项 | 自动完成的事项 |
|---|---|---|
| 变更开发者 | 输入所属库与变更说明,填写 SQL,说明预期结果 | 生成文件与编号,检查命名、库归属、历史不可变性 |
| 另一名开发者 | 在现有代码评审中检查 SQL、数据影响及兼容性 | 生成受影响服务清单、验证结果和发布摘要,不再另填发布 JSON |
| 发布执行人(可由上述任一人承担) | 测试点击部署;生产选择已通过测试的制品并完成现有发布确认 | 数据库预检、执行待执行迁移、复验、部署镜像、健康检查和归档 |
第一阶段复用现有 Git、Jenkins、Credentials、锁资源和 Flyway 历史表;当前已先落地一个薄的离线命令入口,后续再接必要流水线阶段。候选记录使用 Jenkins 归档和镜像 digest,不建设独立发布平台、编号服务、审批系统或数据库结构自动同步引擎。
**重复执行的是部署入口。已成功的 SQL 自动跳过;数据库有新变化时才新增迁移。** 不要求所有 DDL/DML 写成可无限重放的通用脚本,也不采用“开发测试覆盖 SQL、生产另生成不同升级逻辑”的双轨方式。
自动执行指已授权的正常发布全过程无需人工登录服务器执行 SQL;保留现有生产发布确认。SQL 执行失败、部分 DDL 已提交或结构漂移属于异常处理,不承诺换版本号后自动消除所有问题。
## 2. 核心结论
1. 现有两份统一 SQL 只作为空库初始化基线,不能在已有数据库重复执行。
2. 机制启用后,按数据库逻辑变更自动编号;候选制品及成功迁移历史不可覆盖。从未成功且未进入生产的失败候选,按第 13.2.1 节核验后可修订源码并生成新制品,不改写旧制品或成功记录。
3. 国内和海外使用独立目录、独立数据库连接、独立 Flyway 历史表,禁止跨区域或跨 schema 混合执行。
4. Jenkins 每次正常发布都核对发布清单并执行迁移前校验、迁移和严格复验;前后校验配置不同,不能仅依赖 Git diff。
5. 发布顺序为:构建并推送全部目标镜像 -> 获取环境发布锁并审批 -> 执行数据库迁移 -> 部署同一批镜像 -> 健康检查和人工验收。
6. 纳管测试环境允许频繁发版,但成功执行的迁移同样不可修改;应用回滚走独立兼容校验流程,不重跑旧版本数据库发布链。
7. 测试重置是独立任务,生产环境永远不允许自动 `clean`、重建或回滚数据库。
## 3. 当前迁移起点
### 3.1 历史基线记录,待核实
旧稿记录以下文件曾用于生产初始化;本轮未连接环境或取得执行留档,不能将该记录作为已确认的生产事实:
- 国内:`头程数据库统一增量-MySQL8.sql`
- 海外:`海外区域统一增量-MySQL8.sql`
旧稿中的验收记录:
- 国内初始化通过,共 208 张表。
- 海外初始化通过,共 137 张表。
旧稿记载的执行文件 SHA-256(仅用于查找历史留档):
- 国内:`27fbb98c603f7be1681eb4672b0e8d3010944e339fb64c0dfc00fa89b99ef036`
- 海外:`df91d199b84d02509cbe1f9a3a50a44e209e8c746a94ca40da7db32858d6d3a5`
2026-09-18 文档评审时,本地两份同名文件的哈希均不同于上述记录,当前初始化说明列出的表数为国内 208、海外 138。这只说明版本不同,不证明任何环境当前处于哪一版;表数相同也不能证明结构相同。
- 若尚无须保留的生产存量:在首次上线前冻结经过验收的最新初始化包,不必为匹配旧稿哈希回退当前结构。
- 若确认已有须保留的存量:找回实际执行文件及后续变更记录,核验哈希并对照数据库实态。无法证明匹配时停止基线登记,单独评审接入差异,不能用本地文件或表数代替证据。
- 每个环境的实例身份、基线包哈希、代码提交、核对报告、确认人和时间写入受控发布记录;地址、凭据及真实业务数据不写入本方案。
### 3.2 国内数据库边界
国内区域当前包含:
- `baiyue_auth`
- `baiyue_iam`
- `baiyue_order`
- `baiyue_warehouse`
- `baiyue_firstleg`
- `baiyue_file`
- `baiyue_audit`
- `baiyue_event_bridge`
- `baiyue_finance`
### 3.3 海外数据库边界
海外区域当前包含:
- `baiyue_lastleg`
- `baiyue_lastlag_warehouse`
- `baiyue_event_bridge_overseas`
`baiyue_lastlag_warehouse` 的现有拼写视为当前数据库事实。若后续决定改名,必须作为单独的受控迁移处理,不能在引入 Flyway 时顺带修改。
## 4. 推荐目录结构
```text
baiyue-firstleg/
└─ deploy/
└─ sql/
├─ README.md
├─ migration-scopes.json
├─ baseline/
│ ├─ manifest.json
│ ├─ cn/
│ │ └─ cn-baseline.sql
│ └─ overseas/
│ └─ overseas-baseline.sql
├─ migrations/
│ ├─ cn/
│ │ ├─ baiyue_auth/
│ │ ├─ baiyue_iam/
│ │ ├─ baiyue_order/
│ │ ├─ baiyue_warehouse/
│ │ ├─ baiyue_firstleg/
│ │ ├─ baiyue_file/
│ │ ├─ baiyue_audit/
│ │ ├─ baiyue_event_bridge/
│ │ └─ baiyue_finance/
│ └─ overseas/
│ ├─ baiyue_lastleg/
│ ├─ baiyue_lastlag_warehouse/
│ └─ baiyue_event_bridge_overseas/
└─ scripts/
└─ db-migrations.mjs
```
目录已在测试分支建立基础骨架;基线包登记、Flyway 执行和流水线接入仍按阶段 A/C 实施。约束:
- 一个迁移文件只能修改一个 schema。
- 每个 schema 使用自己的 `flyway_schema_history`。
- 区域初始化包独立执行,不使用 `V...` 文件名,不加入 Flyway 的 `locations`;每次 Flyway 调用只指向一个 schema 的迁移目录与历史表。
- 国内任务不能获得海外数据库连接,海外任务不能获得国内数据库连接。
- 不维护手工 SQL 发布版本文件或每次发布必填的计划 JSON。`migration-scopes.json` 是低频维护的区域、schema、所属服务和迁移顺序映射,不含地址或凭据;增加/移动服务或改变依赖时才调整。
- 单一工具入口 `deploy/sql/scripts/db-migrations.mjs` 提供 `new/check/plan/run` 四个子命令,打包及清单生成由流水线调用内部能力,不要求开发依次手跑多套脚本。当前 `new/check/plan` 已可离线执行,`run` 只允许 `--dry-run`;真实 Flyway 执行进入阶段 C。
- 服务模块下的 `resources/db/` 可以继续作为最终结构或开发参考,但上线迁移的唯一事实来源必须是 `deploy/sql/migrations/`。
- `baseline/manifest.json` 记录区域初始化包的 SHA-256、schema 清单、结构及必要种子的核对规则;制品内 `release-manifest.json` 绑定本次发布范围、逐库目标及应用兼容条件,具体字段见第 6 节。
## 5. 基线规则
### 5.1 新建空库
第一阶段采用“独立区域初始化 + 逐库登记”的方式,不拆分现有跨 schema 初始化包:
1. 经目标环境及空库初始化授权后,检查区域全部目标 schema 不存在或为空,校验冻结包哈希。
2. 使用独立初始化入口和临时初始化权限,完整执行本区域 SQL。国内管理员密码哈希通过同一连接安全注入,按初始化说明验证退出码和最终结果。
3. 对区域全部 schema 验证结构与必要种子。全部成功后,逐库显式执行 `baseline`,指定 `baselineVersion=0.0.0`。
4. 确认每个 schema 的历史表登记完成,再按发布清单执行版本迁移。正常发布凭据不拥有初始化和重建权限。
初始化中途失败时停止,不登记任何尚未核验的库,不在半初始化库重放整包。清理或重建须另行获得精确授权。若初始化已全部验证成功、仅部分历史表登记失败,恢复时重新核对实态与已有登记,只补登记缺失的库,不重复初始化。
### 5.2 已初始化数据库
仅对已证明确实匹配所选基线的已有库做一次受控登记:
1. 备份数据库并确认可恢复。
2. 在暂停结构及系统种子变更的窗口,对照基线核对全部表、字段类型/默认值/可空性、索引、外键、CHECK、触发器、视图或过程(如有)及必要系统种子。业务数据值、运行态自增计数等明确排除项另列,不用表数或抽查替代完整核对。
3. 复核基线文件 SHA-256。
4. 逐库显式指定 `baselineVersion=0.0.0` 登记,保存数据库实态核对报告与基线包哈希。已有历史表时核对其记录,禁止覆盖或重复登记。
5. 从各库实际存在的首个大于 `0.0.0` 的迁移开始执行;拟建规则使用整数 `V1`、`V2`,尚无变更的库保持基线版本。
严禁为了建立历史表而重新执行完整初始化 SQL。
`baseline` 只登记版本,不证明数据库实态等于 SQL,也不替代初始化包 SHA-256 校验。正常发布固定 `baselineOnMigrate=false`,禁止自动接受未知的非空库。结构或种子有差异时阻断登记;可丢弃测试库可另行授权重建,须保留的存量单独评审接入方案。
### 5.3 基线不可变
机制启用并冻结基线包后:
- 禁止编辑、覆盖、重新生成或重命名。
- 若发现基线缺陷,在已经正确完成初始化及登记的前提下新增版本迁移修正;基线本身无法完整初始化时阻断启用,不能指望后续迁移修复未完成的初始化。
- 基线文件与其 SHA-256 必须永久留档。
## 6. 自动编号和文件命名
2026-09-22 确认:SQL 迁移保持 `V1、V2、V3、V4、V5……`,每个 schema 独立递增,由创建工具生成,不采用时间戳编号。序号随变更自然增长,不因位数增加而重置、归零或重新编号。生产发布版本、Git commit 和 Jenkins 构建号只用于发布追踪,不参与 SQL 序号计算。
```text
migrations/cn/baiyue_order/V1__add_order_reference.sql
migrations/cn/baiyue_order/V2__add_order_reference_index.sql
migrations/cn/baiyue_auth/V1__create_login_audit_table.sql
```
规则:
- 唯一键为“区域 + schema + 整数序号”,各 schema 独立递增,允许同序号;基线登记仍为 `0.0.0`,首个整数迁移为 `1`。
- 拟建工具的 `new` 子命令只需输入 schema 和动作说明;区域由固定映射确定,读取该库迁移目录后自动分配最大序号加 1,生成 UTF-8 模板,不连接数据库、不自动执行 SQL。文件名使用 `V[1-9][0-9]*__描述.sql`,描述只含小写 ASCII 字母、数字和下划线。
- 单工作区使用排他创建防止覆盖;两人修改同一库时,提交/合并检查重复版本,发生冲突仅对未冻结、未在保留历史环境执行的草稿重新编号并验证。成功迁移保留原文件,已冻结候选通过 Git 提交和受保护制品保留原文;除第 13.2.1 节记录的失败候选修订外,流水线拒绝历史文件变化。不增加独立的全局编号预留系统。
- 一个文件表达一个可独立核验的单库逻辑变更,可以包含多个相关语句;不按每个字段、每次保存或每次部署拆文件,也不把无关模块累积成一个无限增长的文件。
- 反复调整尚未冻结的同一需求,可以修改同一文件,在获授权的一次性数据库上从起点重新验证。保留数据的开发库已成功执行该文件时,同样必须新增修正,不能删除历史表或自动 repair 来掩盖差异;失败未完成按第 13 节处理。
- 制品冻结后保留原始内容及序号;常规修正生成新迁移和新候选。满足第 13.2.1 节的失败候选允许沿用同一迁移序号修订源码,但必须产生新的 Git commit、SQL 包哈希和候选身份。旧候选撤销晋级资格并留档,不在当前迁移目录同时放入同序号的两个文件,也不通过新增更高版本掩盖仍会阻断执行的错误文件。
- 无 SQL 变化不生成空迁移、不推进数据库版本;废弃编号不分配给其他业务变更,序号存在空档不代表执行遗漏。
整数序号及其顺序符合 Flyway 的版本规则;自动创建可复用所选版本的 `add` 能力或通过轻量本地工具实现,不依赖人工递增业务版本。[官方版本规则](https://documentation.red-gate.com/flyway/flyway-concepts/migrations/versioned-migrations)、[官方 Add 命令](https://documentation.red-gate.com/flyway/reference/commands/add)。
### 6.1 发布清单与逐库目标
开发无需手写发布清单。制品内 `release-manifest.json` 由工具依据同一提交中的固定范围映射、迁移文件和已构建镜像生成,流水线展示并核验:
- 发布范围、参与区域、全部受影响服务以及其 schema 依赖;共享种子或结构变化必须覆盖受影响的未选中服务,不能只按代码 diff 选部署服务。
- 每个参与区域的完整 schema 清单用于归属核对,另列本次允许迁移的 schema 清单。只有后者包含执行顺序、迁移目录、基线包身份、目标迁移版本、完整有序迁移文件列表及 SHA-256;目标等于该库随包携带的最大迁移版本,无版本迁移则为 `0.0.0`。范围外的库不设置本次迁移目标,其源码最新版本不能成为执行依据。
- 每项变化的类型、前后检查和是否兼容当前运行版本。模板将简短说明放在 SQL 文件头,普通新增使用统一超时及资源阈值;大表 DDL、数据修正和 Contract 另在代码评审中补充专项执行说明及机器可执行断言。
- 本次上线镜像与允许回滚镜像的兼容依据及评审记录。普通新增明确验证当前生产镜像继续可用即可;Contract 必须更新允许回滚版本,不能沿用收缩前的旧镜像。
构建阶段生成不可变发布制品,绑定源代码完整 commit、清单及 SQL 包 SHA-256、服务镜像 digest、Flyway 镜像版本及 digest。部署阶段只读取该制品,不再次从分支最新状态取 SQL;镜像标签不得覆盖,同一构建重跑必须核对 digest。
文件清单、SHA-256、逐库目标与当前制品标识自动推导,不要求开发重复手填。发布范围由固定映射和当前发布服务计算;跨服务影响、执行依赖和兼容依据仍由两人在现有代码评审中核对,映射不完整时阻断而非猜测。生成结果绑定映射文件哈希及评审提交,在正式测试部署前冻结,并随 Jenkins 制品归档。
例如本次允许迁移 Auth、Order,则 Auth 目标为 `12`,无变化的 Order 仍为 `8`;允许迁移但尚无迁移文件的库目标为 `0.0.0`。范围外的 Firstleg 即使在同一提交中存在新 SQL,也不设置本次目标、不随包进入可执行目录。同一候选从测试晋级生产,这些范围、目标及文件哈希保持相同。验收逐库对照清单,不要求所有历史表等于应用发布版本。
### 6.2 单服务发布的数据库范围
- 本次允许迁移的库由选中服务及评审确认的必要依赖确定;关联库没有新 SQL 时仍核验已有历史和应用要求。区域完整清单仅用于归属核对,不代表整个区域获得迁移资格。
- 打包只包含允许迁移库的完整有序迁移链;执行器按清单白名单逐库调用,不扫描区域目录后自动执行全部库。范围外的库不调用 `migrate/baseline/repair/clean`,不创建历史表;如需读取其状态,只用于依赖兼容检查,不能据此自动追加迁移。
- 发现必要数据库或服务不在已评审范围、依赖映射缺失或当前状态不兼容时,发布前阻断并列明缺项;调整范围后重新生成、测试候选,不能由执行器临时扩大范围。受影响但无需部署的服务也要记录兼容依据。
- 同一库内按完整版本链推进,不按某个服务或某个文件跳过中间版本。该库随包包含的其他待执行变更必须一并评审;尚不具备发布条件时阻断本候选,不通过删文件、临时降低目标或开启乱序绕过。
- 例如只发布 Auth,且与 Firstleg 无依赖,则只核验并迁移 Auth 的必要库。同一提交中 Firstleg 的待执行 SQL 不被带入;若 Auth 确实依赖 Firstleg 的新结构,则先补齐发布范围和测试证据再发布。
### 6.3 功能分支与生产发布编号
以下为确认的发布工作流;跨分支检查和发布编号自动分配尚待流水线实现,不代表当前工具已经提供这些门禁。
| 记录 | 示例 | 增长规则 |
|---|---|---|
| 单库 SQL 迁移编号 | Auth 的 `V1`、`V2`;Order 的 `V1`、`V2` | 该库新增数据库逻辑变更时自动生成;普通提交、重复构建和重复部署不增加 |
| 生产发布版本 | `release-1`、`release-2` | Jenkins 为新的生产发布候选自动分配,不要求开发手工维护版本文件;只有发布成功才更新当前生产版本 |
生产发布版本与 SQL 编号不绑定。例如 `release-12` 可以对应 Auth `V3`、Order `V7`;纯代码发布仍有新发布版本,但数据库目标不变。发布编号须在受保护记录中唯一分配,失败编号不回收;同一制品重试沿用原发布编号,修改代码、SQL 或其他制品内容后分配新编号并重新验证。`release-13` 失败时保留失败记录,当前生产版本仍为最近成功版本;各库和服务的实际状态另行记录,不能据此假定没有发生部分更新。
1. 功能分支从最新 `main` 创建,代码与 SQL 在同一分支维护;这里约定后续团队流程,不自动创建或切换本次工作区分支。
2. 进入共享测试前,拉取并合并最新 `main`,检查同库重复编号和执行顺序。冲突只调整尚未冻结、未在保留历史环境执行的草稿;成功文件不改内容、不改编号,修复使用新增迁移。冻结失败候选只能按第 13.2.1 节处理。
3. 两人可以并行开发;共享测试同一时间只验收一个待合并功能。前一功能测试通过、合并并完成发布或处理失败后,下一功能同步最新 `main` 再进入测试,避免未合并 SQL 污染其他功能的验证起点。
4. 测试结果绑定提交和 SQL 哈希;合并前重新检查 `main` 是否变化,变化时同步并重新验证,不能仅凭“以前拉取过 main”放行。
5. 合并后的 `main` 构建正式候选并再次自动验证;生产使用该正式候选原制品。功能分支测试证据不自动等同于合并后候选证据;已成功的 SQL 保持原编号及内容,由数据库历史决定跳过或执行。
6. 发布记录绑定 `release-N`、`main` 的完整提交、镜像 digest、SQL 清单及各库版本,并保存成功发布的 Git 标签。`main` 表示已合并代码,实际生产版本以成功发布记录为准,不通过分支名称推断数据库历史。
## 7. 迁移类型
### 7.1 版本迁移
业务表结构和受控数据变更统一使用 `V...__...sql`:
- 每个版本只执行一次。
- 执行后保存校验和。
- 成功执行的文件不得修改;失败文件和部分执行残留按第 13 节处理。
### 7.2 可重复迁移
第一阶段不启用 `R__...sql`。视图、存储过程等确有需要时再单独评审引入,因为内容变化会触发再次执行,评审和回滚要求更高。
测试与生产均用同一套版本迁移及待执行判断。重复点击部署不会重复运行成功 SQL;不通过时间戳占位符强制所有 SQL 每次执行,也不要求为每个普通 ALTER 添加“存在就跳过”的分支。需要可续跑的数据任务另行设计幂等条件,不能把“对象存在”当作结构正确。
### 7.3 禁止内容
自动迁移文件中禁止:
- `DROP DATABASE`、`CREATE DATABASE`、`USE` 或访问非所属 schema 的限定名。
- 无保护的 `DROP TABLE`、`TRUNCATE` 或全表删除。
- 密码、Token、云账号密钥和真实客户数据。
- 测试造数、开发演示数据或测试环境专用数据。
- MySQL 客户端的 `--force` 或忽略错误逻辑。
- 在单个文件中同时修改多个 schema。
- 依赖当前时间或不稳定查询结果生成不可重复状态。
## 8. 生产兼容策略:Expand / Contract
本节在需要保留生产存量并启用迁移机制后适用,不要求当前开发阶段增加兼容代码。数据库迁移通常不能像应用镜像一样直接回滚,发布前执行的变更必须兼容当时仍在运行的应用。
### 8.1 Expand:先扩展
允许自动执行的典型变化:
- 新增表。
- 新增允许为空或带安全默认值的字段。
- 新增索引,但需要提前评估锁表时间和磁盘空间。
- 新增不破坏旧代码的枚举值或配置数据。
应用发布前,新旧应用版本都必须能够在扩展后的结构上运行。
“新增”不自动代表安全:新枚举值不能提前被旧代码无法识别的路径读取,非空默认值须满足业务语义。索引及 ALTER 必须在固定 MySQL 小版本和代表性数据量下验证锁等待、执行耗时及磁盘余量,明确超限处置;不能将 Jenkins 超时等同于数据库语句已经停止。
### 8.2 Migrate:迁移或回填
大量历史数据回填不应塞进普通启动迁移:
- 小规模、可快速完成且可验证的数据修正可以使用版本迁移。
- 大规模回填必须使用独立、可观测、可限速、可续跑的任务。
- 回填完成前不能删除旧字段或旧索引。
### 8.3 Contract:最后收缩
删除字段、改字段类型、收紧非空、重建大索引等破坏性操作,必须在旧代码完全下线并经过观察期后,于后续版本单独执行。
第一阶段普通自动发布只接入已证明兼容的 Expand 和小规模受控数据变更;Contract 使用独立评审的发布批次,在明确当前运行版本、消费者和允许回滚版本均满足前置条件后实施,不通过普通发布参数绕过门禁。
因此应用回滚通常只回滚镜像,不自动回滚已经成功执行的扩展型数据库迁移。
## 9. Jenkins 自动发布流程
### 9.1 总体顺序
```text
合并到 main
-> 静态检查与测试
-> 构建全部目标服务镜像
-> 推送不可变 commit 标签
-> 固定发布制品与清单
-> 测试执行同一候选制品并记录验收证据
-> 选择已通过测试的制品晋级生产
-> 获取目标环境发布锁并核对审批
-> 数据库迁移预检
-> 国内/海外迁移
-> 数据库迁移复验
-> 部署同一批预构建镜像
-> 健康检查
-> 人工业务验收
-> 记录发布结论并释放锁
```
数据库迁移失败时,应用部署必须停止。不能出现部分服务已经上线、随后才发现数据库迁移失败的情况。
上图是从候选构建到生产晋级的总体顺序;测试执行内部同样遵循“获取测试环境锁 -> 迁移预检 -> 迁移及复验 -> 部署 -> 验收”,不是绕过数据库阶段先部署应用。生产入口选择已通过测试的制品,不重新构建或重新编号;只替换环境连接、凭据和目标环境授权。
### 9.2 构建与部署解耦
两人团队沿用现有 Jenkins 总控入口,环境采用现有配置选择或固定环境 Job。数据库预检、迁移和复验作为内部 stage 自动串联,不要求发布人先启动数据库 Job、再启动多个服务 Job。独立服务发布也必须进入同一流程。
当前总控和单服务 Job 需要支持等价于以下两种模式:
- `BUILD_ONLY`:构建并推送镜像,不部署。
- `DEPLOY_ONLY`:只部署指定的不可变镜像标签,不重新构建。
总控流程:
1. 计算并固定本次 Git commit 镜像标签。
2. 调用所有目标服务执行 `BUILD_ONLY`。
3. 确认所有镜像均已推送成功并绑定 digest、SQL 包和发布清单。
4. 获得发布锁,重新核对环境当前状态、发布资格和审批,再执行数据库迁移。
5. 数据库迁移成功后,对同一批镜像执行 `DEPLOY_ONLY`;下游必须验证有效发布身份及迁移结果,不能仅凭传入镜像标签绕过迁移门禁。
这样可以避免迁移成功后才开始构建、或部分服务使用不同提交版本。
### 9.3 每次部署都要运行的迁移步骤
不能只在 Git diff 命中 `deploy/sql/` 时执行。每次正常发布在锁内,对第 6.2 节确定的本次允许迁移库逐库执行以下逻辑;全部允许迁移库的预检及必要依赖兼容检查通过后,才开始任何库的迁移。区域清单中的其他库不进入迁移循环:
```text
核验环境身份、发布制品、基线登记及逐库目标
info:将历史、待执行列表与发布清单逐项比对
迁移前 validate:仅允许本次预期的 pending,拒绝 failed/missing/ignored/future
migrate:执行当前库的预期迁移,禁止乱序、自动基线和自动修复
迁移后 validate:不忽略 pending/future 或其他异常历史状态
再次 info + 结构/必要种子断言:确认已执行集合与该库目标一致
```
原因:
- 重试发布时 Git diff 可能为空,但数据库可能尚未迁移。
- 同一发布制品重试时仍需确认数据库历史及当前运行版本;旧构建超出当前兼容范围时拒绝重试。
- `migrate` 对已执行版本是幂等的,会跳过而不会重复执行。
Git diff 可以用于显示“本次包含数据库文件变化”和加强审批,但不能作为是否运行迁移的唯一条件。
实现时须固定 Flyway 版本并测试以下配置语义:
- 每次调用显式设置当前库的连接、`schemas`、`defaultSchema`、`table=flyway_schema_history` 和单库 `locations`;固定 `baselineOnMigrate=false`、`cleanDisabled=true`、`outOfOrder=false`、`validateOnMigrate=true`、`validateMigrationNaming=true`、`createSchemas=false`。
- 独立迁移前 `validate` 使用 `ignoreMigrationPatterns=*:pending`,只对已和清单逐项匹配的 pending 放行。该设置替换默认的 `*:future`,不同时放行 future。
- `migrate` 和迁移后独立 `validate` 显式清空 `ignoreMigrationPatterns`,防止继承默认 future 忽略或前一步的 pending 忽略;迁移前后配置按调用隔离,不写成贯穿整个 Job 的宽松全局变量。
- 有版本迁移的库显式使用清单中的整数 `target`,且该版本必须存在;无版本迁移的库核对 `0.0.0` 登记和空迁移集合后跳过 `migrate`,不生成假的目标文件。
- `validate` 校验历史与文件一致性,不证明实际结构未被人工修改;基线核对和本次结构/种子断言必须单独执行。
正常发布发现数据库高于清单目标、存在未纳入制品的成功迁移、校验和不同或任何失败记录时直接阻断。应用回滚按第 13.3 节执行,不能依靠默认忽略 future 迁移放行旧构建。
### 9.4 国内与海外任务
第一阶段在总控内部拆分为两个串行 Jenkins stage,不额外增加需要人工操作的数据库 Job:
- `db-migrate-cn`
- `db-migrate-overseas`
每个任务只加载本区域:
- 数据库地址。
- 迁移专用账号密码。
- schema 清单。
- 对应迁移目录。
若一次发布同时影响国内和海外,两个区域迁移必须全部成功后才能部署任何依赖这些变更的应用。第一阶段建议串行执行,日志更清楚,失败处理也更简单。
先完成两个区域全部预检,再按清单顺序串行迁移。跨库和跨区域没有统一事务:国内成功、海外失败时,保留国内已成功变更,停止本批次全部应用部署并进入恢复流程。旧应用必须能继续兼容这类部分扩展状态;不得自动逆向回滚成功库。
### 9.5 Flyway 运行环境
- 使用固定版本的 Flyway Docker 镜像,禁止 `latest`。
- 镜像先同步到当前已验证可访问的内部镜像仓库。
- Jenkins 仅挂载只读 SQL 目录。
- 数据库密码从 Jenkins Credentials 注入,不进入 Git、构建参数默认值或控制台明文日志。
- 连接必须启用适合生产的 TLS 设置,并明确字符集与时区。
### 9.6 发布互斥与过期构建
- 第一阶段按环境设置一把发布锁,覆盖本环境国内与海外参与区域;所有总控、独立服务发布、迁移和应用回滚入口必须服从同一锁。构建可以并行,数据库预检开始至部署验收结束不能交错。
- 由协调 Job 持锁,下游携带可核验的发布身份执行,不重复获取父任务已经持有的同一锁,避免死锁。单服务 `DEPLOY_ONLY` 不得绕过协调入口。
- 获取锁后重新读取最近发布状态,拒绝已被更新发布取代的旧构建;同一制品仅能作为该发布的重试。`disableConcurrentBuilds` 或单库 Flyway 锁不能替代环境发布锁。
- 发布状态至少区分待执行、执行中、成功、失败待处置。超时、取消或 Jenkins 重启后,持久化的未决状态必须阻断新发布;核实数据库会话、已执行迁移和已部署服务后才能恢复或关闭。
- 人工验收等待设置明确时限;超时记录失败待处置,不能因锁自动释放就默认允许下一批发布。紧急恢复由受审计入口接管同一发布状态。
上述记录复用 Jenkins 构建状态、持久化发布记录和归档结果,按第 14.1 节保护;保留可恢复的最近发布身份及未决标记,不另建发布状态数据库或管理页面。构建日志缺失或无法确认前次结论时阻断新发布。
## 10. 开发、测试、生产执行方式
### 10.1 环境与操作入口
以下为拟建操作入口和职责,不代表已实现或已获得环境执行授权;开发、测试、生产不各自维护一套 SQL。连接配置与凭据绑定具体环境,入口不得允许操作人随意填连接串。
| 阶段/环境 | 开发提交什么 | 如何执行 | 是否增加 SQL 序号 |
|---|---|---|---|
| 当前未上线的开发、测试 | 模块最终结构与最新区域初始化 SQL | 现有检查;确需重建时另行授权空库初始化,不在已有库重放整包 | 不增加,不为可丢弃存量维护升级链 |
| 机制启用后的开发编辑与一次性演练 | 自动生成的单库迁移、必要断言及模块最终结构 | 本地检查;在获授权的临时库分别验证空库到目标、上一发布到目标,结束后按授权处理临时库 | 同一未冻结变更反复修改不新增 |
| 机制启用后的保留数据开发库 | 已冻结的迁移文件或候选制品 | 查看计划后执行待执行项,采用与测试相同的历史校验,不自动重建 | 成功后调整时新增修正;从未成功候选按第 13.2.1 节恢复 |
| 纳管共享测试 | 已冻结候选制品,无环境专用业务 SQL | Jenkins 测试入口自动预检、迁移、部署及验收,保存可晋级证据 | 执行、重试不新增;成功迁移修改才新增,失败候选受控修订 |
| 生产 | 选择已通过测试的同一候选制品 | 生产发布入口核对测试证据、目标差异及审批,再自动迁移、部署和验收 | 晋级生产不新增、不重新编号 |
### 10.2 开发者最小操作流程
第一阶段只建设必要的轻量工具,操作意图如下;实际脚本名和参数在实现时确定,当前不可作为已可运行命令使用:
| 操作 | 输入 | 工具完成的工作 |
|---|---|---|
| 新建变更(`new`) | schema、业务动作 | 自动确定区域、编号和文件路径,生成 SQL 与断言说明模板,无数据库写入 |
| 检查 | 当前改动 | 文件名、重复编号、冻结记录、SQL 范围、中文注释与依赖检查,输出需要处理的问题 |
| 查看执行计划 | 目标环境、候选制品 | 在已有查询授权下读取历史,列出待执行文件、目标库、目标版本、影响服务和风险,不执行 SQL |
| 开发执行 | 已授权开发目标与冻结制品 | 核对计划并执行尚未成功的迁移;无变更时只验证,不要求手填版本 |
| 候选打包(流水线内部) | 已通过检查的代码、范围映射和评审信息 | 自动生成逐库目标、文件哈希、SQL 包和发布清单,绑定同批镜像,冻结供测试使用 |
| 测试发布 / 生产晋级 | 候选制品身份 | 同一执行内核,分别使用测试/生产配置、环境锁和授权;生产核对对应测试证据 |
例如给订单增加备注字段,开发生成一个 `V9__add_order_remark.sql`。该文件尚未冻结时,把字段长度从 100 调整到 200、补注释和索引,仍修改这个文件,并在临时库从起点重新演练;不能在已成功执行旧内容的保留数据开发库上直接重跑。冻结后,测试和生产都使用同一 `V9`,重复部署不会产生 `V10`。若测试成功后还要改变字段,工具生成 `V10`,新的候选包含原 `V9` 与修正 `V10`,再验证后晋级。若 `V9` 从未成功、未进入生产且自身有 SQL 缺陷,则按第 13.2.1 节修订,不能靠一个永远轮不到执行的 `V10` 解决。
SQL 文件记录必要的数据库变更,数量增长是可追溯性的结果;开发无需手工管理序号。不能为减少文件数量而在测试、生产成功执行后合并历史,或把所有 ALTER 放进每次运行的 repeatable 文件。
两人协作时,一个需求原则上由一人维护对应库的迁移,另一人检查;同一需求的相关字段和索引可放在同一文件。只有语句存在独立失败边界、耗时较长或需要跨发布分步执行时才拆分。多个 schema 仍分别建文件,由流水线按范围映射统一执行,不通过合成跨库大 SQL 减少文件数。
### 10.3 共享测试与生产晋级
- 一个成功迁移在每个环境只执行一次。测试已执行而生产未执行时,生产按自身历史补齐同一制品中的待执行项,不使用测试历史表替代生产判断。
- 生产入口核对测试通过记录对应的 SQL 包、清单、镜像 digest 与当前选择制品完全一致;文件发生变化就生成新候选并重新测试,不能带着旧测试结论上线。
- 测试环境可能领先生产。除测试库自身增量发布外,生产晋级前还须在获授权的隔离数据库验证“生产当前已确认版本到候选目标”的完整待执行链;从冻结包和对应历史迁移重建结构,并按第 11.1 节加入相关合成业务数据,不默认复制生产业务数据。
- 生产预检若发现实际起点与演练起点不一致,阻断晋级并重新核验。生产已处于目标且制品一致时允许幂等重试;不允许因为测试环境更高就放宽生产历史校验。
- 较新候选已经进入共享测试后,不为较旧候选自动降级数据库。较旧候选使用其有效历史证据及隔离升级演练;缺少证据时重新安排测试,不关闭 future 检查来凑通过。
- 尚未部署生产但测试已成功的迁移也保留原顺序,不合并或改写。草稿合并只限从未冻结、从未进入保留历史环境的文件;失败候选修订须单独满足第 13.2.1 节,不能借该例外合并成功历史。可丢弃的开发演练不得被当成生产升级验收。
### 10.4 测试重置任务
如确需频繁重置,建立独立 Jenkins Job,例如 `db-reset-test`,并满足:
- 环境写死为 `test`,不能由参数切换成 `prod`。
- 主机、实例和 schema 使用精确白名单。
- 校验环境登记的实例身份及精确 schema 清单;当前库名没有测试前缀时,不虚构前缀要求,也不能只凭库名判断环境。
- 使用测试专用凭据,凭据本身没有生产权限。
- 执行前显示目标并进行明确确认。
- 重置后执行冻结初始化包、逐库基线登记、目标发布制品中的全部迁移和独立测试造数。
生产 Job 中不提供 `clean`、`reset`、`repair` 按钮或参数。
## 11. 提交前静态门禁
每个发布制品都执行以下离线检查;`deploy/sql/` 变化时还须评审增量差异:
1. 文件路径属于已登记的区域和 schema。
2. 迁移文件名符合第 6 节自动整数编号规则,初始化包排除在迁移扫描目录之外。
3. 每个 schema 内新增版本高于该库既有版本,没有重复或向已冻结版本区间补插的文件;跨 schema 允许同版本。
4. 对照受保护候选及发布存档而非仅 Git diff,确认冻结基线和成功迁移没有修改、删除或重命名。失败候选修订仅按第 13.2.1 节的版本、旧/新哈希及恢复记录精确放行,不扩大为忽略全部校验和变化。
5. 自动生成的清单与固定范围映射、评审信息、SQL 包、代码提交和镜像相符;允许迁移库的目标精确等于随包提供的最大迁移版本,无迁移时为 `0.0.0`;范围外的库无本次迁移目标或可执行 SQL,不再校验 SQL 序号等于业务版本。
6. 文件只影响所属 schema,不出现跨库限定名。
7. 不包含禁止语句、密钥或真实客户数据。
8. SQL 以 UTF-8 保存,结构和注释符合仓库数据库规范。
9. 国内和海外目录没有相互引用。
10. 清单区分区域 schema 归属与本次允许迁移范围,覆盖受影响服务及消费者;可执行 SQL 恰好属于允许迁移库的完整版本链,迁移顺序、文件哈希和制品身份一致,没有漏带、越界或跳号抽取 SQL;第一阶段禁止未经评审的 callback、脚本迁移及 repeatable 文件。合法废弃编号的空档仍按第 6 节处理。
11. 机制启用后,模块有效结构或必要系统种子定义的变化须关联对应迁移,或提供经评审的无需迁移理由;关联记录及应用数据库验证按第 11.2 节执行,不能只检查迁移目录是否变化。
静态扫描只是门禁,不代替数据库执行验证。经授权的隔离 MySQL 验证环境须覆盖“空库初始化到目标”和“上一发布版本到目标”两条路径,结果既要彼此一致,也要满足第 11.2 节的应用预期结构及必要种子断言;使用与目标环境一致的 MySQL 小版本、字符集、排序规则和 SQL mode。不得为此擅自启动数据库、清库或复制项目工作区。
### 11.1 带数据的升级演练
涉及已有字段、索引、约束或数据转换时,在升级起点插入少量合成业务数据,再执行原候选 SQL。测试数据和检查脚本放入独立测试目录,不加入生产 Flyway `locations`,不使用真实客户数据。普通新增表只需覆盖受影响场景,不要求每次发布重做全量压测。
| 变更 | 至少覆盖的合成数据 | 预期结果 |
|---|---|---|
| 新增唯一索引 | 正常唯一值、重复值;按实际排序规则检查大小写等价值 | 正常值升级成功;不满足约束时在 DDL 前明确阻断,或执行已评审的数据处理规则,不静默删行 |
| 收紧非空或新增必填字段 | 有值、NULL、空字符串(业务上相关时) | 明确合法默认值/转换规则;不能处理的数据在结构修改前阻断 |
| 缩短字符串或改变数字类型/精度 | 边界长度、超长值、超范围值、小数精度边界 | 合法数据保留语义;不允许静默截断、舍入或溢出替代错误处理 |
| 数据转换、关联或回填 | 正常记录、缺失关联、重复业务键及部分已处理状态(如支持续跑) | 行数、关联关系及关键金额/数量等断言正确,无重复累计和遗漏 |
正常场景与预期失败场景分别运行并保留结果;失败场景在隔离库触发,不向共享测试或生产写入故意构造的坏数据。相关前置数据检查须纳入实际发布预检,因为生产数据可能在演练后变化;仍由数据库约束和错误处理保障检查与执行之间的并发边界。
报告绑定生产起点、候选 SQL 包哈希、测试数据版本及前后断言。只有空库执行成功不算存量升级通过;涉及大表、锁等待或批量更新时再增加代表性数据量的性能演练。
### 11.2 防止漏写迁移
以下要求从第 1.1 节的机制启用点生效;当前未上线阶段仍维护最新初始化,不为可丢弃数据补升级链。
- 固定范围映射同时登记各库的模块有效结构、必要种子定义和相关数据库测试入口;不把 `resources/db/` 下的历史追溯文件全部当作当前期望。仅在来源或入口变化时维护映射。
- `check` 比较明确的评审起点与候选提交,列出这些有效定义的变化,并检查是否关联本库迁移;没有关联时要求在现有代码评审中说明无需迁移的具体理由,例如仅修改注释说明且不改变数据库定义。该记录绑定提交并随候选归档,不增加独立手填发布清单,也不允许用全局跳过参数放行。
- 关联存在只证明有对应文件,不证明 SQL 正确。隔离演练还须将迁移后的实际结构与同一候选有效定义表达的应用预期比较,至少覆盖受影响的表、列、类型、默认值、索引及约束,并核验必要种子。预期不能只从待测迁移执行结果反向生成;有效定义无法直接提取时,由受影响模块的定向断言表达并随代码评审。
- Mapper、持久化 SQL 或其他数据库读写代码变化时,执行受影响功能的数据库集成验证,覆盖实际读写;不能只依赖 Java 编译、Mock 或迁移历史校验。只有代码变化也可能漏写迁移,因此该验证不以结构文件变化为唯一触发条件;报告绑定候选提交、SQL 包及测试入口。
- 使用现有模块合同、测试入口和合成数据,不引入全量代码到 SQL 推断平台,也不自动生成或应用结构差异。若预期结构来源或必要测试尚未覆盖,明确标为待验证,不能作为候选通过的证据。
## 12. 数据库账号与权限
应用账号与迁移账号必须分离。
### 12.1 应用账号
只授予运行期所需的 DML 权限,不应具备建表、删表、修改结构等 DDL 权限。
### 12.2 迁移账号
只由受控迁移入口使用,按区域和 schema 授予完成受控迁移所需的最小权限。国内、海外必须使用不同凭据,不能访问对方实例。逐库最小权限与单库 locations 一起限制作用域;初始化所需建库权限不长期授予迁移账号。
### 12.3 管理员账号
RDS 高权限管理员账号不应长期配置在 Jenkins 中。紧急人工恢复时使用受审计的临时授权流程。
## 13. 失败、恢复与重跑
### 13.1 迁移前失败
镜像已经构建但迁移尚未开始时,直接停止发布。未部署的镜像保留以便排查或重试。
### 13.2 迁移执行失败
立即停止后续迁移与本批次应用部署,将发布标记为失败待处置。保存 Flyway 输出、数据库错误、历史表快照、已执行语句/对象、版本、区域、schema、SQL 包哈希及 Git commit。连接中断或超时须先确认数据库侧语句是否仍在运行,禁止直接并发重试。
MySQL DDL 可能已隐式提交,不能假定整个文件自动回滚。按以下情况分别处置:
| 情况 | 处置与恢复门槛 |
|---|---|
| 已成功执行,后来发现业务缺陷 | 保留原文件及成功历史;新增更高版本修正,按正常发布流程验证。 |
| 执行失败,已证明没有业务残留 | 消除权限、资源或连接等失败原因;若有失败历史,经审计修复后重跑原文件,不能跳过失败版本。 |
| 执行失败,已有部分 DDL/DML 提交 | 单独评审残留处置清单;可安全恢复到迁移前状态时,在明确授权下处置残留、核对前置状态,再修复失败历史并重跑原文件。 |
| 原文件存在确定性 SQL 缺陷,且从未成功、未进入生产 | 检查第 13.2.1 节全部条件,通过受控候选修订恢复;不能直接追加更高版本绕过错误文件。 |
| 残留无法安全撤销,或不能满足候选修订条件 | 阻断自动发布,另行形成具体恢复方案,列明已提交效果、数据保护、如何达到目标及历史记录处理方式;普通流水线不得擅自修改文件或伪造成功记录。 |
| 数据库效果已完成,但成功登记或客户端回执不明确 | 核验实际结构、种子、数据库会话及历史表;有完整成功记录时按同一制品续跑,否则进入人工恢复方案,不依据 Jenkins 超时判断数据库失败。 |
`repair` 只修复历史表,不撤销业务对象;它还可能对齐校验和或标记缺失文件。禁止自动执行。人工执行前必须使用原发布的完整单库迁移目录,核验所有成功文件未被改变,备份历史记录并评审预期变化;执行后确认只发生已批准的历史修复,禁止借此接受已成功迁移的篡改。
仅当实态、历史记录和迁移文件重新一致、全部参与库预检通过后,才可恢复原发布或经过评审的新发布。一次迁移未完成时,仅新增更高版本不能解除失败历史造成的阻断。
### 13.2.1 从未成功的失败候选修订
这是异常恢复入口,不是日常发布的自动重试选项。允许沿用原迁移序号修订源码,但必须同时满足:
- 根据候选分发记录核对全部已登记的保留历史环境,该迁移没有任何成功记录;历史缺失、已清库导致无法证明或存在未确认的分发目标时,不适用此例外。
- 原候选从未进入生产执行;生产尝试过即转为专项恢复,即使尚无成功记录也不能套用开发测试的修订流程。
- 相关迁移会话已结束,没有正在部署、待重试或排队使用旧候选的任务。下游依赖迁移未成功执行,依赖关系不明确时先停止并核实。
- 已保存原 SQL、代码提交、制品哈希、失败历史及残留证据;两名开发共同复核恢复方案。残留可经明确授权安全恢复到该迁移执行前状态,或已证明没有残留。
恢复步骤:
1. 在受保护发布记录中撤销旧候选及引用该错误 SQL 的其他候选的部署/晋级资格,取消其排队重试;所有部署入口在取锁后重新检查撤销状态。旧制品只读留档,不覆盖。
2. 在目标环境恢复授权范围内处置残留并验证前置状态。需要移除失败历史时,先使用原制品的完整单库目录执行受审计的人工 `repair`;核实没有对齐成功文件校验和或改写其他历史。无失败记录则不执行 `repair`。
3. 在当前源码中修正同一迁移文件,保持原序号和业务目的。记录精确的“区域/schema/版本、旧哈希、新哈希、原候选、恢复报告、复核人”,由现有 Jenkins 归档保存;无需另建恢复管理系统。
4. 检查器依据上述修订记录,仅放行该文件的哈希变化;其他成功迁移与冻结基线继续严格校验。生成新 Git commit 对应的新制品和清单,撤销记录不得被普通发布参数跳过。
5. 先通过空库完整链、升级链及相关带数据场景,再重新部署测试并获取新的测试证据;全部通过后新候选才可晋级生产。后续再次修订时重新核验本节条件,不能复用上次结论。
例如 `V9` 因 SQL 错误在测试失败,所有保留历史环境均未成功且生产从未尝试:恢复测试库后,将修正后的 `V9` 随新候选重新验证。原候选永久保持原内容并禁止执行;新旧两个 `V9` 不得同时出现在同一执行目录。若任一环境已成功执行原 `V9`,本例外不成立,需保留成功历史并单独解决失败环境的恢复问题。
### 13.3 迁移成功、应用失败
- 扩展迁移成功后,只有明确兼容当前数据库与系统种子的旧镜像才允许回滚,数据库结构保留。保留成功迁移的完整发布制品用于校验,不用旧镜像提交里的 SQL 重新执行正常发布链。
- 回滚同样持有环境发布锁。按最新数据库制品严格检查历史及结构,再对照兼容记录部署指定旧镜像 digest;数据库高于该旧镜像对应的原始版本,不自动意味着可以或不可以回滚,必须有兼容依据。
- 部分服务部署成功、部分失败时,记录逐服务当前 digest;只对兼容已证明的服务恢复镜像,不能认为数据库与应用整批自动回到上一版本。事件生产者、消费者和受影响定时任务一起纳入判断。
- 修复应用后重新生成发布制品;数据库目标不变时验证并跳过已成功迁移。若不满足兼容条件,停止自动镜像回滚并进入受控恢复,不能通过忽略 future 或关闭校验放行。
### 13.4 数据库回滚
自动流水线不执行逆向 SQL。严重事故依赖:
- RDS 备份和时间点恢复能力。
- 发布前备份或快照策略。
- 新增向前修复迁移。
- 经人工审批的恢复方案。
恢复方案必须明确 RPO/RTO、恢复目标实例、数据损失窗口、恢复后切换与业务核对步骤,并考虑跨库、跨区域事件、Outbox/Inbox、缓存及文件引用的一致性。默认先在隔离目标验证恢复结果,不能把单库时间点恢复视为整条业务链已经恢复。
## 14. 日志与验收证据
每次迁移至少记录:
- Jenkins 构建号。
- Git commit 和镜像标签。
- 发布身份、清单/SQL 包 SHA-256、各镜像 digest、审批及发布锁状态。
- 候选制品身份、生产发布版本 `release-N`、发布状态及对应测试通过记录;不维护独立手工 SQL 发布版本,编号规则见第 6.3 节。
- 环境与区域。
- schema 名称。
- Flyway 镜像版本。
- 迁移前、迁移后版本。
- 本次实际执行的迁移文件列表及校验和。
- `validate` 结果和最终退出码。
- 各阶段有效校验配置、迁移耗时、前后结构及种子断言结果、失败恢复或应用回滚证据。
日志中必须遮蔽数据库密码、Token 和连接串敏感参数。
应用部署完成后,人工验收至少包括:
- Jenkins 数据库阶段全部通过。
- 各库 Flyway 历史、实际结构及必要种子与发布清单的各自目标一致。
- 服务健康检查通过。
- 国内、海外关键业务路径可用。
### 14.1 发布证据与镜像保留
当前仓库 `Jenkinsfile` 和 `Jenkinsfile.service` 均配置只保留最近 30 次构建。接入迁移前必须调整保留策略,不能让普通构建清理删除发布资格、冻结记录或未决恢复证据。第一阶段复用 Jenkins 的构建保留能力及现有备份方式,不增加独立制品平台。
| 对象 | 保留规则 |
|---|---|
| 初始化基线、生产发布 SQL 包/清单、迁移结果、失败候选原文及修订/撤销记录 | 作为数据库生命周期证据长期保留,明确标记免于普通构建清理,并纳入可恢复备份 |
| 已冻结但尚未晋级/废弃的候选及测试证据 | 冻结归档时即保护;生产晋级前检查完整性与可读取性,缺失即阻断 |
| 执行中、失败待处置或超时结果未确认的发布 | 状态、SQL 包、镜像及证据持续保护;关闭恢复事项前不得清理 |
| 当前生产镜像、仍允许回滚的镜像、待晋级或恢复引用的镜像 | 按 digest 设置保留引用,不随 Jenkins 构建过期或普通镜像垃圾回收删除 |
| 未冻结的普通构建、无发布/恢复引用的临时镜像 | 继续使用常规保留策略;不得据此删除共享的受保护制品 |
流水线在数据库执行前自动完成归档、构建保留标记和镜像保留引用,确认保护生效后才允许执行 SQL。标记或归档失败时立即停止,不能等 SQL 执行完才尝试保存唯一证据。标记“保留构建”不能代替备份,镜像 digest 也不能代替实际镜像可下载。
发布记录保存候选身份、SQL/清单哈希、镜像 digest、测试证据、撤销及恢复状态;取锁后的发布检查始终读取这份受保护记录。任务删除、Jenkins 备份恢复及镜像清理必须覆盖这些记录,记录无法恢复时阻断发布而不重建一个“假定成功”的状态。
候选废弃可撤销晋级资格,但 SQL 原文和冻结/撤销证据继续保留。解除镜像保护须确认其不再被当前生产、允许回滚版本、待晋级候选或未决恢复引用,并记录处理结果。首次验收须模拟超过 30 次普通构建及镜像清理,验证受保护记录仍可读取、镜像仍可拉取,撤销候选仍被拒绝。
## 15. 分阶段实施清单
本轮确认的是小团队工作方式。测试分支已完成第一批离线交付:固定范围映射、自动编号、范围/文件检查、选定服务的发布清单生成和只读计划入口;数据库登记、Flyway 执行及 Jenkins 改造尚未完成。第一阶段完整交付仍为“一份固定范围映射 + 一个命令入口 + 现有 Jenkins 内的自动迁移阶段”,不以文档齐全代替运行完成。
本轮文档评审补项已纳入第 6.2、11.2 节及验收表,可开始阶段 A 的事实整理与阶段 B 的离线实现。首批交付聚焦范围映射、自动编号、离线检查和制品清单;工具版本行为、数据库演练及流水线效果按后续阶段验证,不把“可开工”标为“可生产启用”。
| 工作包 | 交付物 | 完成判定 |
|---|---|---|
| 离线工具 | 固定范围映射、`new/check`、内部打包和清单生成 | 无数据库连接也能验证编号冲突、历史修改、文件遗漏和制品哈希;普通开发不手填版本或清单 |
| 执行工具及流水线改造 | `plan/run`、构建与部署解耦、发布锁、保护与撤销记录 | 在获授权的验证库通过首次执行、重复部署、部分 DDL 失败及候选修订;所有入口均在 SQL 阶段完成后才部署应用 |
| 测试集成与生产晋级 | 同一发布制品在测试验收后进入生产 | 测试覆盖完整发布流程和有数据升级;生产仅切换独立环境配置与凭据,消费已测制品,不再补建另一条流程 |
依赖顺序为:阶段 A 确认环境事实,阶段 B 完成离线工具,阶段 C 完成执行工具及流水线解耦,阶段 D 完整测试,阶段 E 生产配置与启用。A 的事实整理与 B 的离线开发可分别推进;实际连接、基线登记和环境启用仍须完成 A 对应环境的确认。稳定后再考虑额外管理页面、自动 schema diff 或更复杂的发布策略,不把这些作为首轮交付前置。
### 阶段 A:冻结事实
- 确认是否存在须保留的生产存量、启用环境和冻结时点,同步调整适用工程指引。
- 无存量时冻结已验收的最新初始化包;有存量时找回实际执行包及后续变更证据,按第 5 节证明实态匹配。
- 归档区域初始化包、SHA-256 与基线 manifest,确认独立初始化及逐库登记方案。
- 环境连接、备份和结构核对另按精确授权执行;事实未确认前不进入登记。
### 阶段 B:建立迁移目录与校验脚本
- 建立自动整数编号、固定范围映射及生成清单规范,固定 Flyway/MySQL 版本及有效配置。自动编号、范围映射和清单生成已落地,Flyway 版本固定仍待阶段 C。
- 优先实现新建、离线检查、候选打包和只读执行计划入口;无 SQL 变更不得生成新迁移。当前已完成这些离线入口,数据库执行另按授权接入,不能把计划工具变成隐式执行入口。
- 建立国内、海外、schema 级目录。
- 增加文件名、版本、路径、禁止语句和历史不可变检查;区分区域归属清单与允许迁移清单,拒绝越界 SQL、缺失依赖及不完整版本链。
- 登记有效结构、种子和测试入口,接入变更与迁移关联检查;编写漏写迁移、单服务范围隔离、空库链、升级链、带数据边界及失败候选修订的测试用例。本阶段只完成离线准备,数据库执行验证进入 C。
### 阶段 C:执行工具与流水线解耦
- 实现 `plan/run` 及分阶段校验配置,在获授权的隔离数据库验证原始 SQL、首次执行、重复执行、结构断言与失败恢复;验证单服务范围隔离,并用应用预期结构及实际读写测试发现漏写迁移。
- 单服务 Job 先支持构建与部署解耦;总控固定为“全部目标镜像就绪 -> SQL 阶段 -> 同批镜像部署”,此能力必须先于共享测试接入完成。
- 全部独立部署入口纳入环境锁和候选资格校验,补齐归档保护、镜像保留、未决状态及撤销记录;禁止旧 Job 绕过新的 SQL 门禁。
- 接入第 6.3 节的 main 同步检查、分支编号冲突检查和合并后候选验证;自动分配并保护 `release-N`,同一候选重试不换号,成功后更新生产版本和发布标签。
- 对从未成功、未进入生产的失败候选验证第 13.2.1 节修订链;对已成功或生产尝试过的文件验证例外被拒绝。
### 阶段 D:共享测试完整演练
- 按 A 的基线核验结果,在授权测试环境逐库登记 `0.0.0` 并配置本环境专用凭据。
- 使用 C 的完整流水线执行预检、迁移、复验、同批镜像部署及验收;不能只测试 SQL 后再到生产第一次验证发布顺序。
- 验证重复部署跳过成功 SQL、数据库失败阻断应用、跨区部分成功、取消/超时和镜像回滚。
- 验证有数据的升级、共享测试领先生产的完整升级链、失败候选修订及撤销;测试报告必须绑定原 SQL 包和演练起点。
- 验证构建清理和镜像清理后,生产依赖的候选证据、恢复记录和镜像仍受保护。第 16 节相应用例通过后才进入 E。
### 阶段 E:生产配置与启用
- 配置国内、海外生产迁移专用凭据、实例身份和受保护制品访问;复用 D 已测试的流水线逻辑。
- 保留现有生产确认门禁;是否取消发布确认另行决定,不与数据库迁移改造绑定。
- 先选一个只包含安全新增的版本演练。
- 核对 RDS 备份和恢复能力。
- 国内、海外按发布范围执行迁移。
- 只晋级已通过测试的原候选制品,核验生产实际起点与升级演练一致。
- 完成健康检查与人工业务验收。
## 16. 首次落地的验收标准
以下是待实施的验收用例,不代表已执行通过。首次启用前须保存对应制品、报告和人工确认:
| 场景 | 预期结果 |
|---|---|
| 空库初始化并接入 | 区域初始化完整成功、逐库登记 `0.0.0`、迁移到各自目标;初始化包不被 Flyway 重放。 |
| 已有匹配库接入 | 全量结构及必要种子核对通过,只登记,不重放初始化;任一差异阻断。 |
| 从上一发布版本升级 | 与空库到目标路径一致,且两者均满足候选应用预期结构、必要种子断言和受影响数据库读写验证。 |
| 新增 pending 迁移 | 迁移前校验允许清单内 pending,实际执行后严格复验成功;不会被默认独立 validate 提前阻断。 |
| 同一制品重复发布 / 无 SQL 变更 | 成功迁移不重复执行,各库目标保持正确;无迁移库继续停留基线版本。 |
| 修改、删除或重命名成功迁移 | 发布存档校验或 Flyway 校验阻断;失败候选修订记录也不能放行成功历史的变化。 |
| 数据库出现 future、missing、ignored 或失败历史 | 正常发布阻断;默认忽略 future 的设置不得漏入流水线。 |
| 一个文件前条 DDL 成功、后条失败 | 应用未部署,残留与失败历史可见;按恢复方案演练后才可续跑,不用更高版本掩盖失败。 |
| 国内成功、海外失败 | 本批次全部应用部署停止,国内成功迁移保留,旧应用继续兼容;恢复时不重放成功部分。 |
| 单服务入口与总控同时发布 / 旧构建晚到 | 整批发布不交错,独立入口不能绕过锁,过期构建被拒绝。 |
| 迁移超时、取消或 Jenkins 重启 | 新发布被未决状态阻断,先确认数据库会话及真实执行结果。 |
| 迁移成功、部分应用部署失败 | 仅在兼容记录允许时回滚指定镜像 digest;不执行旧 SQL、不逆向回滚数据库。 |
| 制品被替换、跨库访问或未知实例 | 哈希、权限或目标白名单校验阻断;测试重置凭据无法连接生产。 |
| DDL 锁等待/耗时超限、备份恢复 | 超限后不继续部署;已演练恢复并记录 RPO/RTO 和跨服务一致性核对。 |
| 日志与人工业务验收 | 无凭据泄漏,逐库证据完整,目标镜像 digest 正确,国内/海外关键路径人工确认。 |
| 自动编号与并发创建 | 开发不输入版本;同库冲突被阻断且不覆盖文件,只允许未冻结草稿重新编号。 |
| 开发反复编辑与普通代码发布 | 未冻结同一变更不因编辑次数增加编号,无 SQL 变化不生成空文件;成功迁移禁止覆盖,失败候选仅按受控例外修订。 |
| 同一候选从测试晋级生产 | SQL、序号、清单和镜像 digest 完全相同,不重新生成;生产只执行自身缺少的迁移。 |
| 共享测试领先生产 | 额外验证生产起点到候选的完整链;起点变化或缺少对应测试证据时阻断晋级。 |
| 两人团队正常发布 | 新建迁移只输入库与说明;其余随代码检查和部署自动完成,不需要另填计划 JSON、手工改版本或依次点击数据库/服务 Job。 |
| 失败候选从未成功且未进入生产 | 核验全部环境、撤销旧候选、恢复前置状态后,可修订同一编号并生成新制品;旧候选始终不可执行,空库和带数据升级均重新通过。 |
| 失败候选例外不满足条件 | 存在任一成功记录、生产尝试、未确认分发目标或未清残留时拒绝修订;不得批量忽略校验和。 |
| 有业务数据的约束及转换 | 正常数据保留业务语义;重复值、NULL、越界或缺失关联按第 11.1 节明确处理/阻断,无静默丢失,失败用例仅在隔离库运行。 |
| 超过 30 次构建及镜像清理 | 受保护 SQL 包、发布/测试/恢复证据仍可读,当前生产、可回滚和待晋级镜像仍可拉取,撤销候选仍被拒绝。 |
| 测试验证发布全流程 | 构建与部署解耦已在测试阶段启用;迁移失败时没有任何目标应用提前部署,生产复用同一逻辑。 |
| 单服务发布,同提交含无关库的新 SQL | 只执行已评审的允许迁移库;范围外 SQL 不进入可执行包,范围外数据库及历史表没有写入。 |
| 必要依赖不在范围 / 同库夹带未就绪变更 | 生成候选或预检时阻断并列明原因;不得自动扩库、漏部署必要服务或跳过同库中间版本。 |
| 有效结构变化但未关联迁移 | 离线检查阻断;只有与具体变化绑定、经评审的无需迁移理由可放行,不能用通用跳过开关。 |
| 代码读取新列,但结构与迁移都漏改 | 即使两条迁移路径相同,受影响的真实数据库读写验证仍失败,候选不得通过。 |
| 迁移文件存在但漏改字段或必要种子 | 与应用预期结构或种子断言不符时阻断,不能仅凭存在迁移文件或历史校验成功放行。 |
## 17. 当前 Jenkins 数据库检查边界
仓库已保留数据库结构只读门禁实现,但当前 Jenkins 暂时关闭该阶段(`databaseSchemaContractEnabled = false`)。后续完成 Credentials 配置并确认启用时,再打开该开关。启用后的门禁只做结构一致性检查,不做业务验收和迁移执行:
- 检查国内、海外初始化 SQL 约定的表和字段是否存在。
- 只读查询 MySQL `information_schema`。
- 缺表或缺字段时流水线失败,阻止继续构建/部署。
- 不执行初始化 SQL、版本迁移 SQL、DML、造数、清理或回填。
- 不检查业务表中是否有数据,也不检查数据内容和业务流程。
Jenkins 需要为每个环境配置一个文件型 Credentials:
```text
SCHEMA_CHECK_READ_ONLY=true
SCHEMA_CHECK_HOST=...
SCHEMA_CHECK_PORT=3306
SCHEMA_CHECK_USERNAME=...
SCHEMA_CHECK_PASSWORD=...
```
Credentials 名称约定:
- `firstleg-development-schema-readonly`
- `firstleg-test-schema-readonly`
- `firstleg-production-schema-readonly`
账号只授予目标实例的 `information_schema` 读取权限和连接所需权限,不授予建表、改表、删除、插入、更新或执行迁移权限。生产环境未配置对应 Credentials 前,结构门禁会失败,不会静默跳过。
脚本入口:
```text
node scripts/database-schema-contract.mjs --region BOTH
```
## 18. 官方参考
- [Flyway Versioned migrations](https://documentation.red-gate.com/fd/versioned-migrations-273973333.html)
- [Flyway Repeatable migrations](https://documentation.red-gate.com/fd/repeatable-migrations-273973335.html)
- [Flyway Schema history table](https://documentation.red-gate.com/fd/flyway-schema-history-table-273973417.html)
- [Flyway Validate](https://documentation.red-gate.com/flyway/reference/commands/validate)
- [Flyway Ignore migration patterns](https://documentation.red-gate.com/flyway/reference/configuration/flyway-namespace/flyway-ignore-migration-patterns-setting)
- [Flyway Target](https://documentation.red-gate.com/flyway/reference/configuration/flyway-namespace/flyway-target-setting)
- [Flyway Baseline](https://documentation.red-gate.com/flyway/reference/commands/baseline)
- [Flyway Repair](https://documentation.red-gate.com/flyway/reference/commands/repair)
- [Flyway Migration transaction handling](https://documentation.red-gate.com/flyway/flyway-concepts/migrations/migration-transaction-handling)
- [Flyway Validate on migrate](https://documentation.red-gate.com/flyway/reference/configuration/flyway-namespace/flyway-validate-on-migrate-setting)
- [Flyway Create schemas](https://documentation.red-gate.com/flyway/reference/configuration/flyway-namespace/flyway-create-schemas-setting)
- [Flyway Validate migration naming](https://documentation.red-gate.com/flyway/reference/configuration/flyway-namespace/flyway-validate-migration-naming-setting)
- [Flyway Add](https://documentation.red-gate.com/flyway/reference/commands/add)
- [Jenkins Pipeline options:构建及制品保留](https://www.jenkins.io/doc/book/pipeline/syntax/#options)
以上行为参考官方文档;实施时须按选定 Flyway 版本重新验证,尤其是 pending/future 校验、空迁移库和 MySQL 失败历史处理,不能将文档评审等同于运行验收。