租户库升级方案.md 8.8 KB

SaaS 租户库升级方案(字段/表结构变更同步)

本文档用于解决 SaaS 模式下「代码发版后,数据库字段和表结构如何同步到各租户库」的问题。
当前项目采用「主库 tenant_info + 每租户独立库」模式,方案基于现有 TenantDataSourceManagerTenantTaskRunnerTenantUtils 能力落地。


一、目标与范围

1.1 目标

  • 每次发版涉及 DDL/DML 变更时,能够自动同步到所有目标租户库。
  • 支持灰度(部分租户)与全量(所有租户)两种执行模式。
  • 支持失败重试、断点续跑、任务审计与结果追踪。

1.2 范围

  • 包含:租户业务库的字段新增/修改、索引、初始化数据、数据回填。
  • 不包含:跨库强事务回滚(多租户不适合做全局事务回滚)。

二、总体设计

采用「版本化脚本 + 租户执行记录 + 主库任务编排」模式:

  1. 脚本按版本管理(VyyyyMMdd_nn__desc.sql)。
  2. 每个租户库记录已执行版本(tenant_db_migration)。
  3. 主库记录升级任务与租户执行明细(tenant_upgrade_tasktenant_upgrade_task_detail)。
  4. 升级时主库查租户列表,按租户切库执行未执行脚本。
  5. 失败租户可单独重试,成功租户不重复执行。

三、数据库表设计

3.1 租户库:迁移记录表(必须)

表名:tenant_db_migration

