Skip to content
bitzorcas
中EN

Guide

Operations Schema 漂移与迁移安全

讲透声明元数据、漂移检测、三级脚本生成、审批、dry-run、逐句执行、审计和当前缺失的计划绑定与并发控制。

Last updated

Operations 的 Schema 能力是一条“实时检测 → 生成 SQL → 可选执行”的流水线。它适合开发和受控运维,但当前没有不可变迁移计划、预览哈希、执行租约或全有全无事务,不能按传统迁移平台理解。

1. 完整数据流

ActivityAuditSinkMigrationExecutorMigrationGeneratorDriftDetectorModuleAssemblyRegistryHandlerActivityAuditSinkMigrationExecutorMigrationGeneratorDriftDetectorModuleAssemblyRegistryHandleralt[dry-run][execute]Operatordrift / preview / applycollect current metadataDetectAsync(metadata)SchemaDriftReportGenerateScript(report, level)current SQL textApplySafeAsync(script)executed + skippedsuccess auditMigrationResultOperator

每次 preview 和 apply 都重新读取当前编译期 metadata、重新检测数据库、重新生成脚本。两次调用之间的代码、数据库或配置变化会改变结果。

2. 三个只读/执行用例

用例Level副作用
GetSchemaDrift.Query无只返回漂移报告
PreviewSchemaMigration.Query调用方可传三级返回 SQL、数量和级别
ApplySafeSchemaMigration.Command固定 SafeOnlydry-run 或执行
ApplyAllSchemaMigration.Command固定 FullForcedry-run 或执行

PreviewSchemaMigration 允许预览 FullForce,但返回值没有 plan ID、hash、有效期或审批绑定。

3. 声明元数据来源

Handler 调用 ModuleAssemblyRegistry.GetRegistrations(),展开每个注册项的 MetadataByType.Values。这意味着检测依据是组合根实际注册的编译期 ORM 元数据,不是运行时扫描实体特性。

若某模块没有进入 registry,其表不会出现在声明侧;漂移报告必须结合组合 Profile 阅读。

4. 安全级别

Level允许的操作
SafeOnlyADD COLUMN、放宽字符串长度、ADD INDEX
WithConfirm以上 + 收窄长度、改变列类型
FullForce以上 + DROP COLUMN、DROP INDEX

“安全”表示生成器分类,不代表无锁、零停机或对大表无影响。ADD INDEX、带默认值加列仍可能造成锁、日志增长或部署超时。

5. Dry-run 的真实语义

Safe dry-run 的当前实现
// ① 每次请求都读取当前 metadata 和当前数据库漂移。
var report = await detector.DetectAsync(declaredEntities, cancellationToken);
var script = generator.GenerateScript(report, MigrationSafetyLevel.SafeOnly);
// ② DryRun 只阻止执行;没有保存脚本或返回不可变计划标识。
if (request.DryRun)
return new MigrationResult(
[script.ToSqlText()],
script.Statements.Select(x => x.TableName).Distinct().ToList(),
[$"[Dry-run] {script.Count} 条语句未执行"],
clock.UtcNow);

dry-run 把整段 SQL 放进 ExecutedSqls 形状的第一项,语义上是复用了执行结果 DTO。调用方不能把该字段名理解为已经执行。

6. 审批并非所有环境一致

Safe apply 只在 approvalGate.IsApprovalRequired 时校验。API Host 对 Production 与 Staging 返回 true。

FullForce 无条件调用 ValidateAsync,但 Host 的适配器在非强制环境直接返回 true。因此当前效果仍是 Production/Staging 强制,而不是源码注释宣称的所有环境强制。

当强制环境解析不到 IOpsExtensionStore 时,仅凭非空工单号放行;没有校验状态、创建者或变更内容。

7. FullForce 的 Reason 不是必填

ApplyAllSchemaMigration.Command.Reason 是 nullable。Handler 没有空值验证,审计中会写 (未提供)。

如果产品要求破坏性变更必须说明原因,应把非空和长度约束放入请求/领域验证,并纳入审批工单内容绑定。

8. 执行器为什么叫 ApplySafeAsync

ISchemaMigrationExecutor 只有 ApplySafeAsync(MigrationScript)。FullForce Handler 也调用这个方法。执行器不会再次检查 Level,只执行传入脚本;审批与危险级别完全由上游负责。

