☰
Activiti运行时表全解析:核心表字段、关联关系与故障排查实战
2026/10/15 1:43:59 网站建设 项目流程

1. 先搞清楚:什么是 Activiti 的运行时数据表

做工作流开发,最怕就是流程跑到一半卡住了、任务不见了、变量查不到。这种时候你第一个要打开数据库看的,就是那一堆ACT_RU_*开头的表。我当年排查一个“用户任务莫名其妙消失”的线上事故,折腾了两天,最后发现就是卡在ACT_RU_TASK和ACT_RU_EXECUTION的数据关联上——可见这组运行时表虽然平时不显眼,真出了问题它就是唯一的线索。

Activiti 的表结构按前缀分成了几大类,这个你如果之前看过表清单就有印象:

  • ACT_RE_*:Repository,流程定义、流程模型这类静态资源,属于“元数据”,基本不怎么变。
  • ACT_RU_*:Runtime,流程实例执行过程中的动态数据,是本文的主角。
  • ACT_HI_*:History,历史数据,流程跑完、任务办完、变量变更都会在这里留痕。
  • ACT_ID_*:Identity,用户、组、权限关系。
  • ACT_GE_*:General,通用数据,比如二进制内容、属性配置。

运行时数据表的核心特征是“动态”和“临时”。一条流程实例从发起到结束,它的执行路径、当前任务、变量值、定时器状态,都会实时写进这组表里;而流程一旦到达结束节点,这些数据就会被框架自动清理掉,只把历史轨迹留存到ACT_HI_*表。换句话说,ACT_RU_*记录的是“此刻正在发生的事情”,ACT_HI_*记录的是“曾经发生过的事情”。

我在项目中给新人解释这组表的时候,常打一个比方:流程的定义就像一张建筑施工图(ACT_RE_*),开工之后楼盖到哪一层、哪个工位上有工人在干活、材料堆在哪儿,都是施工现场的动态状态——这就是ACT_RU_*。等楼封顶竣工,施工现场要撤场,再把整栋楼的验收档案移交到档案馆,这就是ACT_HI_*。你要是去施工现场找已经封顶的楼层信息,那肯定找不到,同理,去运行时表里查一个已结束流程的数据,也是查不到的。

需要说明一点:我下面讲的内容基于 Activiti 6.x / 7.x 这套表结构。如果你还在用 Activiti 5.x,表名和部分字段会有差异,比如 5.x 里没有ACT_RU_TIMER_JOB这些拆分后的 Job 表,定时器任务都集中在ACT_RU_JOB里。先确认好版本再对照排查,不然会被表名绕晕。

2. 核心运行时表逐一拆解:字段、职责与状态流转

运行时表是一个“表组”,每张表承担一块独立的职责,但它们之间又通过外键紧密关联。我按实际排查问题时的习惯顺序来拆,先把最重要的三张主表讲透,再讲辅助表。

2.1 ACT_RU_EXECUTION:流程实例与执行流的“主躯干”

这张表是整个运行时数据组的核心,也可以说是“总目录”。每启动一个流程实例,这里就会插入一条ID_对应ACT_RE_PROCDEF中流程定义版本的记录,同时PROC_INST_ID_字段会记录流程实例的唯一标识。

关键字段有:

  • ID_:执行实例的唯一标识,一个流程实例可能对应多条执行实例记录。
  • PROC_INST_ID_:流程实例 ID,如果把流程实例比作“整个任务”,ID_比作一条“分支路线”,那同一时刻多条分支会共享同一个PROC_INST_ID_。
  • BUSINESS_KEY_:业务键,用来关联你自己的业务系统单号,比如订单号、工单号。
  • PARENT_ID_:父执行实例 ID。出现子流程、并行网关、多实例会签等情况时,会派生多条子执行流。
  • IS_ACTIVE_:是否存活。1 表示这条执行流正处于活跃状态,0 表示已挂起或等待。
  • IS_CONCURRENT_:是否并发分支。
  • SUSPENSION_STATE_:挂起状态。流程被挂起后,这个字段会从 1 变成 2。

