BitzOrcas.DatabaseMaintenance 复用 Framework 的 SqlSugarDatabaseBackupService 与 SqlSugarTableExportService,提供 SQL Server 全量/差异/日志备份、RESTORE VERIFYONLY、带安全门恢复,以及单表 CSV/JSON Lines 导出。它是高权限运维入口,不是普通开发 CLI。
操作与风险
Restore 会覆盖同名数据库且使用 WITH REPLACE,属于不可逆高风险操作。Verify 只验证备份集可被 SQL Server 读取,不等于完成恢复演练。Export 使用 SELECT *,可能包含全部 PII/Secret。
配置与最小权限
CLI mode 和参数由手写 parser 读取。连接串优先 --connection,否则 Configuration 读取 ConnectionStrings:Default;backup/export directory 与 database name 可来自 DatabaseBackup section。Provider 顺序为 appsettings、local、DBMAINT_ environment、命令行。
{ "ConnectionStrings": { "Default": "Server=sql-ops;Database=BitzOrcas;User Id=bitz_ops;Password=Secr3t!StrongP@ss;TrustServerCertificate=True" }, "DatabaseBackup": { "BackupDirectory": "/var/opt/mssql/backup/bitzorcas", "ExportDirectory": "/secure-export/bitzorcas", "DatabaseName": "BitzOrcas" }}配置保存在 gitignored appsettings.local.json。Backup/Restore 账号需要 SQL Server 对应权限;Export 尽量使用只读账号。不要为方便给日常应用连接串授予 restore 权限。
文件系统拓扑是硬约束
BACKUP DATABASE [<database>] TO DISK 路径由 SQL Server 服务进程解释,但工具随后使用本机 FileInfo/File.Exists 读取同一路径。因而 backup directory 必须同时对 SQL Server 与 CLI 进程可见,且路径字符串一致;远程 SQL Server + 开发机本地目录通常不满足。
容器、Kubernetes 或远程 SQL Server 应挂载共享受控卷,并先用非生产小库验证读写身份、UID/GID、SELinux 与路径。工具不会把 .bak 从数据库主机下载到 CLI 主机,也不会上传到对象存储。
全量备份
# 先在隔离环境验证路径与权限;默认 backup type 为 Full。dotnet run --project src/Tooling/BitzOrcas.DatabaseMaintenance -- \ --backup-database \ --connection "$DB_MAINT_CONNECTION" \ --backup-dir /var/opt/mssql/backup/bitzorcas \ --database BitzOrcas \ --backup-type FullFull 使用 BACKUP DATABASE [<database>] TO DISK = '<path>' WITH FORMAT, INIT, SKIP, STATS=10,文件名为 <database>_full_<UTC timestamp>.bak。INIT 初始化新生成的目标文件;timestamp 只有秒级,避免并发运行写同一名称。
成功输出文件名、大小与耗时。日志中的 Password/Pwd 会掩码,但运行日志和备份路径仍是敏感运维信息。
差异与日志备份
# 差异备份依赖有效 full base;工具不替你验证整条恢复链。dotnet run --project src/Tooling/BitzOrcas.DatabaseMaintenance -- \ --backup-database --backup-type Differential \ --connection "$DB_MAINT_CONNECTION" --backup-dir "$BACKUP_DIR"
# Log 备份要求 FULL/BULK_LOGGED recovery model。dotnet run --project src/Tooling/BitzOrcas.DatabaseMaintenance -- \ --backup-database --backup-type Log \ --connection "$DB_MAINT_CONNECTION" --backup-dir "$BACKUP_DIR"数据库为 SIMPLE recovery 时,Log backup 返回成功但标记 Skipped=true,CLI 仍退出 0 并打印跳过原因。自动化必须解析/监控 skip,不能把 code 0 当作日志链持续健康。
验证备份
--backup-file 只能是备份目录内的纯文件名;包含 ..、/、\ 或路径成分会被拒绝。服务规范化 full path 并验证仍在 root 内。
# VERIFYONLY 不修改目标数据库,但仍需 SQL Server restore 权限和文件可见性。dotnet run --project src/Tooling/BitzOrcas.DatabaseMaintenance -- \ --verify-backup \ --connection "$DB_MAINT_CONNECTION" \ --backup-dir "$BACKUP_DIR" \ --backup-file BitzOrcas_full_20260716_020000.bak底层执行 RESTORE VERIFYONLY FROM DISK = '<path>' WITH STATS=10。验证通过 code 0;SQL 异常会转换成 IsValid=false,CLI code 2。VERIFYONLY 不验证 RTO、应用 smoke、登录/权限、后续差异/日志链或业务数据正确性。
恢复安全门
Restore 需要 --confirm RESTORE 精确匹配。工具先对当前数据库执行 full pre-restore backup,再用指定文件执行 RESTORE DATABASE [<database>] FROM DISK = '<path>' WITH REPLACE, RECOVERY, STATS=10。
# 仅在隔离恢复目标执行;命令本身没有 dry-run。# file 只能是目录内纯文件名;database 必须再次确认不是生产库。dotnet run --project src/Tooling/BitzOrcas.DatabaseMaintenance -- \ --restore-database \ --connection "$ISOLATED_RESTORE_CONNECTION" \ --backup-dir "$BACKUP_DIR" \ --backup-file BitzOrcas_full_20260716_020000.bak \ --database BitzOrcas_RestoreDrill \ --confirm RESTORE如果 pre-restore snapshot 成功而 restore 失败,snapshot 会保留,CLI code 2。操作员必须记录部分状态,不能假设数据库未受任何影响。
恢复演练
- 把完整 backup chain 复制/挂载到隔离 SQL Server。
- 运行 VERIFYONLY。
- 使用新数据库名或专用实例恢复,避免生产名称误连。
- 记录开始/结束、文件大小、SQL Server 版本、RTO。
- 执行 schema drift、seed、登录、核心 API、JobHost 与消息 smoke。
- 对关键表计数/校验和与源快照比较。
- 验证账号、权限、加密 key、外部连接和 broker 不会指向生产。
- 记录恢复失败和 pre-restore snapshot 的回滚步骤。
只有完成真实 restore + application validation,才能证明备份可用。
单表导出
# CSV 实际使用 ^ 分隔;JSON 选项输出 .jsonl,每行一个对象。dotnet run --project src/Tooling/BitzOrcas.DatabaseMaintenance -- \ --export-table \ --connection "$READONLY_EXPORT_CONNECTION" \ --export-dir "$SECURE_EXPORT_DIR" \ --table SysPermission \ --format json表名白名单要求字母/下划线开头、仅字母数字下划线、1–128 字符,并拒绝分号、斜杠、反斜杠和 ..。工具再通过 INFORMATION_SCHEMA.TABLES 验证存在性。
当前只支持无 schema 前缀表名,查询固定 SELECT * FROM [table],不支持列选择、WHERE、tenant filter、脱敏或行数上限。它会流式读取避免整表入内存,但磁盘、网络和数据库扫描成本仍可能很大。
CSV 与 JSONL 语义
CSV 使用 ^ 分隔、header、一行一记录;换行替换为空格,null 输出空串,DateTime/DateTimeOffset 使用 O 格式。当前 formatter 不对字段中的 ^ 做引用/转义,因此含该字符的数据可能破坏列结构。
JSON 输出扩展名 .jsonl,每行一个对象,保留 null 与基础 SQL 值。两种格式都没有字段级脱敏或加密;导出目录必须权限隔离、加密存储并有到期清理。
# 检查权限、文件类型和行数;不要把样本数据输出到公共 CI 日志。find "$SECURE_EXPORT_DIR" -maxdepth 1 -type f -lswc -l "$SECURE_EXPORT_DIR"/*退出码
| Code | 含义 |
|---|---|
| 0 | 操作成功,或 SIMPLE recovery 下 Log backup 被跳过 |
| 1 | 缺 mode/connection/file/table、无效 type/format |
| 2 | 备份/恢复/验证/导出业务失败,或确认 token 拒绝 |
| 3 | Ctrl+C 取消;检查部分文件/恢复状态 |
| 99 | 未处理致命异常 |
取消备份/导出可能留下不完整文件;当前 CLI 不自动删除。使用者应按运行 ID 标记并隔离失败制品。
安全与运维清单
- 工具 Commit、SQL Server 实例、database name 与目录均二次确认;
- Backup/Restore 与 Export 使用不同最小权限身份;
- SQL Server 与 CLI 共享同一受控 backup path;
- 每个 backup 记录 hash、size、type、base/chain 与异地副本;
- VERIFYONLY 后仍完成真实恢复演练与应用 smoke;
- Restore 已隔离连接、确认拓扑并准备失败回滚;
- Export 表经过数据 owner 批准,PII/Secret 有脱敏或禁止导出;
^字符、schema 名、超大表等限制已预检;- code 0 的 skipped Log backup 有独立告警;
- 失败/取消产生的部分文件不会进入保留链。