Activiti 25张表全解析:流程引擎表结构设计与排障指南
发布时间:2026/10/17 14:55:07 锦皓数字建站

第一次看到 Activiti 的建表脚本很多人会被 25 张表吓到明明就是跑一个审批流怎么要建这么多表我最初对着数据库愣了半天直到把表按前缀分组、按流程生命周期去理解才意识到这套设计其实很优雅。说白了Activiti 把流程引擎要管的五件事——通用配置、流程定义、运行时状态、历史归档、身份信息——拆成五类表用前缀就能一眼识别。这篇文章没有高深理论只做一件事把 25 张表分类讲透告诉你每张表在流程跑起来时到底扮演什么角色以及排障时该去哪张表捞数据。适用的读者包括刚接触工作流引擎的后端开发、从 Activiti 5 迁移到 6 的维护者以及准备在公司内部推广流程平台、需要设计表归档方案的技术负责人。如果你正被“流程定义在哪张表”“任务为什么没结束”“历史表能不能删”这类问题困扰这篇就按这个顺序来。1. 25张表到底在解决什么问题先别急着背表名要理解 Activiti 为什么需要这么多表得先回到工作流引擎的核心需求。一个流程从建模到跑完至少要经历三个阶段设计期、运行期、历史期。设计期要保存流程定义的原始文件和解剖后的定义数据运行期要记录当前执行到哪个节点、谁在处理、流程变量传了什么值流程结束后这些数据不能直接丢掉审计、统计、对账都要用到所以还要有一份完整的历史记录。Activiti 正是按这个生命周期来设计数据库的。前缀不同数据的性质和删除策略也不同ACT_RE_*Repository流程定义和部署相关的静态数据更新频率低基本跟着版本走。ACT_RU_*Runtime运行期的动态数据流程结束后通常会被清理记录当前“活着的”状态。ACT_HI_*History历史归档数据只增不减是报表和审计的数据源。ACT_ID_*Identity用户、用户组和成员关系提供一套最小可用的身份体系。ACT_GE_*General通用数据既有全局配置也有被部署资源引用的二进制文件。如果把流程引擎比作一家公司ACT_RE_*是挂在墙上的制度文件ACT_RU_*是办公室里正在处理的工单ACT_HI_*是档案室里的历史案卷ACT_ID_*是员工花名册ACT_GE_*则是行政部负责保存公章的扫描件和全局规则。这一分层设计最大的好处是隔离。运行时表必须能够快速写入和查询数据量要控制得越小越好历史表则可以无限增长查询慢一点也能接受。如果所有数据混在一张表里删除和归档就会变成一场灾难。所以引擎刻意把“正在跑的”和“跑完的”分开这是理解 25 张表的第一把钥匙。还有一个容易踩坑的点不同版本的 Activiti 表数量并不完全一样。Activiti 5.x 常见的表接近 23 张Activiti 6.x 把作业相关表拆分之后形成了 25 张业务表到了 Activiti 7 以后云端架构把表结构又做了一次调整。因此下面这张全景图以主流 Activiti 6.x 为准如果你拿到的脚本里多了ACT_RU_BATCH之类的扩展表也不用惊讶那是异步任务增强后新增的附加能力。2. 25张表全景一张表读懂所有表我在刚开始学的时候最缺的是一张“总表”能一次性看到表名、分组、用途和关键字段。现在把 25 张表按五类整理出来建议先看分组再看表名最后再看字段这样记忆负担会小很多。分组表名核心职责一个关键字段通用数据ACT_GE_PROPERTY保存引擎属性和版本号NAME_如schema.version通用数据ACT_GE_BYTEARRAY保存部署资源的二进制内容BYTES_存 BPMN/PNG流程定义ACT_RE_DEPLOYMENT部署包主记录DEPLOY_TIME_部署时间流程定义ACT_RE_MODEL设计器里的模型DEPLOYMENT_ID_关联部署流程定义ACT_RE_PROCDEF解析后的流程定义VERSION_流程版本号运行时ACT_RU_EXECUTION流程实例/执行树PROC_INST_ID_实例 ID运行时ACT_RU_TASK当前待办任务ASSIGNEE_办理人运行时ACT_RU_VARIABLE当前流程变量TASK_ID_关联任务运行时ACT_RU_IDENTITYLINK候选人/候选组关系TYPE_类型运行时ACT_RU_EVENT_SUBSCR事件订阅EVENT_TYPE_事件类型运行时ACT_RU_JOB普通异步作业RETRIES_重试次数运行时ACT_RU_TIMER_JOB定时器作业DUEDATE_触发时间运行时ACT_RU_SUSPENDED_JOB挂起的作业SUSPENSION_STATE_状态运行时ACT_RU_DEADLETTER_JOB死信作业EXCEPTION_MSG_异常信息身份ACT_ID_USER用户ID_用户 ID身份ACT_ID_GROUP用户组ID_组 ID身份ACT_ID_MEMBERSHIP用户和组关系USER_ID_、GROUP_ID_身份ACT_ID_INFO用户扩展信息TYPE_信息类型历史ACT_HI_PROCINST流程实例历史END_TIME_结束时间历史ACT_HI_TASKINST任务实例历史ASSIGNEE_END_TIME_历史ACT_HI_ACTINST活动实例历史ACT_ID_节点标识历史ACT_HI_VARINST变量终值历史TASK_ID_关联任务历史ACT_HI_DETAIL变量变更明细NAME_变量名历史ACT_HI_COMMENT审批意见/备注MESSAGE_内容历史ACT_HI_ATTACHMENT附件信息URL_附件地址看着 25 行很多但实际使用时 80% 的查询都集中在ACT_RE_PROCDEF、ACT_RU_EXECUTION、ACT_RU_TASK、ACT_HI_PROCINST、ACT_HI_TASKINST这五张表上。其余表属于配套存放用到了再深入也来得及。这里也要说一个常见误区很多人以为ACT_RU_TASK就是“流程实例”其实任务的来源是执行实例。一个流程可能产生多个任务也可能处于等待状态只有执行实例没有任务。真正代表流程级别状态的是ACT_RU_EXECUTION中PROC_INST_ID_对应的根执行对象。所以排查“流程卡在哪”的时候先看执行实例再看任务这个顺序别搞反。3. 通用表和流程定义表部署时最先写入这部分表在部署模型时开始产生不是每次流程启动都会动因此相对静态但不代表可以忽略。3.1 两张开头表ACT_GE_PROPERTY与ACT_GE_BYTEARRAYACT_GE_PROPERTY是一张很小的键值表字段无非NAME_、VALUE_、REV_。它最常被问到的是两行数据schema.version和next.dbid。前者记录 Activiti 表结构的版本引擎启动时会做校验如果版本对不上就会报错后者是 Activiti 生成下一条数据库 ID 的游标默认 ID 生成策略就是从这里取值再自增。ACT_GE_BYTEARRAY则是容易被忽视的存储大户。你在流程设计器里画好图部署的时候会把 BPMN 2.0 XML、流程图 PNG、表单定义等文件统一转成二进制存到这张表的BYTES_字段里。每一次部署都会生成新的资源记录旧版本并不会自动删除这就导致它经常悄悄变大。排障时想确认某个流程定义对应的资源文件可以查这段 SQLSELECT d.ID_ AS deployment_id, d.NAME_ AS deployment_name, pd.KEY_ AS process_key, pd.VERSION_ AS version, pd.RESOURCE_NAME_ AS bpmn_file, ba.ID_ AS bytearray_id, ba.NAME_ AS resource_name FROM ACT_RE_DEPLOYMENT d LEFT JOIN ACT_RE_PROCDEF pd ON d.ID_ pd.DEPLOYMENT_ID_ LEFT JOIN ACT_GE_BYTEARRAY ba ON d.ID_ ba.DEPLOYMENT_ID_ WHERE pd.KEY_ leaveProcess;这里需要特别注意ACT_GE_BYTEARRAY既有部署时的源文件也可能被ACT_RE_PROCDEF通过DGRM_RESOURCE_NAME_指向流程图。如果为了省空间直接删这张表的旧数据很可能导致流程定义拿不到解析资源前端查看流程图时直接空白。正确做法是结合部署记录做整体清理而不是孤立地删二进制数据。3.2 三张流程定义表部署、模型、定义ACT_RE_DEPLOYMENT是部署包的大主语。你上传一个 zip 或一次传多个 BPMN 文件引擎会创建一个部署记录得到DEPLOYMENT_ID_。在这条部署下每个 BPMN 文件都会被解析成一条流程定义写入ACT_RE_PROCDEF。ACT_RE_PROCDEF的特点是同一个流程KEY_会存在多个版本VERSION_从 1 开始递增。流程启动时默认使用最新版本但通过processDefinitionKey加版本号可以指定任意版本。字段ACTIVE_标记定义是否挂起挂起状态下不允许发起新的流程实例。ACT_RE_MODEL则偏向设计期。用 Activiti Modeler 在线画的流程图在部署之前会先存到模型表DEPLOYMENT_ID_字段在部署后才会被填充。如果你们的团队不用自带的模型设计器而是用独立工具画图再上传部署这张表基本是空的完全正常。一组典型的数据关系是一个部署记录对应多个流程定义一个流程定义对应一个或两个资源文件一个流程定义会被多个流程实例引用。这个关系理清了后续查“这个线上跑的流程是哪个版本”就不会乱。4. 运行时核心表ACT_RU_*是排障主战场运行时表是 25 张表里最有“活人感”的部分里面每一行都代表当前正在运行的流程状态。这一部分也是调优和排查最需要花时间的地方。4.1 主链ACT_RU_EXECUTION与ACT_RU_TASKACT_RU_EXECUTION有双重身份。当ID_等于PROC_INST_ID_时它是流程实例的根执行对象当PARENT_ID_不为空时它是子执行实例可能对应并行分支、子流程或者嵌套调用。这个设计是理解 Activiti 执行模型的根本也是初学者最容易糊涂的点。任务则是挂在执行实例下的。ACT_RU_TASK中TASK_DEF_KEY_对应 BPMN 里用户节点的idEXECUTION_ID_指向所属执行实例ASSIGNEE_是当前办理人。如果一个任务设置了候选人或候选组候选人关系不会直接写在ASSIGNEE_里而是写到ACT_RU_IDENTITYLINK由TYPE_区分candidate和participant。查询当前某个用户所有待办最基础的 SQLSELECT t.ID_ AS task_id, t.NAME_ AS task_name, t.ASSIGNEE_, t.CREATE_TIME_, e.PROC_INST_ID_, pd.KEY_ AS process_key FROM ACT_RU_TASK t JOIN ACT_RU_EXECUTION e ON t.EXECUTION_ID_ e.ID_ JOIN ACT_RE_PROCDEF pd ON t.PROC_DEF_ID_ pd.ID_ WHERE t.ASSIGNEE_ zhangsan AND t.SUSPENSION_STATE_ 1;注意这里会同时查出任务和流程定义信息。如果带条件查不到数据未必是“没有任务”可能是任务已经被完成只是流程实例还在等待下一步事件这时候要去ACT_RU_EXECUTION看看等待的节点而不是死磕任务表。4.2 变量和身份ACT_RU_VARIABLE、ACT_RU_IDENTITYLINK流程变量是整个流程的“公共内存”。你可以在流程启动时传入参数也可以在节点里设置变量。这些变量的当前值就存在ACT_RU_VARIABLE中。字段VARIABLE_SCOPE_比较关键它表示变量的作用域是流程实例级还是任务级TASK_ID_不为空时表示该变量绑定在某个任务上。变量表最常见的业务用途是“抄送人列表”“审批金额”和“表单数据”。查询某个流程实例的全量变量SELECT NAME_, TYPE_, LONG_, DOUBLE_, TEXT_ FROM ACT_RU_VARIABLE WHERE PROC_INST_ID_ 10001 ORDER BY NAME_;ACT_RU_IDENTITYLINK表面上是身份链接表实际存储的是“谁和当前资源有什么关系”。一个用户任务如果指定了多个候选人和候选组这里会多行记录。很多人查不到候选任务就是因为查了任务表的ASSIGNEE_而不是查这张关系表。4.3 作业家族四张 Job 表为什么会拆开Activiti 5 里只有一张ACT_RU_JOB到了 6.x 被拆成了普通作业、定时器作业、挂起作业、死信作业四张表。拆开的动机很现实不同的作业有不同的生命周期和排查方式混在一张表里很容易互相干扰。ACT_RU_JOB常规异步作业比如异步延续引擎会在后台线程里执行。ACT_RU_TIMER_JOB定时器事件比如超时提醒、自动驳回字段DUEDATE_表示触发时间。ACT_RU_SUSPENDED_JOB作业执行时所在流程实例被挂起作业暂时冻结。ACT_RU_DEADLETTER_JOB作业反复执行失败超过重试次数后进入死信区。作业表出问题通常就是定时器不触发或者死信堆积。优先看RETRIES_和EXCEPTION_MSG_异常信息往往已经写得很明确。清理测试环境的死信作业可以按流程实例删除DELETE FROM ACT_RU_DEADLETTER_JOB WHERE PROC_INST_ID_ 10001;不过生产环境别随手删先搞清楚作业为什么失败否则删了之后问题还会继续产生。4.4 事件订阅ACT_RU_EVENT_SUBSCR信号事件、消息事件在等待触发时会在ACT_RU_EVENT_SUBSCR里留下一行订阅记录相当于引擎在说“我在等某个信号召唤”。EVENT_TYPE_标记事件类型EVENT_NAME_是订阅名称。排障时如果发现流程停在中间节点不动但是任务表里又没有任务建议查一下这张表看是不是在等外部信号。5. 历史表和身份表查询统计与用户体系流程跑完之后运行时数据会逐渐被清理或迁移到历史表。这部分数据对于审计、统计和前端“我的已办”非常重要。5.1 历史七兄弟各司其职ACT_HI_PROCINST是流程实例的历史主表流程启动时写一行流程结束时更新END_TIME_。ACT_HI_TASKINST记录每一次任务的创建和完成换句话说一个流程实例可能对应多条历史任务。ACT_HI_ACTINST更细记录每个节点活动的开始和结束包括用户节点、服务节点、网关节点。前端流程轨迹图的基础数据主要来自这张表。ACT_HI_VARINST保存变量的历史快照查询时可以看到变量最终值。ACT_HI_DETAIL则记录变量每次变化的明细比如“金额从 1000 改成 1200 是谁改的”。如果你的业务不需要这种变更审计可以考虑在引擎配置里关闭明细记录因为这个表的数据增长非常快。ACT_HI_COMMENT和ACT_HI_ATTACHMENT相对独立一个存审批意见一个存附件路径。需要特别提醒的是意见和附件与流程实例、任务实例存在外键关系归档清理历史表时不要只删主表否则外键会报错。查询某个人的已办任务常用语句SELECT t.PROC_INST_ID_, p.PROC_DEF_NAME_, t.NAME_ AS task_name, t.ASSIGNEE_, t.START_TIME_, t.END_TIME_, t.DELETE_REASON_ FROM ACT_HI_TASKINST t LEFT JOIN ACT_HI_PROCINST p ON t.PROC_INST_ID_ p.PROC_INST_ID_ WHERE t.ASSIGNEE_ zhangsan ORDER BY t.END_TIME_ DESC;如果END_TIME_为空就是流程还没走到这个任务节点如果DELETE_REASON_为completed表示任务正常完成为deleted表示被取消或流程终止。这些字段在业务统计时都会用到。5.2 身份四表该用但别被绑架ACT_ID_USER、ACT_ID_GROUP、ACT_ID_MEMBERSHIP、ACT_ID_INFO构成了 Activiti 内置最小身份体系。优点是小项目省事可以直接用ACT_ID_USER作为登录表的底子缺点也很明显字段太少密码策略、用户状态、部门层级都不够用很难直接当统一用户中心用。我见过不少项目把业务系统的用户表与 Activiti 内置身份表并存流程中只使用用户 ID 字符串并不真正同步到ACT_ID_USER。这样做完全可行但要注意如果使用的assignee或candidateGroup在ACT_ID_*表里不存在引擎内部有些查询会查不到对应人员需要额外处理。更稳妥的做法是只使用 Activiti 的 API 传String类型的用户 ID不要依赖它的内置身份服务用户展示名称自己从业务系统查。5.3 历史数据清理不能太随意历史表无限增长是早晚会遇到的问题。一般策略是定期把已完成的流程实例对应的历史数据备份后删除。删除时要注意父子关系建议按照PROC_INST_ID_统一清理而不是孤立删某张表。DELETE FROM ACT_HI_DETAIL WHERE PROC_INST_ID_ 10001; DELETE FROM ACT_HI_VARINST WHERE PROC_INST_ID_ 10001; DELETE FROM ACT_HI_COMMENT WHERE PROC_INST_ID_ 10001; DELETE FROM ACT_HI_ATTACHMENT WHERE PROC_INST_ID_ 10001; DELETE FROM ACT_HI_TASKINST WHERE PROC_INST_ID_ 10001; DELETE FROM ACT_HI_ACTINST WHERE PROC_INST_ID_ 10001; DELETE FROM ACT_HI_PROCINST WHERE PROC_INST_ID_ 10001;顺序上先删明细、再删任务、最后删流程实例能降低外键冲突的概率。更细的做法是给ACT_HI_PROCINST按END_TIME_建索引归档脚本只扫最近一个月完成的数据避免全表扫描。6. 排障实录25张表最常见的坑最后用一个速查表总结我实际维护中踩过的坑问题和解决办法都写在一起定位时直接看对应行。现象可能原因排查思路流程实例明明结束了ACT_RU_EXECUTION还有数据存在未完成的子流程、并行分支或者事件等待按PROC_INST_ID_查所有ACT_RU_EXECUTION看IS_ACTIVE_和节点任务表没有待办但流程不往下走流程在等待信号、消息或定时器查ACT_RU_EVENT_SUBSCR和ACT_RU_TIMER_JOBACT_RU_JOB数据堆着不动异步执行器关闭、死锁或异常重试耗尽查RETRIES_、EXCEPTION_MSG_确认处理器配置明明重新部署了新版本但启动还是旧流程启动时指定了版本或流程缓存在ACT_RE_PROCDEF中版本选择逻辑不对查ACT_RE_PROCDEF的VERSION_和ACTIVE_流程图在页面上不显示部署资源文件缺失或ACT_GE_BYTEARRAY被误删按DGRM_RESOURCE_NAME_查对应资源记录ACT_HI_DETAIL数据量异常大变量频繁更新且开启了明细记录评估是否关闭historicDetails或只保留关键变量删除历史表数据报外键异常删除顺序不对子表还有引用先删明细、变量、评论、附件再删任务、活动最后删流程实例关于最后一条我还想多说一句。有些团队为了控制历史表体积会直接TRUNCATE历史表。这个操作在开发库可以生产环境务必先备份因为它会一次性清掉所有审计数据并且如果运行时还有未结束的流程实例历史主表的记录被清掉后恢复时间和成本都可能非常高。另外做版本升级前最好先对表结构做一次 diff。Activiti 不同小版本之间也会增加字段或索引直接用旧版 SQL 建表后跑新版引擎启动阶段往往就会因schema.version不匹配而报错。稳妥的做法是使用引擎自带的建表策略或者对照官方版本迁移脚本。7. 让25张表变成你的工具箱我现在的维护习惯已经固定下来了先看ACT_RU_EXECUTION有没有残留的根执行实例确认流程是不是真的结束了再看ACT_HI_PROCINST拿流程实例的完整生命周期最后用ACT_GE_BYTEARRAY反查部署资源确认当前跑的定义到底是谁画的版本。至于身份表大多数业务项目不会真正使用不用花太多时间纠结。25 张表真正需要深度掌握的其实只有七八张也就是运行时的执行和任务、历史的主表加明细、部署定义三张、二进制一张。剩下的是配套能力遇到具体问题再逐个击破就好。如果你接下来要做一个工作流平台建议先把部署、启动、查询待办、查询历史这四个场景对应的表结构和 SQL 全部整理一遍后面调接口心里会踏实很多。毕竟代码里经常报错的不是流程 API 不会用而是数据落在表里以后不知道去哪查。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。