查一个流程当前到底跑到哪个节点、有多少条活跃分支,先看这张表。比如并发网关分出三个分支,你会看到三条PARENT_ID_相同的记录,IS_CONCURRENT_都是 1。等三个分支都汇聚完成,这三条记录会被框架自动删除,只保留主执行实例。

实操中我经常用这条 SQL 定位流程实例当前状态:

SELECT ID_, PROC_INST_ID_, BUSINESS_KEY_, IS_ACTIVE_, IS_CONCURRENT_, SUSPENSION_STATE_, ACT_ID_ FROM ACT_RU_EXECUTION WHERE PROC_INST_ID_ = '你的流程实例ID';

ACT_ID_字段会告诉你这个执行流当前停留在哪个节点 ID,配合ACT_HI_ACTINST历史表能还原出完整路径。

2.2 ACT_RU_TASK:用户任务待办表

这张表是开发感知最强的表。每个待办任务都在这里有一条记录,前端待办列表基本就是查这张表。当你处理完一个用户任务并调用complete()方法后,这条记录会被立即删除,同时可能根据流程定义创建下一个任务记录。

核心字段:

  • ID_:任务 ID,待办列表里传的那个 ID 就是它。
  • NAME_:任务名称,对应流程图中用户任务的名称。
  • ASSIGNEE_:办理人。如果任务创建时没有指定办理人,这个字段为 null,就需要通过候选人或候选组来圈定范围。
  • OWNER_:任务发起人。
  • CREATE_TIME_:任务创建时间。
  • DUE_DATE_:超时时间,配合定时器做催办提醒。
  • EXECUTION_ID_和PROC_INST_ID_:关联到上面的执行实例表。
  • TASK_DEF_KEY_:任务定义 key,对应流程图里用户任务的 id。
  • SUSPENSION_STATE_:任务挂起状态,流程挂起时这里的任务也跟着挂起。

我踩过一个坑:看任务是否被某个用户办理,不能只盯着ASSIGNEE_字段,因为提交审批还好说,有些场景是通过ACT_RU_IDENTITYLINK的候选人机制来分配任务的,这时候ASSIGNEE_是空的。你查“某人有哪些待办”就得带上身份关系表一起 join,否则漏数据。

排查待办任务去向,常用 SQL:

SELECT t.ID_, t.NAME_, t.ASSIGNEE_, t.CREATE_TIME_, t.TASK_DEF_KEY_, e.PROC_INST_ID_ FROM ACT_RU_TASK t LEFT JOIN ACT_RU_EXECUTION e ON t.EXECUTION_ID_ = e.ID_ WHERE t.SUSPENSION_STATE_ = 1 ORDER BY t.CREATE_TIME_ DESC;

2.3 ACT_RU_VARIABLE:流程变量实时快照

流程变量是工作流里的“传参通道”。你提交申请时填的表单数据、审批人填写的审批意见、网关判断用的条件值,都会以变量的形式暂时存进这张表。

字段不多,但类型处理要小心:

  • NAME_:变量名。
  • TYPE_:变量类型,常见的有 string、integer、long、double、boolean、date、serializable。
  • LONG_、DOUBLE_、TEXT_:根据类型分别存数值或文本。查变量值时,要根据类型去对应字段取值,不能统一查TEXT_。
  • EXECUTION_ID_、PROC_INST_ID_、TASK_ID_:变量挂载的作用域。这一点非常关键——变量可以挂在流程实例级别,也可以挂在任务级别。

任务级变量的失效时机是一个经典问题。一个变量如果绑定在任务 A 上,设置方式是通过taskService.setVariable(),那么任务 A 完成时这个变量会被删除;只有挂在execution上(流程实例级)的变量才会跟随整个流程走完。很多新手在网关判断时发现变量丢了,十有八九就是变量作用域挂错了。

再提醒一个点:流程结束后,ACT_RU_VARIABLE中的数据会全量挪到历史表ACT_HI_VARINST,运行时表里不会留任何痕迹。所以想做流程变量留痕查询,得去历史表查。