这不是说 FullForce 被降级为 SafeOnly,而是接口名容易造成错误安全感。商业接口应改成语义中立的 ApplyAsync,并显式验证允许的 Level 与审批证据。

9. 逐句 best-effort 语义

SqlSugar 执行器逐条循环:

  • ADD INDEX 先按 SQL 正则提取索引名,再检查是否已存在;
  • 每条通过 ExecuteCommand 单独执行;
  • 异常写 warning 并加入 SkippedStatements;
  • 继续执行后续语句;
  • 最终总是返回成功 Result<MigrationResult>。

因此“HTTP/Result 成功”只代表执行流程结束,不代表所有语句成功。

10. 正确判定结果

调用方必须检查 skipped
var result = await mediator.Send(
new ApplySafeSchemaMigration.Command(ticket, DryRun: false),
cancellationToken);
if (result.IsFailure)
return ReleaseDecision.Block(result.Error.Code);
var migration = result.Value!;
// ① skipped 可能是幂等跳过,也可能是执行异常,当前只有文本可区分。
if (migration.SkippedStatements.Any(x => x.Contains("失败:", StringComparison.Ordinal)))
return ReleaseDecision.Block("存在失败迁移语句");
// ② 发布证据要保存 executed SQL 与受影响表,而不只保存 HTTP 200。
return await evidence.RecordAsync(migration, cancellationToken);

当前 skipped 是本地化文本而非结构化原因,自动化只能脆弱解析字符串。

11. 事务和并发边界

执行器没有包裹整个脚本的事务,也没有部署级分布式锁。两个实例可以同时检测并执行同一漂移。

只有 ADD INDEX 有专门幂等检查;其他语句依赖数据库结果,失败后被记为 skipped。部分成功是设计现状,回滚必须依靠人工或后续脚本。

12. 审计边界

Safe/FullForce 只在执行器返回 success 后写 ActivityRecord。由于执行器把单句错误转成 success,审计可能显示 IsSuccess=true,同时结果中含失败 skipped。

空脚本和 dry-run 不写活动审计;审批拒绝和执行前异常也不写失败审计。审计只记录计数、表名、原因和工单关联,不保存完整 SQL 或 before/after diff,尽管源码注释声称更强。

13. HTTP 可达性边界

四个 Schema 请求只有 [GenerateEndpoint],手写 OperationsEndpointGroup 没有对应映射。当前生成器不递归静态用例下的嵌套 Query/Command,因此特性本身不足以证明路由可达。

GA 前需要运行真实 Host 的路由清单/HTTP 合同测试,而不是只用源码 rg 检查特性存在。

14. 上线前最小 runbook

  1. 固定应用构建和数据库快照;
  2. 获取 drift 与目标级别 preview;
  3. 评审每条 SQL 的锁、日志、数据截断和回滚影响;
  4. 保存 SQL hash 并由不同人员批准;
  5. 进入单实例/部署锁维护窗口;
  6. 重新检测,若 hash 变化则重新批准;
  7. 执行并检查每条结构化状态;
  8. 再次检测必须无未解释漂移;
  9. 保存 before/after、审批、操作者与数据库标识;
  10. 演练部分成功后的补偿路径。

其中 4—8 的自动绑定能力当前尚未实现。

15. 关键 GA 缺口

  • 不可变 migration plan、数据库指纹、SQL hash 和有效期;
  • 预览、审批、执行三者强绑定;
  • 数据库级或部署级互斥锁;
  • 结构化逐句结果与失败整体策略;
  • 可选全脚本事务/按 DDL 能力分组;
  • 强制 Reason、SoD 和审批 fail-closed;
  • 失败、拒绝、dry-run 与空计划审计;
  • 真实 HTTP、SQL Server 并发与故障合同测试。

16. 测试命令

Terminal window
# Schema 应用层和生成/执行器测试。
dotnet test tests/BitzOrcas.Application.Tests \
--filter 'FullyQualifiedName~SchemaMigration|FullyQualifiedName~SchemaDrift'
# 搜索仍不存在的商业安全原语;当前预期无实质实现。
rg -n "PlanHash|DatabaseFingerprint|DistributedLock|MigrationLease" \
src/Platform/Operations src/Framework -g '*.cs' \
--glob '!**/bin/**' --glob '!**/obj/**'

Operations 总览 · 备份与归档

100%

滚轮或按钮缩放 · 放大后拖动画面 · 双击切换 100% / 200%