BitzOrcas 把 Schema 收敛分成三条车道,发版时不要把它们捆成一次 InitTables:
| 车道 | 何时 | 自动做什么 | 不做什么 |
|---|---|---|---|
A. --init-schema | 部署窗口 | 建缺失表;给已有表补可空且无默认值的列 | 改列、建已有表索引、卸整表索引 |
B. /host/schema 与 scripts/database/migrations/ | 运维审阅窗口 | 生成放宽长度、必填加列、建索引、改类型的受保护脚本 | 发版瞬间自动执行 |
C. API Host operations-schema-drift-notify | 常驻 API 每 15 分钟 | 只读嗅探残留漂移,给 host-admin 发站内信 | 自动 ALTER、阻断启动 |
空库仍由编译期元数据驱动 SqlSugar CodeFirst。已有库的破坏性或 size-of-data 变更必须先执行 scripts/database/migrations/ 中尚未应用的顺序脚本,不能指望 --init-schema 改列长。
关键路径图
下图展示了生产环境数据库版本迁移与安全升级流水线:通过迁移锁与幂等校验确保多实例滚动发布时表结构安全变更。
命令语义
| 命令 | 行为 |
|---|---|
--init-schema | 业务表 + 当前审计分表 + CAP Outbox + 种子 |
--init-schema --no-seed | 业务表 + 审计分表 + CAP Outbox,不跑种子 |
--seed-demo | 等同完整 --init-schema,表达“恢复演示状态” |
--seed-only | 初始化 CAP Outbox 后跑种子,业务表必须已存在 |
--reset-schema | 破坏性重建;Production/Staging 拒绝 |
AppHost BITZORCAS_ASPIRE_RESET_SCHEMA=true | 把 --reset-schema --force 交给一次性 schema-initializer。默认叠加 --no-seed;同时开 BITZORCAS_ASPIRE_SEED_DEMO=true 则叠加 --seed-demo。不能与 BITZORCAS_ASPIRE_RESET_DEMO_PASSWORDS 同时使用。Quartz 表不会被删除 |
systemd 发版模式(deploy.sh)见 部署形态选择:BITZORCAS_DEPLOY_DB_MODE=schema-only|platform-seed|full-seed。
AppHost persist 库整库重建
dotnet run --project src/Hosts/BitzOrcas.AppHost -- --reset-schema 不会 reset 库:-- 后面的参数进的是 Aspire AppHost 自己,进不了 schema-initializer。fast / persist 库要推倒重建,先停掉正在跑的 AppHost,再开显式开关:
# 删业务表、审计分表、CAP 表后重建,并重新播种演示账号。BITZORCAS_ASPIRE_RESET_SCHEMA=true \BITZORCAS_ASPIRE_SEED_DEMO=true \dotnet run --project src/Hosts/BitzOrcas.AppHost --launch-profile fast
# 只要空表、不要演示账号时去掉 SEED_DEMO。BITZORCAS_ASPIRE_RESET_SCHEMA=true \dotnet run --project src/Hosts/BitzOrcas.AppHost --launch-profile fastDashboard 里 schema-initializer 应先打印破坏性警告,再跑 --reset-schema --force。成功后必须关掉 BITZORCAS_ASPIRE_RESET_SCHEMA 再日常启动。Production / Staging 的 AppHost 会直接退出。列长变更不要靠这条,走 /host/schema 或 scripts/database/migrations/。
只改演示密码、要保留业务数据时,用 BITZORCAS_ASPIRE_RESET_DEMO_PASSWORDS,不要开 RESET_SCHEMA。
推荐脚本:
scripts/database/init-schema.shscripts/database/seed-demo.sh目标数据库必须由 DBA 预先创建,账号需要建表和建索引权限。
初始化顺序
- 从编译期
PersistenceModelRegistry创建业务表与索引; - 创建当前时间桶的审计分表;
- 通过 CAP
IStorageInitializer幂等创建 Outbox 表; - 按
ISeedStep.Order执行幂等 CSV seed。
常驻 Host 正常启动时仍由 CAP Provider 自动创建 Outbox 表;一次性命令不会启动 hosted service,因此 --init-schema 无论是否带 --no-seed 都会幂等初始化 CAP Outbox(保证仅建表路径也就绪)。未来审计分表在首次写入对应时间桶时按规则生成。
SQL Server 上 --init-schema 先做目录探测,再按锁安全计划执行:
| 探测结果 | 发版行为 |
|---|---|
| 对象齐全且字符串列够宽 | 毫秒级跳过 |
| 缺物理表 | 只对不存在的表 InitTables,并给新表建声明索引 |
| 已有表缺可空、无默认值列 | ALTER TABLE [<table_name>] ADD [<column_name>] <data_type> NULL,LOCK_TIMEOUT 5000,超时失败退出 |
| 列更窄、缺索引、必填/带默认值加列、改类型 | 不改库,打残留日志,退出码仍为 0 |
| 探测失败 / 非 SQL Server | 逐表存在性探测,仍遵守上表 |
BITZORCAS_SCHEMA_FULL_INIT=1 | 仅 Development/Demo 可全量 CodeFirst,且不卸已有表索引;Production/Staging 忽略 |
已有数据库升级
上线新版本前:
备份 → 盘点 scripts/database/migrations(破坏性 / 回填 / 列变宽) → 按文件名顺序由 DBA 执行未应用脚本 → deploy.sh 跑 --init-schema --no-seed(只建缺表、补可空列) → 启动 API / JobHost → Host-Admin 若收到「Schema 漂移待实施」站内信,打开 /host/schema → 验证后再切流deploy.sh 不会自动执行 scripts/database/migrations/。那是 DBA 清单,不是发版隐式步骤。
示例 1:日常发版只加了一张新表和一列可空字段
PO 新增 SysWidget,并在已有 SysUser 上增加可空 Region。生产执行:
BITZORCAS_DEPLOY_DB_MODE=schema-only \ sudo /www/scripts/deploy.sh /tmp/app.zip Production--init-schema --no-seed 会 CREATE TABLE 新表(含新表索引),并对 SysUser 执行类似:
SET DEADLOCK_PRIORITY LOW;SET LOCK_TIMEOUT 5000;ALTER TABLE [dbo].[SysUser] ADD [Region] NVARCHAR(256) NULL;这是 SQL Server 元数据操作,与行数无关;若 5 秒内拿不到 Schema Lock,命令失败退出,不会无限等。
示例 2:把 SysModule.RequiredPermission 从 100 放到 2000
这是有界加长,不会被 --init-schema 自动执行。种子若写入超过 100 字符,会得到 SQL 2628。正确顺序:
- 发版后 Host-Admin 打开
/host/schema,预览WidenLength审阅脚本。 - 或手跑幂等语句(目录小表,通常秒级):
-- 目录小表:先确认列仍偏窄,重复执行不会无条件改写。IF COL_LENGTH('dbo.SysModule', 'RequiredPermission') IS NOT NULL AND COL_LENGTH('dbo.SysModule', 'RequiredPermission') < 4000BEGIN -- 100 字符存不下权限码;init-schema 不会做这次 ALTER。 ALTER TABLE dbo.SysModule ALTER COLUMN RequiredPermission nvarchar(2000) NULL;END;- 再跑
--seed-only或带种子的演示初始化。
示例 3:已有大表缺一个声明索引
--init-schema 只记残留,不 CREATE INDEX。/host/schema 生成的审阅脚本在 SQL Server 上带:
CREATE INDEX [IX_Orders_Region] ON [sales].[Orders]([Region]) WITH (ONLINE = ON, MAXDOP = 1, RESUMABLE = ON);企业版/开发版可在线建;标准版请删掉 WITH 子句,放到维护窗口。行数估计超过 10 万时脚本会主动中止。
禁止「先 DROP 全部声明索引再 CREATE」。若必须替换某个索引:先用新名字 ONLINE 建 → 校验 → 再 DROP 旧索引。
发版后 Host-Admin 通知
真正嗅探跑在 API Host 的 SchemaDriftNotifyHostedService(Catalog 名 operations-schema-drift-notify),默认每 15 分钟一次。它使用与 /host/schema 同一套全量实体目录和 host-admin 收件端口。
JobHost 仍登记同名 Quartz 适配器,以满足 Catalog 完整绑定;但 JobHost 没有全量模型目录和 Authorization 收件端口,执行时会 fail-soft 跳过,避免半套模型报假干净。不要为了这份通知去扩大 JobHost CompositionInclude。
- 无漂移:保持静默。
- 有残留:向 TenantId=
0的host-admin角色成员发聚合站内信,链接/host/schema。 - 失败 fail-soft,不影响 API 和 JobHost 健康。
- 可用
Operations:SchemaDriftNotify:Enabled=false或BackgroundJobs:operations-schema-drift-notify:Enabled=false关闭。 - 也可随时打开
/host/schema或运行:
dotnet run --project src/Tooling/BitzOrcas.SchemaMaintenance -- --check-schema种子数据
CSV seed 使用 Storageable upsert,可重复执行但不会自动删除 CSV 中已移除的旧行。删除和归档必须是显式迁移或运维动作。
退出码
| 码 | 含义 |
|---|---|
| 0 | 成功 |
| 1 | 初始化异常 |
| 2 | 生产持久化 adapter 未启用,常见于连接串/RabbitMQ 配置不全 |
| 3 | 至少一个种子步骤失败 |
| 130 | 用户中断 |
另见
当前自动化边界
仓库的 scripts/database/migrations/*.sql 是按文件名排序的人工迁移集。当前没有通用 migration ledger 或 runner 自动记录某个环境已执行文件、Checksum、操作者和时间。发布系统必须在外部受控流程中维护这份证据,不能仅凭“脚本目录存在”判断数据库已升级。
22 个现有脚本主要保护统一聚合、owner rows、审计时间类型和平台持久化变更;相关 Architecture tests 只证明关键脚本仍存在,不证明它已在目标数据库执行。
安全的 expand/contract
- Expand:新增可空列/新表/兼容索引,不删除旧形态。
- Deploy:新旧副本都能读写兼容 schema。
- Backfill:按租户和稳定主键分批,记录进度与失败。
- Switch:读路径切到新形态,观察慢查询与差异。
- Contract:观察期后才删除旧列、旧索引或兼容代码。
-- ① 示例迁移应自检对象存在性,重复执行不会无条件破坏数据。IF COL_LENGTH('dbo.SandboxNote', 'NormalizedName') IS NULLBEGIN ALTER TABLE dbo.SandboxNote ADD NormalizedName nvarchar(200) NULL;END;
-- ② 回填与 NOT NULL 收缩分成不同发布批次,先验证所有租户无空值。这段 SQL 只演示 expand 形状,不对应当前待执行脚本。真实迁移必须包含 provider、schema、锁表影响、回滚/前向修复和数据验证。
发布前后核对
- 备份文件完成 VERIFYONLY,并有最近真实恢复演练;
- 未应用脚本按名称、Checksum、审批与目标环境登记;
- 长事务、锁、索引空间和复制/AG 影响经过评估;
- API/JobHost 混合版本能运行在 expand schema;
- 回填按租户可暂停、可续跑、可审计;
- 切流后核对行数、不变量、Outbox、作业和关键查询;
- contract 变更直到观察期结束才执行。
规划缺口
平台需要补充带 Checksum 的 migration ledger、受控 runner、dry-run/plan、并发锁与发布报告。完成前,这些职责属于部署流水线和 DBA,不应在说明书中宣称框架自动完成。