2.4 ACT_RU_IDENTITYLINK:任务与人的关系表

这张表记录的是“谁和这个任务或流程实例有关系”——是办理人、候选人,还是候选组。它的粒度是关系对:TASK_ID_加USER_ID_或者GROUP_ID_,再加上TYPE_字段区分关系类型。

TYPE_常见的值有:

  • assignee:办理人。这个会同步在ACT_RU_TASK.ASSIGNEE_里出现。
  • candidate:候选人,说明这个任务可以被指定用户认领。
  • participant:流程参与者,一般表示流程实例级别的关联用户。
  • starter:流程发起人。

我自己的经验是,这表看着简单,但在做任务池(Task Pool)功能时候选组查询、待办聚合查询、转办(Delegate)功能的实现,全都要靠它。排查“待办数据不对”时,优先查这张表是不是产生了多余的身份关系。

2.5 ACT_RU_JOB 系列:定时器、异步消息与失败重试

这部分是运行时表里最容易出“隐性故障”的地方。Activiti 从 6.x 开始把 Job 拆成了多张表:

  • ACT_RU_JOB:常规定时器任务、异步延续任务。
  • ACT_RU_TIMER_JOB:定时器 Job,比如流程里配置的边界定时事件、定时开始事件。
  • ACT_RU_SUSPENDED_JOB:被挂起的 Job。
  • ACT_RU_DEADLETTER_JOB:执行失败且无法自动重试的“死信任务”,需要人工介入。
  • ACT_RU_HISTORY_JOB:历史 Job 记录,记录已执行完成的任务。

定时任务执行失败后,会进入重试机制,RETRIES_字段控制剩余重试次数。重试耗尽仍失败,Job 会被标记为死信,进入ACT_RU_DEADLETTER_JOB。

这里有个常见故障:某个流程配置了定时提醒,但后台 Job Executor 没有启动,或者数据库时钟和服务器时钟不一致,导致一到时间点定时任务不触发或者反复触发。排查方向就是:

-- 查看当前所有未执行的定时任务 SELECT ID_, JOB_TYPE_, DUEDATE_, RETRIES_, EXCEPTION_MSG_ FROM ACT_RU_TIMER_JOB WHERE DUEDATE_ IS NOT NULL ORDER BY DUEDATE_;

这个字段EXCEPTION_MSG_很值钱,它存的是上一次执行失败的异常信息。看到具体报错,排查效率直接翻倍。

2.6 其他运行时表:事件订阅、批量任务与集成任务

  • ACT_RU_EVENT_SUBSCR:事件订阅表。流程运行中注册的信号事件、消息事件会在这里留记录。流程实例结束时,关联的事件订阅会被清除。
  • ACT_RU_BATCH:批量任务表。在 Activiti 7 中做大批量流程实例迁移、删除时会用到。
  • ACT_RU_INTEGRATION:集成任务上下文表。Activiti 7 里连接 Spring Boot 服务任务时,记录集成调用的上下文信息。

这几张表平时用得少,但不代表可以忽略。尤其是ACT_RU_EVENT_SUBSCR,排查“信号事件没触发”“消息事件收不到”时就得查它,看看对应流程实例的事件订阅是否还存在、是否被异常清理。

3. 运行时数据与历史数据的联动:流程关闭后发生了什么

理解了单张表,还得理解表与表之间在时间轴上的流转关系。我画过一条很清晰的数据线,这里用文字讲给你:

流程启动时:

  • ACT_RU_EXECUTION插入主执行流记录。
  • 如果流程定义中第一个节点是用户任务,ACT_RU_TASK插入一条待办。
  • 流程变量写入ACT_RU_VARIABLE。
  • 任务候选关系写入ACT_RU_IDENTITYLINK。
  • 同时,ACT_HI_PROCINST插入流程实例历史记录,ACT_HI_ACTINST插入当前节点的历史活动记录。