CREATE TABLE IF NOT EXISTS `tenant_db_migration` (
  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
  `version` VARCHAR(64) NOT NULL COMMENT '脚本版本,如 V20260416_01',
  `script_name` VARCHAR(255) NOT NULL COMMENT '脚本文件名',
  `checksum` VARCHAR(64) DEFAULT NULL COMMENT '脚本内容 SHA256',
  `status` VARCHAR(16) NOT NULL COMMENT 'SUCCESS/FAILED',
  `started_at` DATETIME NOT NULL COMMENT '开始时间',
  `finished_at` DATETIME DEFAULT NULL COMMENT '结束时间',
  `error_msg` TEXT DEFAULT NULL COMMENT '失败错误信息',
  `executor` VARCHAR(64) DEFAULT NULL COMMENT '执行器标识',
  `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_version` (`version`),
  KEY `idx_status` (`status`),
  KEY `idx_started_at` (`started_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='租户库迁移记录表';

3.2 主库:任务总表(建议)

表名:tenant_upgrade_task

CREATE TABLE IF NOT EXISTS `tenant_upgrade_task` (
  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
  `task_no` VARCHAR(64) NOT NULL COMMENT '任务号',
  `scope_type` VARCHAR(16) NOT NULL COMMENT 'ALL/INCLUDE/EXCLUDE',
  `target_versions` VARCHAR(512) NOT NULL COMMENT '目标版本范围或列表',
  `status` VARCHAR(16) NOT NULL COMMENT 'RUNNING/SUCCESS/PARTIAL/FAILED',
  `total_tenants` INT NOT NULL DEFAULT 0 COMMENT '总租户数',
  `success_tenants` INT NOT NULL DEFAULT 0 COMMENT '成功租户数',
  `failed_tenants` INT NOT NULL DEFAULT 0 COMMENT '失败租户数',
  `started_at` DATETIME NOT NULL COMMENT '开始时间',
  `finished_at` DATETIME DEFAULT NULL COMMENT '结束时间',
  `trigger_by` VARCHAR(64) DEFAULT NULL COMMENT '触发人',
  `remark` VARCHAR(500) DEFAULT NULL COMMENT '备注',
  `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_task_no` (`task_no`),
  KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='租户库升级任务主表';

3.3 主库:任务明细表(建议)

表名:tenant_upgrade_task_detail

CREATE TABLE IF NOT EXISTS `tenant_upgrade_task_detail` (
  `id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
  `task_no` VARCHAR(64) NOT NULL COMMENT '任务号',
  `tenant_id` BIGINT NOT NULL COMMENT '租户ID',
  `tenant_code` VARCHAR(64) NOT NULL COMMENT '租户编码',
  `current_version` VARCHAR(64) DEFAULT NULL COMMENT '升级前版本',
  `target_version` VARCHAR(64) DEFAULT NULL COMMENT '目标版本',
  `status` VARCHAR(16) NOT NULL COMMENT 'SUCCESS/FAILED/SKIPPED',
  `error_msg` TEXT DEFAULT NULL COMMENT '失败原因',
  `started_at` DATETIME NOT NULL COMMENT '开始时间',
  `finished_at` DATETIME DEFAULT NULL COMMENT '结束时间',
  `created_at` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_task_tenant` (`task_no`, `tenant_id`),
  KEY `idx_task_no` (`task_no`),
  KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='租户库升级任务明细';

四、脚本规范

4.1 存放目录

fs-service/src/main/resources/db/migration/tenant/

4.2 命名规则

  • V20260416_01__add_column_xxx.sql
  • V20260416_02__init_data_xxx.sql

4.3 编写要求

  • 只增量,不修改历史版本脚本。
  • 脚本必须幂等(IF EXISTS/IF NOT EXISTS、条件插入/更新)。
  • 大数据回填分批处理,避免长事务。
  • DDL 与 DML 拆分,便于排障与重试。

五、应用架构与组件设计

建议在 fs-service 增加以下组件:

  • TenantMigrationScriptLoader:扫描脚本、解析版本、排序、计算 checksum。
  • TenantMigrationRepository:在当前租户库读写 tenant_db_migration
  • TenantMigrationExecutor:单租户执行器,串行执行该租户未执行脚本。
  • TenantUpgradeOrchestrator:主编排器,主库查租户并发起升级,汇总结果写主库任务表。

可复用当前能力:

  • tenant_info:租户主数据来源。
  • TenantDataSourceManager:按租户切换数据源。
  • TenantTaskRunner:顺序/并行按租户执行框架。
  • TenantUtils:执行 SQL 脚本的工具能力(已改为 UTF-8 读取脚本)。

六、升级流程

6.1 发布前

  1. 研发提交本次版本 SQL 到脚本目录。
  2. 自测验证幂等(同脚本重复执行不报错)。
  3. 预发环境先全量演练。

6.2 生产执行

  1. 先灰度(INCLUDE 指定租户)。
  2. 观察锁表、慢 SQL、失败率。
  3. 全量执行(ALL)。
  4. 对失败租户发起重试任务。

6.3 发布后

  1. 导出任务结果(成功数/失败数/失败原因)。
  2. 与应用功能开关联动,确保目标版本达到后再开启新功能入口。

七、接口设计(fs-admin)

7.1 启动升级

  • POST /tenant/upgrade/run

入参建议:

  • scopeTypeALL / INCLUDE / EXCLUDE
  • tenantIds:租户 ID 列表
  • fromVersion:起始版本(可空)
  • toVersion:目标版本(可空)
  • parallel:是否并行
  • threads:线程数
  • remark:备注

7.2 查询任务

  • GET /tenant/upgrade/task/{taskNo}:任务总览
  • GET /tenant/upgrade/task/{taskNo}/failures:失败明细

7.3 失败重试(可选)

  • POST /tenant/upgrade/retry/{taskNo}

八、并发与稳定性策略

  • 默认顺序执行,稳定优先。
  • 租户多时开启并行(建议 4~8 线程起步)。
  • 原则:
    • 租户之间可并行。
    • 单租户内脚本严格串行。
    • 单租户失败不中断全局任务。

执行前检查建议:

  • 数据源连接池最大连接数 >= 并行线程数。
  • 避开业务高峰执行重 DDL。
  • 对高风险脚本先在小租户样本验证。

九、失败处理与回滚策略

9.1 处理原则

  • 不做跨租户全局回滚。
  • 失败租户记录错误并继续其余租户。
  • 修复后按失败清单重试。

9.2 常见失败场景

  • 租户库结构已被手工改过(字段存在/类型不一致)。
  • 权限不足(DDL 权限缺失)。
  • 锁等待超时(与线上写入冲突)。

十、实施步骤(建议分两期)

第一期:可用版(1~2 周)

  1. 建立三张管理表(租户库一张,主库两张)。
  2. 落地脚本扫描与版本比较。
  3. 落地单租户执行器(含记录 SUCCESS/FAILED)。
  4. 落地主编排器(ALL/INCLUDE)。
  5. 提供 run/query 接口。

第二期:增强版

  1. 并行执行与线程控制。
  2. 失败重试接口与告警通知。
  3. SQL 风险扫描(关键字与锁风险)。
  4. 与发布平台/流程联动自动触发。

十一、与当前项目的关系说明

  • 本方案不改变现有「每租户独立库」隔离模型。
  • 不要求现有业务表新增 tenant_id 字段。
  • 方案可与当前 SaaS 登录、定时任务、动态切库实现并行推进。

十二、实施清单(执行版)

  • 创建 tenant_db_migration(租户库)
  • 创建 tenant_upgrade_task(主库)
  • 创建 tenant_upgrade_task_detail(主库)
  • 新建脚本目录 db/migration/tenant
  • 规范脚本命名 VyyyyMMdd_nn__desc.sql
  • 实现脚本加载器与版本排序
  • 实现租户单库执行器
  • 实现主库任务编排器
  • 提供升级触发与查询接口
  • 预发全量演练
  • 生产灰度 + 全量 + 重试