我们做流程引擎开发时最怕的不是写代码而是出了问题看不懂数据库里那几十张表到底装了什么东西。Activiti 从早期版本到现在核心的数据库表基本固定为 25 张而今天我要聊的是这其中的第 9 张表也是流程定义的核心载体ACT_RE_PROCDEF。如果你曾经部署了 BPMN 文件却在运行时死活找不到对应的流程实例或者想清理掉历史版本却发现版本号混乱那这张表你一定绕不过去。这篇内容我会从表结构、数据产生机制、常用查询到踩坑排除一条线讲清楚适合正在做 Activiti 二次开发、运维排障的同事参考。1. Activiti 数据库整体架构与 25 张表的分区逻辑1.1 四大核心表前缀到底在守什么阵地Activiti 的表命名非常有规律前缀直接告诉你这张表属于哪个模块。ACT_RE_ 是存储资源类型的RE 就是 repository放的是部署记录、流程定义、模型等静态信息ACT_RU_ 是运行时表RU 是 runtime比如正在执行的任务、执行实例、变量这些动态数据ACT_HI_ 是历史表HI 是 history流程跑完之后留下的痕迹都在这ACT_ID_ 是身份表ID 就是 identity用户、组、以及成员关系。另外还有 ACT_GE_ 这种通用表存储字节数组和全局属性。我们常说的 25 张表就是这四大类里规模比较平衡的一个统计口径。实际不同版本会多一些或少一些比如新增了事件订阅表、作业表等但 ACT_RE_PROCDEF 从 5.x 到 6.x 一直存在字段变化也不大所以它非常值得你花功夫吃透。1.2 为什么必须搞清楚这 25 张表很多同事一开始只抱着 API 调用的思路写代码用 RepositoryService 部署流程、用 RuntimeService 发起流程一切正常但一遇到问题就抓瞎。比如明明部署成功前端却不显示流程定义比如流程修改后重新部署旧版本占满了列表甚至有人直接去数据库删记录删完系统直接报错。这种困境的根本原因是你没有建立起表与表之间是怎么协作的心智模型。ACT_RE_PROCDEF 握着一把钥匙它关联着部署表 ACT_RE_DEPLOYMENT 和运行时表 ACT_RU_EXECUTION还是历史表 ACT_HI_PROCINST 的数据来源。搞清楚它的结构和行为后面排查任何流程定义相关的问题都会轻松很多。1.3 本博文涉及的表间关系预读在深入字段之前你先记住一条主线部署一个 BPMN 文件会在 ACT_RE_DEPLOYMENT 插入一条部署记录同时在 ACT_RE_PROCDEF 插入至少一条流程定义记录。流程定义记录会记录它来自哪个部署版本以及它对应的资源文件名。当你发起流程时系统会根据流程定义的 KEY 和当前激活版本在 ACT_RU_EXECUTION 中创建根执行实例并且这个执行实例的外键会指向 ACT_RE_PROCDEF 的 ID。所以 ACT_RE_PROCDEF 你可以把它想象成产品型号登记簿它不记录具体某一个产品流程实例它只记录有哪些型号、什么版本、当前状态如何。具体的产品流水账在运行表和历史表里。2. ACT_RE_PROCDEF 表结构与字段逐项拆解2.1 主键与版本字段——怎么区分同一个流程的不同迭代先看最核心的几个字段。ID_ 是字符串主键看起来像 myProcess:1:12504 这种格式实际上是把 KEY、版本、随机序号拼接出来的为了保持全局唯一。REV_ 是乐观锁版本号每次对记录做修改操作它都会自增用来防止并发更新冲突。KEY_ 是流程定义的业务标识这个非常重要。你在 BPMN 文件的 process 元素上定义的 id 属性就会写到这个字段里。比如process idleaveProcess那么 KEY_ 就是 leaveProcess。NAME_ 是流程定义的名称对应 process 元素的 name 属性。VERSION_ 是版本号它决定了同一个 KEY 下的新旧关系。当部署一个同 KEY 的新版本时Activiti 会自动查出当前最大版本号然后加一。这里有个很多人误解的地方VERSION_ 不是全局递增的而是针对同一个 KEY 内部递增。不同 KEY 的流程可以各自有版本 1互不干扰。2.2 部署关联字段——找到这个流程定义从哪来DEPLOYMENT_ID_ 指向 ACT_RE_DEPLOYMENT 表的主键它告诉我们这个流程定义是哪个部署包带来的。如果同一个部署包里有多个 BPMN 文件那么每个文件都会在 ACT_RE_PROCDEF 中生成一条记录但这些记录的 DEPLOYMENT_ID_ 是相同的。RESOURCE_NAME_ 是 BPMN 文件的资源路径通常是类路径下的一个字符串比如 processes/leave.bpmn20.xml。DGRM_RESOURCE_NAME_ 是流程图的图片资源路径如果你在部署时生成了流程图这里就有值。如果你只是部署了一个 BPMN 文件而没有生成图片这个字段可能是空的。CATEGORY_ 字段来源于 BPMN 文件的 targetNamespace 属性你可以用它来给流程分组。比如不同业务线的流程targetNamespace 写不同的值查询的时候就能按分类过滤。2.3 状态与表单字段——流程定义是活是举SUSPENSION_STATE_ 表示流程定义的挂起状态1 是激活2 是挂起。挂起状态下你不能基于这个流程定义发起新的流程实例已经发起的实例会继续执行除非你把流程实例也挂起。这个状态位是排障时最容易忽略的。HAS_START_FORM_KEY_ 是个布尔字段表示这个流程定义是否有开始表单的标识。HAS_GRAPHICAL_NOTATION_ 表示是否包含图形化信息它会影响历史流程实例能不能在前端展示流程图。TENANT_ID_ 是租户 ID适用于多租户场景。如果你没有用到多租户这个字段是空的。ENGINE_VERSION_ 是引擎版本字段特殊情况下才会用到。另外还有一个 DESCRIPTION_ 字段保存流程定义的描述信息。3. 流程定义的生命周期与表数据是怎么产生的3.1 从 BPMN 文件到 ACT_RE_PROCDEF 的部署链路当你调用 repositoryService.createDeployment().addClasspathResource(processes/leave.bpmn20.xml).deploy() 时Activiti 会做两类事情。第一类是把文件内容存到 ACT_GE_BYTEARRAY同时在 ACT_RE_DEPLOYMENT 插入一条记录。第二类就是解析这个 BPMN 文件提取出流程定义的信息插入到 ACT_RE_PROCDEF。这里要注意解析时机。Activitli 支持自动解析和懒加载两种模式。默认情况下部署时会自动解析 BPMN 文件也就是立刻生成流程定义记录。如果你设置了 isBpmn20XMLValidationEnabled 或者一些延迟解析的配置可能部署后流程定义还没马上生成而是等到第一次发起流程时才解析。我遇到过几次这类问题就是因为大项目里有人改了配置部署了但表里看不到记录。3.2 同一 KEY 不同版本如何共存这个机制是流程引擎最迷人的地方之一。假设你部署了 leaveProcess 版本 1系统插入一条 VERSION_1 的记录。第二天你改了流程重新部署同一个 KEY系统不会覆盖旧记录而是插入一条 VERSION_2 的新记录。旧记录依然存在只是它的状态可能变成非激活。怎么判断当前激活的是哪个版本答案是 VERSION_ 最大的那条就是默认激活版本除非你手动把它挂起或者有特殊配置。发起流程时默认使用当前激活版本除非调用 startProcessInstanceById 指定某个具体版本的 ID。这里有个实操经验很多时候我们想在同一个环境里同时跑新旧流程让存量工单走旧逻辑新工单走新逻辑。简单的方式就是记住旧版本的流程定义 ID发起流程时按 ID 指定版本。但要注意如果新版本挂起了系统会自动尝试回退到较低版本吗不会它只会报错说流程定义被挂起。所以不要依赖自动回退要自己做好版本规划。3.3 挂起与激活状态在表中如何体现操作流程定义挂起有两个层级挂起流程定义和使用流程定义挂起流程实例。数据库中 SUSPENSION_STATE_ 字段直接反映层级。当值为 1 时流程定义是激活的可以发起新实例值为 2 时流程定义被挂起不能发起新实例。如果你想全部挂起已经发起的未完成流程实例需要调用 suspendProcessDefinitionById 并设置 suspendProcessInstances 参数为 true。这么操作会同时更新 ACT_RE_PROCDEF 的状态以及所有关联运行中的 ACT_RU_EXECUTION 的状态。所以你在排查问题时如果发现所有新的流程实例都启动不了第一步先去查 ACT_RE_PROCDEF.SUSPENSION_STATE_ 是不是变成了 2。4. 实操基于 ACT_RE_PROCDEF 的常用查询与运维脚本4.1 查询当前所有流程定义最基础也是最高频的查询就是列出所有流程定义及其版本信息SELECT ID_, KEY_, NAME_, VERSION_, DEPLOYMENT_ID_, SUSPENSION_STATE_ FROM ACT_RE_PROCDEF ORDER BY KEY_, VERSION_这个查询能让你快速掌握当前系统里有哪些流程、每个流程有几个版本、当前部署的新旧关系。实际排查时我会习惯加上 WHERE 条件按 KEY 过滤SELECT * FROM ACT_RE_PROCDEF WHERE KEY_ leaveProcess ORDER BY VERSION_ DESC;这样能立刻看到这个流程的所有历史版本记录特别是最上面的那条就是当前激活版本。4.2 对比不同版本的流程定义有时候你需要知道两个版本之间 BPMN 文件本身有什么差异。ACT_RE_PROCDEF 里只存了元信息文件内容在 ACT_GE_BYTEARRAY 中。你可以先用 RESOURCE_NAME_ 找到对应的字节数组记录然后取出内容进行文本对比。常见的做法是导出历史版本的 BPMN 文件用 diff 工具对比新的文件。比如SELECT d.ID_, d.NAME_, p.KEY_, p.VERSION_, p.RESOURCE_NAME_ FROM ACT_RE_DEPLOYMENT d JOIN ACT_RE_PROCDEF p ON d.ID_ p.DEPLOYMENT_ID_ WHERE p.KEY_ leaveProcess ORDER BY p.VERSION_;然后用 Java 代码从 bytearray 里还原 XML或者直接查数据库把 BLOB 字段导出到本地。这里提醒一句不要直接对数据库里的 XML 做胡乱的修改即使你正确配置了命名空间Activiti 缓存也会让你摸不着头脑。4.3 清理无用流程定义的正确姿势时间长了ACT_RE_PROCDEF 里会堆满各种历史版本。很多新手直接执行 DELETE 想把旧版本删掉结果系统启动后报找不到流程定义。原因是你只删了流程定义表没有清理部署表和字节数组表而且内存中的缓存还是旧数据。正确的清理方式是调用 APIrepositoryService.deleteDeployment(deploymentId, true)。第二个参数表示是否级联删除运行中的流程实例。如果你确定这个流程定义没有任何运行中的实例可以用这个 API 安全删除。它内部会先删除关联的流程定义记录再删除部署记录和字节数组记录同时清理缓存保证一致性。如果你确实没办法用 API只能直接操作数据库那你要记住三个动作删除 ACT_RU_EXECUTION 中关联的实例如果没有的话、删除 ACT_RE_PROCDEF 对应记录、删除 ACT_RE_DEPLOYMENT 对应记录最后再去 ACT_GE_BYTEARRAY 把字节数组删掉。顺序和范围缺一不可。5. 常见问题与排查技巧实录5.1 部署成功但 ACT_RE_PROCDEF 查询不到这种情况我会先检查有没有配置延迟解析。如果部署接口返回时没有任何异常但表里没有记录大概率是解析时机被改成了懒加载。你可以读取部署对象后再调用 repositoryService.createProcessDefinitionQuery().deploymentId(deploymentId).singleResult()看看能不能在代码里拿到流程定义。如果代码能拿到说明只是查数据库的时机不对刷新一下再查就有了。如果代码也没有返回就要检查 BPMN 文件本身是否合法。比如 process id 跟已有的定义了冲突或者文件里存在引用不存在的 eventListenerActiviti 会在日志里输出解析错误数据库自然不会有记录。5.2 VERSION 重复导致的问题VERSION_ 字段理论上不会重复除非有人手动更新了它或者数据库表数据被外部工具污染过。一旦出现重复版本你发起流程时会拿最大的版本号但如果有两条记录都是最大版本号系统会随机选一条这会带来线上行为不一致的隐患。排查方法很简单按 KEY 分组统计每个 KEY 下的版本最大记录是否唯一SELECT KEY_, MAX(VERSION_) FROM ACT_RE_PROCDEF GROUP BY KEY_ HAVING COUNT(1) 1;如果查出异常就用 API 删掉不想要的版本对应的部署记录不要直接 UPDATE 版本号因为版本号被许多关联表引用。5.3 表数据与内存缓存不一致Activiti 在运行时会把流程定义缓存到内存主要为了性能。你如果直接改了数据库的 SUSPENSION_STATE_或者删除了流程定义记录内存里可能还是旧状态。这就是为什么我强烈推荐用官方 API 而不是手写 SQL。如果已经手写了 SQL出现缓存不一致试试调用 repositoryService.activateProcessDefinitionById 或者 suspendProcessDefinitionById 来强制刷新状态或者直接重启节点让缓存全部重建。实际排障中重启往往是最快且最安全的兜底方案。5.4 多租户场景下查询不到流程定义多租户开启后ACT_RE_PROCDEF.TENANT_ID_ 会参与查询过滤。如果你查询流程定义时没有指定租户 ID可能返回空结果但表的物理记录是存在的。排查时先确认有没有配置了 tenantId。常见错误是部署时设置了 tenantId查询时却用默认无租户上下文去查。我习惯在代码里统一用 repositoryService.createProcessDefinitionQuery().processDefinitionTenantId(tenantId).list() 来查询这样字段和索引都能对上问题就很好定位。6. 实战心得与建议我把 ACT_RE_PROCDEF 从头到尾梳理下来最深的感受就是只要理解了这张表整个 Activiti 的导航地图就算是打开了半边。它连接着部署、运行时、历史三张网很多疑难杂症最终都指向它的版本或状态字段。另外一个建议是在运维层面建立一个每周巡检习惯重点看 ACT_RE_PROCDEF 里有没有超出 5 个版本的流程定义有没有状态为挂起但业务还在继续发起的流程有没有 KEY 和 NAME 明显不对应的脏数据。这些巡检用我上面的 SQL 就能跑不复杂但非常有价值。最后再分享一个小技巧当你需要快速搞清楚一个生产事故是不是流程定义版本引起的不要只盯着 ACT_RE_PROCDEF要联合 ACT_HI_PROCINST 一起看。历史实例表里的 PROC_DEF_ID_ 字段就是流程定义表的 ID_如果新实例用的版本和预期不一致你能非常清楚地定位到具体是哪个版本记录被系统选中了。掌握这种交叉查询的能力你处理后续的问题会轻松很多。