任务办理过程中:

  • 每次任务完成,ACT_RU_TASK删除当前待办,ACT_HI_TASKINST补全任务完成时间。
  • ACT_HI_ACTINST记录节点完成时间和耗时。
  • 变量变化会以新版本的记录追加到ACT_HI_VARINST。

流程走到结束节点时:

  • ACT_RU_EXECUTION中该流程实例的所有执行记录被删除。
  • ACT_RU_TASK中该流程实例的待办全部删除。
  • ACT_RU_VARIABLE中该流程实例的变量全部迁移到ACT_HI_VARINST。
  • ACT_RU_IDENTITYLINK中被关联的记录删除,保留必要的历史关联到ACT_HI_IDENTITYLINK。
  • ACT_RU_TIMER_JOB、ACT_RU_EVENT_SUBSCR等待执行的异步内容一并清理。

这个联动机制带来的一个启示是:运行时表的数量斜率,能直接反映系统当前积压的流程量。我习惯写一个监控 SQL,每半小时跑一次,看ACT_RU_EXECUTION的PROC_INST_ID_去重计数,如果这个数字异常飙升,说明有流程实例既没走到结束,也没被异常处理掉——多半是卡在等待状态或者死信任务上。

-- 统计当前活跃流程实例数 SELECT COUNT(DISTINCT PROC_INST_ID_) AS ACTIVE_PROC_COUNT FROM ACT_RU_EXECUTION WHERE IS_ACTIVE_ = 1;

另外,有个很精辟的工程建议:永远不要在业务系统里直接 join 运行时表来做统计报表。运行时表随时在删增,统计口径不稳定,而且会给流程引擎数据库带来额外压力。统计报表一律基于历史表去做,只有查询“当前待办”“当前状态”才查运行时表。

4. 实操经验:运行时表的运维、排查与性能调优

运行时表是线上故障排查的第一现场。下面根据我实际经历,分享几组高频排查 SQL 和优化细节。

4.1 定位“流程跑不下去”的标准排查路径

流程实例停在某处不动了,按这个顺序查:

第一步,查执行流状态:

SELECT ID_, PROC_INST_ID_, ACT_ID_, IS_ACTIVE_, SUSPENSION_STATE_ FROM ACT_RU_EXECUTION WHERE PROC_INST_ID_ = '实例ID';

如果SUSPENSION_STATE_是 2,说明流程被挂起,需要调用runtimeService.activateProcessInstanceById()激活;如果IS_ACTIVE_是 0,说明在等待子流程或外部消息。

第二步,查当前任务:

SELECT ID_, NAME_, ASSIGNEE_, CREATE_TIME_ FROM ACT_RU_TASK WHERE PROC_INST_ID_ = '实例ID';

没有任务记录但有活跃的执行流,说明流程停在了非用户任务的等待节点,比如消息中间事件、定时器事件。

第三步,查等待的 Job:

SELECT ID_, JOB_TYPE_, DUEDATE_, RETRIES_, EXCEPTION_MSG_ FROM ACT_RU_TIMER_JOB WHERE PROC_INST_ID_ = '实例ID';

有记录说明在等定时器触发;RETRIES_为 0 而且EXCEPTION_MSG_不为空,说明定时任务已经死了,要去ACT_RU_DEADLETTER_JOB人工处理。

第四步,查事件订阅:

SELECT * FROM ACT_RU_EVENT_SUBSCR WHERE PROC_INST_ID_ = '实例ID';

有记录说明流程在等待某个信号事件或消息事件,触发这个事件就能继续推进。

这套路径我整理成了一个排查脚本,每次线上出问题都先跑一遍,比漫无目的地翻日志高效得多。

4.2 运行时表数据清理与归档策略

运行时表的数据“活不长”,但并不意味着可以放任不管。特殊场景下会出现“僵尸数据”:

  • 流程实例异常终止但事务没回滚干净,执行流残留。
  • 外部系统调用流程引擎后自身报错,导致一部分运行时数据没被正常清理。
  • 定时器 Job 重复创建,死信堆积。

