数据库合并与流程配置更新
在企业级系统整合的实战中,最让人神经紧绷的场景之一,莫过于将多个独立运行的子系统“缝合”进一个统一平台。这不仅是数据的搬运,更是一场对一致性、可用性和业务连续性的全面考验。尤其是当这些系统各自拥有完整的数据库和复杂的工作流逻辑时,任何一步疏忽都可能导致流程中断、任务丢失,甚至引发严重的生产事故。
我们最近在一个大型集团的数字化治理项目中就遇到了这样的挑战:三个区域子公司的人力资源系统需要并入总部统一平台,每个系统都有自己的流程引擎实例(基于 Flowable)、独立部署的数据库,以及大量正在运行的薪资审批流程。目标很明确——不丢数据、不断流程、不影响用户操作的前提下完成整合。以下是我们从实践中提炼出的一套可复用的技术路径。
核心难点与整体思路
真正的难点从来不在“把表拷过去”,而在于如何处理那些看不见的依赖关系。比如:
- 多个系统的
act_ge_bytearray.ID_都是自增主键,直接合并必然冲突; - 子公司 A 的“财务主管”角色在总部系统里叫“财务负责人”,权限映射错一位,整个审批链就断了;
- 正在走流程的单据,突然换了一套规则,该往哪儿走?
我们的解决策略可以概括为四个阶段:结构统一 → 数据迁移 → 流程重注册 → 实例平滑过渡。每一步都必须带着“回滚思维”去执行。
表结构融合:别让字段差异成为拦路虎
开始之前,第一件事是搞清楚“我们到底有哪些不同”。推荐使用 DBeaver 或 Navicat 的 Schema Diff 功能,逐表比对关键字段类型、索引设置和字符集。
常见的坑集中在几个地方:
- 字符集混用:有的库用utf8,有的用utf8mb4,合并后 emoji 或生僻字会变问号;
- 字段长度不一致:NAME VARCHAR(50)和VARCHAR(64)谁迁移到谁?
- 默认值缺失:新增列没设默认值,老代码读取时抛空指针。
我们曾在一个项目中发现,两个系统的salary_info.NAME一个是VARCHAR(50),另一个是VARCHAR(32)。如果按较小的截断,会导致员工姓名被砍掉一半;若以大的为准,则需对目标库做结构升级。
ALTER TABLE salary_info MODIFY COLUMN NAME VARCHAR(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;对于新增字段,一定要带默认值:
ALTER TABLE imp_info ADD COLUMN IF NOT EXISTS IMPORT_SOURCE VARCHAR(50) DEFAULT 'manual';这类变更脚本必须纳入版本控制。我们在 Git 中建立/db-scripts/merge-phase-1/目录,按顺序存放每一个 SQL 文件,并通过脚本自动编号执行,确保环境间一致性。
数据迁移:ID 冲突是头号敌人
BPM 引擎的核心表如act_ge_bytearray、act_re_procdef等,其ID_字段在整个集群中必须唯一。一旦重复,轻则资源覆盖,重则流程定义错乱。
我们的做法是在迁移时给源库的所有 ID 加前缀,例如来自“华东区”的记录加EAST-,华南区加SOUTH-:
INSERT INTO target_db.act_ge_bytearray ( ID_, REV_, NAME_, DEPLOYMENT_ID_, BYTES_, GENERATED_ ) SELECT CONCAT('EAST-', ID_) AS new_id, REV_, NAME_, CONCAT('EAST-', DEPLOYMENT_ID_), BYTES_, GENERATED_ FROM east_db.act_ge_bytearray WHERE NAME_ LIKE '%.bpmn20.xml%' OR NAME_ LIKE '%.png%' ON DUPLICATE KEY UPDATE REV_ = VALUES(REV_), BYTES_ = COALESCE(VALUES(BYTES_), BYTES_);注意这里不仅改了ID_,连DEPLOYMENT_ID_也要同步修改,否则关联关系会断裂。这种批量替换虽然繁琐,但能从根本上避免主键冲突。
小技巧:可以用临时视图先预览转换后的 ID 是否合规,避免拼接出非法字符。
所有写操作建议包裹在事务中,尤其是跨库同步时:
START TRANSACTION; -- 执行多条 INSERT ... SELECT -- ... COMMIT; -- 出错则 ROLLBACK同时保留源库快照,哪怕只是 mysqldump 的压缩包,关键时刻能救命。
流程定义重新部署:不只是上传文件那么简单
很多人以为把.bpmn20.xml文件传到新系统就算完事,其实这才刚起步。真正的挑战是如何让这些流程“活”起来,且不干扰已有业务。
首先,必须人工校验 BPMN 文件的语义正确性。我们使用 BPMN.IO Modeler 打开每个文件,重点检查:
- 用户任务的assignee或candidateGroups是否引用了已存在的用户体系;
- 条件表达式中的变量名是否与其他流程冲突(比如${amount}vs${money});
- 多实例节点的循环逻辑是否合理,有没有死循环风险。
确认无误后,通过管理后台或 API 触发部署。系统会在三张核心表中生成记录:
| 表名 | 作用 |
|---|---|
act_re_deployment | 记录一次部署行为 |
act_re_procdef | 存储流程定义元信息 |
act_ge_bytearray | 存放 XML 和流程图图片 |
手动插入示例如下:
-- 先创建部署记录 INSERT INTO act_re_deployment (ID_, NAME_, CATEGORY_, DEPLOY_TIME_) VALUES ('deploy-salary-v2-2024', '薪资流程V2', 'hr-processes', NOW()); -- 再注册流程定义 INSERT INTO act_re_procdef ( ID_, NAME_, KEY_, VERSION_, DEPLOYMENT_ID_, RESOURCE_NAME_, HAS_GRAPHICAL_NOTATION_ ) VALUES ( 'salaryProcess:2:7890', '薪资审批流程 V2', 'salaryProcess', 2, 'deploy-salary-v2-2024', 'salary_approval_v2.bpmn20.xml', 1 );此时新流程已可启动,但老实例仍指向旧版本。这就引出了最关键的一步:运行时实例的衔接策略。
运行中流程怎么办?两种选择,各有利弊
假设某员工上周提交的调薪申请还在“部门经理审批”环节,今天系统升级了流程图——原来下一步是“HR复核”,现在变成了“预算委员会评审”。这个实例该怎么走?
我们有两种选择:
方案一:自然终结(推荐)
保持原有实例继续沿用旧版流程定义,直到它自然结束。新发起的流程才使用新版。
优点是安全稳定,无需干预运行状态;缺点是新旧逻辑并存,可能造成业务理解混乱。
查询当前未完成的实例:
SELECT PROC_INST_ID_, PROC_DEF_ID_, START_TIME_ FROM act_hi_procinst WHERE PROC_DEF_KEY_ = 'salaryProcess' AND END_TIME_ IS NULL;只要不强制干预,这些实例会自动绑定到原来的proc_def_id上继续执行。
方案二:强制迁移(高风险)
调用流程引擎提供的“流程切换”API(如 Flowable 的RuntimeService.changeDeploymentId()),将执行流挂接到新定义上。
⚠️警告:仅适用于新旧流程结构高度兼容的情况,比如只修改了文字说明或增加了非关键分支。一旦节点 ID 变化或路由条件改变,极易导致任务卡住或跳转错误。
我们一般只在灰度发布阶段用于测试验证,生产环境慎用。
配置同步:别忘了前端看得到的东西
流程能跑通,不代表功能就完整了。很多前端菜单、按钮权限、下拉选项都依赖系统字典表。如果这部分没同步,用户登录后会发现“我的待办不见了”或者“无法选择审批人”。
以sys_dict_single为例,合并时采用ON DUPLICATE KEY UPDATE避免重复插入:
INSERT INTO sys_dict_single (ID, TYPE_ID, VALUE, LABEL, SORT) VALUES ('dict-salary-01', 'todo_type', 'salary_review', '薪资复核', 5), ('dict-finance-02', 'staff_position', 'finance_mgr', '财务主管', 6) ON DUPLICATE KEY UPDATE LABEL = VALUES(LABEL), SORT = VALUES(SORT);完成后必须刷新缓存。如果是 Spring Boot 应用,可通过注解清除本地缓存:
@CacheEvict(value = "sysDictCache", allEntries = true) public void clearDictionaryCache() { log.info("系统字典缓存已清空"); }此外,建议通过消息队列广播一条“配置更新”事件,通知所有在线用户重新加载权限,避免因缓存延迟导致操作失败。
验证不是走过场,而是最后一道防线
所有变更完成后,必须进行闭环验证,不能只看“有没有报错”。
我们通常执行三步检查:
查定义数量
sql SELECT KEY_, COUNT(*) AS version_count FROM act_re_procdef WHERE KEY_ = 'salaryProcess' GROUP BY KEY_;
确保返回预期版本数(如 v1 和 v2 同时存在)。验资源完整性
sql SELECT NAME_ FROM act_ge_bytearray WHERE DEPLOYMENT_ID_ = 'deploy-salary-v2-2024';
输出应包含.bpmn20.xml和对应的.png图像文件。跑真实流程
在 Web 控制台手动发起一条新流程,观察:
- 任务是否正确分配给指定用户?
- 条件路由是否根据金额准确分流?
- 历史记录能否完整追踪?
只有这三个环节全部通过,才能宣布合并成功。
经验沉淀:哪些事我们不会再犯第二次
回顾整个过程,有几个教训值得铭记:
永远不要相信“结构一样”
即使两个表名字相同,也必须逐字段比对。我们曾因忽略DATETIME和TIMESTAMP的时区处理差异,导致流程超时提醒提前触发。ID 前缀要统一规划
别随便用A-,B-,最好有命名规范,比如{region}-{type}-{seq},便于后期排查。小批量、分批次导入
一次性导入百万条记录容易锁表超时。建议按时间分区或按业务模块拆分,每次处理几千条,配合事务提交。操作要有审计日志
每次变更记录谁在什么时候做了什么,影响了多少条数据。这不是为了追责,而是为了快速定位问题。双写过渡期很有必要
在正式切换前,可以让新旧系统并行运行一段时间,关键操作同时写入两边,对比结果一致性后再停用旧系统。
写在最后
数据库合并和流程配置更新,本质上是一场精密的外科手术。它要求你既能看到宏观架构,又能精准操作每一根“血管”和“神经”。没有万能工具,也没有一键方案,唯有周密计划、充分测试、步步为营。
这套方法已在多个 SaaS 化改造、微服务整合及集团 IT 治理项目中落地,帮助客户实现了零数据丢失、零业务中断的平稳迁移。如果你正面临类似的挑战,不妨从这几个动作做起:
- 画出所有源库与目标库的表结构对比图;
- 制定 ID 映射规则并编写转换脚本;
- 在灰度环境完整演练一遍流程迁移;
- 设置变更窗口,通知相关方暂停操作;
- 完成后立即验证 + 回滚预案准备。
技术的价值,往往不在于多么炫酷,而在于它能让复杂的系统,在别人毫无察觉的情况下,悄然进化。