我见过一个生产环境,ACT_RU_JOB表积累了上百万条死信记录,每次 Job Executor 启动都要花半小时清理旧数据,整个流程引擎启动直接被拖垮。后来制定了策略:每天凌晨对ACT_RU_DEADLETTER_JOB中超过 7 天的数据做转储归档,然后物理删除;对ACT_RU_JOB中RETRIES_大于 0 但重试时间远在过去的 Job 做强制失败处理。

清理脚本我建议写成存储过程,根据日期分批删除,避免一次删太多锁表。核心逻辑类似:

DELETE FROM ACT_RU_DEADLETTER_JOB WHERE LOCK_EXP_TIME_ < DATE_SUB(NOW(), INTERVAL 7 DAY) LIMIT 5000;

注意加LIMIT,分批提交,减少对数据库的锁竞争。

4.3 索引设计:别让默认索引拖垮查询

Activiti 自带的索引覆盖了基本外键,但生产环境查询模式千差万别。以ACT_RU_TASK为例,默认索引一般只有主键和PROC_DEF_ID_外键,但实际查询经常是按ASSIGNEE_查待办、按CREATE_TIME_排序,这时候你就得自己补索引。我线上的优化配置是:

  • ACT_RU_TASK.ASSIGNEE_和SUSPENSION_STATE_联合索引,支撑待办列表查询。
  • ACT_RU_TASK.PROC_INST_ID_索引,支撑按流程实例查任务。
  • ACT_RU_VARIABLE.PROC_INST_ID_和NAME_联合索引,支撑变量查询。
  • ACT_RU_EXECUTION.PROC_INST_ID_索引,这个一般有,没有一定补。

建索引要克制,因为运行时表写入频繁,索引过多会拖慢 DML 性能。我的做法是:先用慢查询日志找出最耗时的 SQL,针对这些 SQL 的执行计划来建索引,而不是把每张表所有查询字段都建一遍。

4.4 读写分离与连接池配置

运行时表是“写多读少”还是“读多写多”,取决于业务形态。待办列表、流程状态查询很频繁的,可以把流程引擎数据源和业务库数据源分开配置;更进一步,可以把ACT_HI_*历史表的读请求指向只读从库,但运行时表不建议走从库——因为它的数据实时性强,主从延迟会造成任务状态读写不一致。

连接池上也有一点要注意:Activiti 的 Job Executor 是后台线程池,它需要额外占用数据库连接。如果你的数据源连接池上限设置过小,比如 10,但流程引擎的异步执行器默认也要拿连接,再叠加业务请求,连接会被打满。我习惯把最大连接数放宽到业务高峰预估值的 1.5 到 2 倍,并且单独给 Job Executor 配置独立的连接池。

5. 常见问题与避坑实录

5.1 流程实例删不掉,报外键约束错误

这个坑几乎每个用 Activiti 的都踩过。直接调用runtimeService.deleteProcessInstance()删除一个运行中的流程,框架会尝试清理运行时数据,但如果存在任务数据异常、历史数据残留等情况,删除会失败并抛外键约束异常。

我的处理方法是:先按前面提到的排查顺序找到所有关联的运行时数据,逐一确认归属;如果确实要强制清理,则按顺序删除子表再删主表。生产环境还有一个更稳的方案:把流程实例先挂起(suspend),确认无关联 Job 后,再走框架的 API 删除,避免直接操作数据库表带来的不可控风险。

提示:任何情况下都不要手动去ACT_RU_EXECUTION里 DELETE 数据。Activiti 的缓存和异步任务机制会与数据库状态不完全同步,手删数据库容易造成缓存脏读、Job 重复执行等一系列连锁故障。真要清理,调用框架 API。

5.2 任务级变量神秘消失

一个审批任务完成后,后续网关判断时变量取不到值。排查方式不复杂:看ACT_HI_VARINST里变量的创建和删除记录。如果变量确实被挂载在任务作用域下,任务一结束它就被清了。解决方式是在设置变量时显式挂到执行实例上:

runtimeService.setVariable(executionId, "varName", value);

避免在任务里用taskService.setVariable()设置后续网关要用的变量。这个规则我在团队里反复强调,属于“踩一次就长记性”的典型问题。

5.3 定时器任务不触发或重复触发

排查要点有三个:

  • Job Executor 是否开启。Activiti 7 的 Spring Boot 集成里默认是自动启用,但若改成自定义配置,很容易漏掉asyncExecutorActivate设置为 true。
  • 数据库系统时间和应用服务器时间是否一致。定时器由于DUEDATE_存的是绝对时间,两边时钟漂移会造成提前或延后触发。我见过数据库服务器时区配置错误,导致所有定时任务提前八小时执行的案例。
  • 流程实例处于挂起状态时,它的关联 Job 也会被挂起(SUSPENDED_JOB),恢复流程实例后 Job 才会重新激活。

排查时先看ACT_RU_TIMER_JOB的DUEDATE_和LOCK_OWNER_,如果LOCK_OWNER_一直由某个节点持有且未释放,可能是那个节点上的 Job Executor 线程卡死了,需要人工解锁或重启执行器。

5.4 大批量流程实例导致运行时表锁竞争

上线初期数据量小,没人注意锁问题。等哪天业务方做一次批量审批(比如几千条流程同时推进),ACT_RU_TASK的删除和ACT_RU_VARIABLE的插入并发剧增,行锁升级为间隙锁甚至表锁,整个库的吞吐量立刻崩掉。

缓解办法:

  • 控制批量操作的批次大小,官方推荐每批事务不超过 100 条流程实例。
  • 调整数据库的隔离级别,在允许的情况下把REPEATABLE READ降为READ COMMITTED,减少间隙锁冲突(这个要结合业务允许性)。
  • 对运行时表的写入操作尽量走到同一个数据库连接事务里,避免分布式事务带来的长事务锁。

5.5 ID 字段长度导致的兼容性问题

ACT_RU_EXECUTION.ID_和ACT_RU_TASK.ID_默认是 64 字符,如果你把流程实例 ID 或者业务键生成得太长,插入时就会爆字段长度错误。尤其是自定义了 ID 生成策略时,一定要先算好长度上限。

我踩过一次:用 UUID 变体生成 36 位 ID 没问题,但后来换成了包含业务前缀的复合 ID,直接超过 64 位,测试环境一启动就报Data too long for column 'ID_'。排查到后第一反应是改表结构,后来发现更好的办法是自定义IdGenerator实现,控制生成 ID 的位数,从源头解决问题。

6. 运行时表管理的最终建议

这组表的本质,是流程引擎在运行期的“内存态持久化”。理解它的关键不是背表名,而是理解每一个字段背后对应的流程引擎行为——ACT_RU_TASK的增删反映任务的流转,ACT_RU_VARIABLE的变更反映变量作用域的切换,ACT_RU_TIMER_JOB的异常反映异步引擎的健康状态。

如果团队里有新人接手 Activiti 项目,我一般建议做三件事:第一,自己走一次完整流程,在数据库里对比流程启动瞬间、任务办理瞬间、流程结束瞬间这三组时间点的运行时表变化;第二,把常用的排查 SQL 整理成团队文档,遇到问题先跑 SQL 定位再翻代码;第三,把运行时表单独做成监控大盘,哪怕只是最简单的记录数趋势曲线,也能提前发现流程积压的苗头。

我在实际项目里还坚持一个习惯:每次上线新流程前,检查该流程涉及的所有节点类型会产生哪些运行时数据,评估数据量峰值。比如一个引入了批量子流程的流程定义,它的ACT_RU_EXECUTION会有成倍的数据增长,这时候必须先约定清理和归档机制再放量上线,否则就是给未来的自己埋雷。

运行时表不是孤立存在的——它和历史表、定义表配合起来,才构成一套完整可追踪的流程引擎体系。花一个下午把这几张表的全生命周期摸一遍,会给后续所有的流程问题排查省下大量的时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询