1. Flowable 表结构整体拆解从“一脸懵”到“心中有图”很多朋友第一次接触 Flowable启动完工程、跑通一个请假流程后打开数据库一看哗啦啦几十张表摆在面前直接懵掉。这跟 MyBatis-Plus、Spring Data JPA 那种几张业务表加几张关联表的“小打小闹”完全不是一个量级。Flowable 一上来就是几十张 ACT_ 开头的表看着吓人其实只要搞懂它的设计逻辑这些表就像你家里的抽屉——每个抽屉放什么东西是有讲究的。Flowable 是从 Activiti 5 分叉出来的工作流引擎表结构基本沿袭了 Activiti 的那套设计所以你在网上搜 Activiti 的表结构说明大部分也能对上号。不过 Flowable 在 6.x 之后做了一些调整比如增加了 ACT_RU_运行时、ACT_HI_历史、ACT_ID_身份、ACT_GE_通用、ACT_EVT_事件等前缀每个前缀代表一类职责。理解了这套“按前缀分包”的设计思想你再看任何一张 ACT 开头的表都能猜出它大概干什么。我自己的经验是不要试图一次性把所有表都背下来那没有意义。你只需要先抓住三类核心表——流程定义、流程实例、任务然后把它们之间的关联关系理清楚剩下的表都是围绕这三类核心表做辅助的。等你真正去排查线上问题、写统计报表、做数据迁移的时候那些边角表的用途自然会刻进脑子里。这套表结构还有一个非常实用的特点运行时数据和历史数据分离。Flowable 的表单数据、任务数据在处理过程中都放在运行时表里一旦流程结束数据会被“归档”到历史表。这个设计的直接好处是查询进行中的任务永远走的是几张小表速度极快而所有历史记录汇总在大表里方便做审计和报表两者互不干扰。理解了这一点你就明白为什么 Flowable 的待办查询能扛住高并发而历史查询可以慢慢跑。2. 六大表分类详解ACT_GE、ACT_RE、ACT_RU、ACT_HI、ACT_ID、ACT_EVT 各管哪摊事Flowable 的表前缀一共就那么几类全部以 ACT_ 开头后面跟缩写。这里我先给出一张完整的分类表然后逐个拆解。前缀全称含义职责定位典型表ACT_GEGeneral通用数据主要是资源文件和属性ACT_GE_BYTEARRAY、ACT_GE_PROPERTYACT_RERepository流程定义、流程模型等静态资源ACT_RE_DEPLOYMENT、ACT_RE_PROCDEF、ACT_RE_MODELACT_RURuntime运行时数据流程实例、任务、变量、执行流ACT_RU_EXECUTION、ACT_RU_TASK、ACT_RU_VARIABLE、ACT_RU_IDENTITYLINK、ACT_RU_ACTINST、ACT_RU_JOB、ACT_RU_TIMER_JOB、ACT_RU_SUSPENDED_JOB、ACT_RU_DEADLETTER_JOBACT_HIHistory历史数据已完成的流程、任务、活动、附件ACT_HI_PROCINST、ACT_HI_TASKINST、ACT_HI_ACTINST、ACT_HI_VARINST、ACT_HI_DETAIL、ACT_HI_COMMENT、ACT_HI_ATTACHMENTACT_IDIdentity身份管理用户、组、关系ACT_ID_USER、ACT_ID_GROUP、ACT_ID_MEMBERSHIP、ACT_ID_INFOACT_EVTEvent事件日志ACT_EVT_LOGACT_APPApp应用模型Flowable 6.x 新增ACT_APP_DEPLOYMENT、ACT_APP_RELATIONACT_FOForm表单数据Flowable 6.x 新增ACT_FO_DATABASECHANGELOG、ACT_FO_DATABASECHANGELOGLOCK2.1 通用表 ACT_GE所有表的地基ACT_GE_PROPERTY是 Flowable 的属性表里面存的是引擎级别的 key-value 配置比如数据库表结构版本号、Schema 版本、下一次 ID 取值等。这张表最重要的价值在于Flowable 启动时会检查这张表里的版本信息判断数据库结构是否需要升级。你要是手动改过表结构或者从旧版本升级没跑脚本引擎会直接报错查这张表一眼就能看出问题。ACT_GE_BYTEARRAY存储的是二进制数据核心用途有两个一是存流程定义部署时上传的 BPMN XML 文件内容二是存流程定义对应的图片资源PNG/SVG。因为是一张通用的二进制表它的 data 列是 LONGBLOB 类型千万别用 Navicat 直接去查询里面的内容会卡死客户端。排查询问题的时候常用它来确认“这条流程定义的 XML 到底是谁上传的、是什么内容”。提示ACT_GE_BYTEARRAY 里的 DEPLOYMENT_ID_ 字段跟 ACT_RE_DEPLOYMENT 的 ID_ 关联。如果你清理部署记录一定要连这张表的二进制资源一起清理否则数据库会越来越大全是没用的 XML 老版本。2.2 仓库表 ACT_RE流程定义的家ACT_RE_DEPLOYMENT是部署表每一次你用 repositoryService.createDeployment() 部署一个 BPMN 文件这张表就多一条记录。它记录部署的 ID、名称、部署时间、类别等。很多人误以为部署一次等于一个流程定义其实不对——一次部署可以包含多个 BPMN 文件也就是多个流程定义。所以部署表和流程定义表是一对多的关系。ACT_RE_PROCDEF是流程定义表这才是真正的“流程模板”。每个 BPMN 里的一个 process 元素对应一条记录。关键字段有KEY_流程定义的业务标识比如 leaveProcess、NAME_流程名称、VERSION_版本号、DGRM_RESOURCE_NAME_流程图资源名、RESOURCE_NAME_BPMN 文件名、SUSPENSION_STATE_挂起/激活状态。这里有个新手最容易踩的坑老版本流程定义更新时历史流程实例还是按老版本跑的但新发起的流程默认走新版本。所以一个 KEY 下会存在多个 VERSION_ 版本的记录查流程定义时一定要带条件“只看最新版本”或者按 KEY 分组否则你会算出翻倍的流程数。用 SQL 查的时候经典写法是SELECT p.* FROM ACT_RE_PROCDEF p WHERE p.KEY_ leaveProcess AND p.VERSION_ (SELECT MAX(VERSION_) FROM ACT_RE_PROCDEF WHERE KEY_ p.KEY_);ACT_RE_MODEL是模型表Flowable 6.x 之后把“在线建模”的数据存到这里。如果你用 Flowable Modeler 画流程就会产生这条记录。它的数据是 JSON 格式的模型定义发布时才转换成 BPMN 部署到 ACT_RE_DEPLOYMENT 和 ACT_RE_PROCDEF。搞集成开发的话模型表一般用不上但如果你做的是流程平台二次开发模型发布这块绕不开它。2.3 运行时表 ACT_RU正在跑的流程全在这运行时表是 Flowable 表结构里最核心、最复杂、也最容易出现“疑问”的一块。它们的特点是数据是临时存在的流程结束后会被清理默认策略下所以它们的行数不会无限膨胀。ACT_RU_EXECUTION是执行实例表这是整个 Flowable 运行时最核心的表。每条流程实例ProcessInstance在这里是一条主记录它的父执行流父 ID为空或者指向自己而流程内部每个并行分支又会产生子执行流记录通过 PARENT_ID_ 关联。也就是说这张表不仅是“流程实例表”还承载了“执行树”的关系。新手刚开始看这张表会犯迷糊为什么一个流程实例在 EXECUTION 表里会有好几条记录我举个例子一个会签节点来了 5 个人审批你会在 EXECUTION 表里看到 5 条同 PROC_INST_ID_ 的子执行记录各有各的 IS_ACTIVE_ 状态。要查“当前有哪几个流程实例在跑”正确的 SQL 是SELECT DISTINCT PROC_INST_ID_ FROM ACT_RU_EXECUTION WHERE PARENT_ID_ IS NULL;或者直接查 ACT_RU_TASK 去重也能得到正在跑的流程实例数。ACT_RU_TASK是待办任务表。这是你日常写待办查询接口时最常用的一张表。每条记录代表一个待办任务关键字段有ID_任务 ID、NAME_任务名、ASSIGNEE_办理人、TASK_DEF_KEY_流程节点 ID对应 BPMN 里的 userTask id、EXECUTION_ID_、PROC_INST_ID_、PROC_DEF_ID_、CREATE_TIME_、DUE_DATE_、PRIORITY_。查“张三的待办”就是最简单的SELECT * FROM ACT_RU_TASK WHERE ASSIGNEE_ zhangsan AND SUSPENSION_STATE_ 1;注意 SUSPENSION_STATE_如果流程实例被挂起它的任务也会被挂起待办查询记得过滤。另外如果任务是通过候选人candidate方式分配的ASSIGNEE_ 是空的要看候选人的话得关联 ACT_RU_IDENTITYLINK 表这在后面展开讲。ACT_RU_VARIABLE是流程变量表。你在流程里设置的每一个变量比如请假天数、审批意见、金额、业务主键都存这里。关键字段NAME_变量名、VARIABLE_TYPE_变量类型string/int/long/serializable 等、LONG_/TEXT_/DOUBLE_、BYTEARRAY_ID_复杂对象序列化后指向二进制表。变量的作用域有两种一种是流程实例级EXECUTION_ID_ 指向主执行流一种是任务级TASK_ID_ 有值。查询“某个流程实例的所有变量”就是SELECT * FROM ACT_RU_VARIABLE WHERE PROC_INST_ID_ 12345;这里提醒一句Flowable 的序列化变量存对象的会往 ACT_GE_BYTEARRAY 里写二进制数据如果你存了很大的对象比如整个查询结果 List数据库会迅速膨胀。我在生产项目里见过有人把 10MB 的对象存进流程变量结果一张表几个 G查询全卡死。流程变量这东西能用 ID 关联就只存 ID别图省事存大对象。ACT_RU_IDENTITYLINK是身份关联表。它的作用是记录“谁跟这个流程/任务有关联”比如任务的候选人、候选组、办理人。字段有TASK_ID_、PROC_INST_ID_、TYPE_candidate/assignee/owner、USER_ID_、GROUP_ID_。查某个任务的候选组可以这么写SELECT GROUP_ID_ FROM ACT_RU_IDENTITYLINK WHERE TASK_ID_ taskId AND TYPE_ candidate;ACT_RU_ACTINST是活动实例表。它记录流程里每一次节点活动的执行情况包括用户任务、排他网关、并行网关、开始事件、结束事件等。关注字段ACT_ID_活动节点 ID、ACT_TYPE_活动类型、START_TIME_、END_TIME_、DURATION_。这张表很适合用来做“流程走到哪一步了”的实时追踪比如显示“当前停留节点”就是查 END_TIME_ IS NULL 的记录。不过网上不少教程在流程进度查询上用的是 ACT_HI_ACTINST这里要区分开ACT_RU_ACTINST 放的是还没结束的活动ACT_HI_ACTINST 放的是所有历史活动两者用 PROC_INST_ID_ 关联即可。运行时表里还有一堆以 JOB 结尾的表ACT_RU_JOB、ACT_RU_TIMER_JOB、ACT_RU_SUSPENDED_JOB、ACT_RU_DEADLETTER_JOB。它们是 Flowable 的异步任务机制比如定时器、异步延续、事务监听等。这里不展开详细讲你只需要知道一旦流程里有用到定时边界事件、异步执行这些表就会出数据。排查询速度问题的时候如果发现 JOB 表数据量巨大十有八九是异步任务堆积了得检查 ACT_RU_TIMER_JOB 的 DUEDATE_ 字段。2.4 历史表 ACT_HI所有流程的“记账本”历史表的数据是永久保留的除非你配了历史清理任务所以它们的行数会一直增长。生产环境里这几张表往往是最占空间的。但说实话Flowable 的历史表是它的宝藏不管是审计、报表、统计、反查线索都靠这几张表。ACT_HI_PROCINST是历史流程实例表。一个流程跑的完整生命周期结束后会在这里留一条记录。关键字段PROC_INST_ID_流程实例 ID、PROC_DEF_ID_、START_TIME_、END_TIME_、DURATION_、START_USER_ID_发起人、DELETE_REASON_删除原因completed/fullDelete 等。这张表非常适合做“我的发起列表”因为不管流程有没有结束所有流程都在这里且能拿到发起人、开始结束时间。查询“某用户发起的所有流程”SELECT * FROM ACT_HI_PROCINST WHERE START_USER_ID_ zhangsan ORDER BY START_TIME_ DESC;ACT_HI_TASKINST是历史任务表。每个任务创建后都会在这里留一条记录无论完成没完成。它的字段非常丰富TASK_ID_、ASSIGNEE_、NAME_、START_TIME_、END_TIME_、DURATION_、DELETE_REASON_、CLAIM_TIME_认领时间。做“已办任务列表”就是查这张表加上 END_TIME_ IS NOT NULL 条件。这里有一个很多人踩过的坑ASSIGNEE_ 在历史任务表里记录的是任务“最终办理人”。如果任务经过转办、委派那么 ASSIGNEE_ 存的是最终完成人而中间被转给别人时的办理记录要看 ACT_HI_DETAIL 或者通过 IDENTITYLINK 的历史记录才能追溯。所以做“任务的流转轨迹”时别只盯着 ACT_HI_TASKINST 的 ASSIGNEE_ 字段。ACT_HI_ACTINST是历史活动实例表。这张表是流程节点的“完整执行流水账”每条活动开始事件、用户任务、网关、结束事件都在这里有一条记录包括节点的进入时间和离开时间。它和 ACT_RU_ACTINST 的区别在于历史表记录全量运行时表只记录当前还在活跃的。做流程追踪图、时间轴、甘特图基本都靠这张表SELECT * FROM ACT_HI_ACTINST WHERE PROC_INST_ID_ 12345 ORDER BY START_TIME_;ACT_HI_VARINST是历史流程变量表。运行时 ACT_RU_VARIABLE 的变量在流程结束后会归档到这里。字段和运行时变量表基本一致多了 REV_、CREATE_TIME_、LAST_UPDATED_TIME_。查“历史流程的关键业务参数”比如金额、审批结果常用这张表。ACT_HI_DETAIL是历史明细表。它记录的是流程变量在“每一个时刻”的变化比如同一变量在节点 A 被赋值一次在节点 B 又被覆盖一次这张表里就有两条记录。但注意默认配置下 ACT_HI_DETAIL 只会记录流程变量的历史变化不会记录任务表单字段明细。要想记录表单字段还得配置引擎开启表单字段历史记录。如果嫌这张表太占空间可以在引擎配置里关掉变量历史明细historyaudit 或 activity 级别只保留必要的。ACT_HI_COMMENT是历史评论表。你用 taskService.addComment() 添加的评论、审批意见都在这里。字段TYPE_comment/event、TIME_、USER_ID_、TASK_ID_、PROC_INST_ID_、MESSAGE_。做“审批记录、意见汇总”的查询基本就是SELECT * FROM ACT_HI_COMMENT WHERE PROC_INST_ID_ 12345 AND TYPE_ comment ORDER BY TIME_;ACT_HI_ATTACHMENT是历史附件表存储任务的附件信息。一般不存实际文件内容存的是 URL 或者附件名实际二进制可放在文件服务器上。做“附件列表”时查询它。2.5 身份管理表 ACT_ID用户和组但通常用不上Flowable 自带的身份管理表包括 ACT_ID_USER用户表、ACT_ID_GROUP组表、ACT_ID_MEMBERSHIP用户组关联表、ACT_ID_INFO用户信息表、ACT_ID_TOKEN登录令牌一般不用。这套表设计得非常简陋本质上就是一张用户表 一张组表 一张关联表没有密码加密、没有角色权限连用户 ID 都是业务侧自己传的字符串。生产项目里绝大多数团队都不会用 Flowable 自带的身份表而是通过自定义实现 UserManager、GroupManager 或者直接不走身份服务把用户 ID、部门 ID 作为字符串传入流程变量。拿请假流程来说发起人直接传一个员工工号字符串作为 START_USER_ID_审批人直接传给 ASSIGNEE_。这样省事也符合大多数系统的用户体系现状。实操建议如果你做企业级集成不要在 Flowable 里重新维护一套用户体系否则用户同步会让你痛不欲生。直接把 ACT_ID 表当成“可选组件”该用业务系统的用户服务就用它。2.6 事件日志表 ACT_EVT_LOG出了问题第一个要查的地方Flowable 6.x 之后引入了事件机制ACT_EVT_LOG 表记录了引擎运行过程中产生的事件日志包含事件类型、时间、处理状态等。这张表最典型的用途是排分布式事务问题——比如 Spring 集成下事件处理失败时事件会在这张表里留着方便重放。我遇到过一种情况流程启动时抛异常“cant find event handler for event”查了半天业务代码最后发现是 ACT_EVT_LOG 里有未处理的事件堆积清理掉重试就好了。所以这张表在监控里很重要建议定期清理避免数据无限增长。3. 表间关系与核心查询从一张任务表串起整条链路光看表格定义不够真正要把流程跑起来、把数据查出来还得理解表之间的关系。下面用最常见的“发起请假流程 - 部门经理审批 - 总经理审批 - 流程结束”场景把一条完整的数据链路给你串起来。流程发起后数据是这样落库的ACT_RE_PROCDEF 里有一条请假流程定义记录ID 如 leaveProcess:1:4。ACT_RU_EXECUTION 里插入一条流程实例主记录PROC_INST_ID_ 如 5001。ACT_RU_TASK 里插入第一条待办任务部门经理审批ASSIGNEE_ 为 zhangsanTASK_DEF_KEY_ 为 deptManagerApproval。ACT_RU_ACTINST 里插入开始事件、当前用户任务的活动实例记录。ACT_RU_VARIABLE 里写入流程变量比如 applyUserzhangsan、days3。ACT_HI_PROCINST、ACT_HI_ACTINST、ACT_HI_TASKINST、ACT_HI_VARINST 同步写入对应的历史记录这里注意Flowable 是边运行边写历史不是结束时才写。部门经理点“审批通过”后ACT_RU_TASK 里的这条任务会被删除ACT_HI_TASKINST 里的 END_TIME_ 被更新成完成时间ACT_HI_ACTINST 对应的活动实例也补上 END_TIME_。接着引擎会继续推进生成下一张待办任务总经理审批再次插入到 ACT_RU_TASK 和 ACT_HI_TASKINST。流程结束后所有运行时表中的记录会被清理ACT_RU_EXECUTION、ACT_RU_TASK、ACT_RU_VARIABLE 等历史表里的对应记录则永久保留。这正是 Flowable“运行时轻、历史重”的典型表现。现在我们来解决一个高频需求——“查询某个流程实例的最新审批进度”。这个查询要查到当前待办任务和已完成的节点列表最实用的写法如下-- 当前停留在哪个节点运行时活动表未结束的活动 SELECT a.ACT_ID_, a.ACT_NAME_, a.START_TIME_ FROM ACT_RU_ACTINST a WHERE a.PROC_INST_ID_ 5001 AND a.END_TIME_ IS NULL; -- 已完成的节点轨迹历史活动表 SELECT h.ACT_ID_, h.ACT_NAME_, h.START_TIME_, h.END_TIME_ FROM ACT_HI_ACTINST h WHERE h.PROC_INST_ID_ 5001 ORDER BY h.START_TIME_;如果需要在业务系统里展示“当前办理人”则可以关联 ACT_RU_TASKSELECT t.ID_ AS taskId, t.NAME_ AS taskName, t.ASSIGNEE_ AS assignee, t.CREATE_TIME_ AS createTime, e.ID_ AS executionId FROM ACT_RU_TASK t LEFT JOIN ACT_RU_EXECUTION e ON t.EXECUTION_ID_ e.ID_ WHERE t.PROC_INST_ID_ 5001 AND t.END_TIME_ IS NULL;这里多解释一句ACT_RU_TASK 表本身没有 END_TIME_ 字段所以判断“未结束任务”其实不需要条件存在即未结束但如果你查历史任务就得用 ACT_HI_TASKINST 里 END_TIME_ 是否为空来判断这个任务是否已完成还是被删除了。再一个重要场景“分页查询某人的待办”。实际开发中待办查询往往是首页压力最大。推荐查询方案是先查 ACT_RU_TASK加上 ASSIGNEE_ 和 SUSPENSION_STATE_ 过滤然后关联 ACT_RU_EXECUTION 和 ACT_RE_PROCDEF 取流程实例 ID 和流程名称。如果有候选人/候选组的待办则要先用 ACT_RU_IDENTITYLINK 把任务 ID 集合捞出来再关联查主表SELECT t.*, e.PROC_INST_ID_, d.NAME_ AS procDefName FROM ACT_RU_TASK t LEFT JOIN ACT_RU_EXECUTION e ON t.EXECUTION_ID_ e.ID_ LEFT JOIN ACT_RE_PROCDEF d ON t.PROC_DEF_ID_ d.ID_ WHERE (t.ASSIGNEE_ zhangsan OR t.ID_ IN (SELECT TASK_ID_ FROM ACT_RU_IDENTITYLINK WHERE USER_ID_ zhangsan AND TYPE_ candidate)) AND t.SUSPENSION_STATE_ 1 ORDER BY t.CREATE_TIME_ DESC LIMIT 0, 20;优化事项ACT_RU_TASK 表在数据量大时ASSIGNEE_ 一定要加索引。Flowable 默认给 ASSIGNEE_ 建的索引叫 ACT_IDX_HI_TASKINST_ASSIGNEE但不同版本覆盖情况不一样生产库上建议自己检查一遍索引尤其是 ACT_RU_TASK 和 ACT_HI_TASKINST 两张大表的 ASSIGNEE_、PROC_INST_ID_ 字段缺了就 ALTER TABLE 补上。4. 常见问题与排查技巧实录4.1 表数据不断膨胀数据库一个月涨了几个 G这是 Flowable 使用中最常见的运维问题。原因无非两种一是历史表的数据没有做归档和清理二是运行时表里的 JOB 任务堆积。解决办法分三步走第一确认 Flowable 是否开启了历史清理任务。Flowable 6.4 之后提供 historyCleanup 机制需要在引擎配置里启用并且配置清理策略比如删除多少天前的历史数据。线上配置可以参考flowable.history-cleanup-enabledtrue flowable.history-cleanup-strategyendTimeBased flowable.history-cleanup-end-time-limit180d flowable.history-cleanup-batch-size100第二对于运行时表ACT_RU_JOB 和 ACT_RU_TIMER_JOB检查是不是有异常任务不断重试。用 SQL 看下任务数量SELECT * FROM ACT_RU_TIMER_JOB WHERE DUEDATE_ NOW();如果有大量过期任务多半是定时器节点触发了重试或者在流程中存在死循环路径。可以在引擎配置里设置异步执行器的最大重试次数比如async-executor-activatefalse关掉异步执行或者把async-executor-core-pool-size调小减少堆积。第三架构层面彻底解决——历史数据迁移。把 ACT_HI_* 表的数据定期同步到独立的报表库或数据仓库Flowable 库只保留近几个月的数据。增量的数据同步工具用 DataX、Canal、Debezium 都行Flowable 没有强一致要求最终一致即可。4.2 明明同一个流程定义历史库里却查不到某条记录这个问题我遇到过好几次排查到最后通常不是表结构问题而是历史级别history level配置问题。Flowable 有几种历史级别none不记录历史、activity记录活动历史、audit默认记录活动历史 变量历史、full记录所有包括表单字段和变量细节。如果你配了 activity 级别那么 ACT_HI_VARINST 里的变量记录就不会写入ACT_HI_DETAIL 里只有流程变量不包含参数明细。排查思路很简单先看 ACT_HI_PROCINST 有没有这条流程实例记录。有但 ACT_HI_TASKINST 没有对应的任务记录那大概率是流程还没走到用户任务节点就结束了或者流程在发起后被直接删除任务未被创建这属于正常现象。如果 ACT_HI_PROCINST 也没有那检查部署时是否用了不同引擎标识、或者流程根本没启动进去。4.3 查询待办慢接口响应超过 3 秒待办查询慢基本就是一张表扛了太多数据或者关联查询没走索引。我这里给出一条完整的排查流程先跑 EXPLAIN 看查询计划EXPLAIN SELECT t.* FROM ACT_RU_TASK t WHERE t.ASSIGNEE_ zhangsan;如果 type 是 ALL全表扫描说明索引没建上。Flowable 官方脚本里其实建了不少索引但很多数据库迁移工具或自定义建库脚本会漏掉所以我强烈建议你在初始化完成后手动检查一下 ACT_RU_TASK、ACT_HI_TASKINST、ACT_HI_ACTINST 三张表的索引情况。推荐索引清单如下直接执行即可ALTER TABLE ACT_RU_TASK ADD INDEX idx_ru_task_assignee (ASSIGNEE_) USING BTREE; ALTER TABLE ACT_RU_TASK ADD INDEX idx_ru_task_procinst (PROC_INST_ID_) USING BTREE; ALTER TABLE ACT_HI_TASKINST ADD INDEX idx_hi_taskinst_assignee (ASSIGNEE_) USING BTREE; ALTER TABLE ACT_HI_TASKINST ADD INDEX idx_hi_taskinst_procinst (PROC_INST_ID_) USING BTREE; ALTER TABLE ACT_HI_ACTINST ADD INDEX idx_hi_actinst_procinst (PROC_INST_ID_) USING BTREE; ALTER TABLE ACT_HI_VARINST ADD INDEX idx_hi_varinst_procinst (PROC_INST_ID_) USING BTREE;除了索引查询写法也有讲究。Flowable 推出的 Rest API 里查任务列表默认会返回很宽泛的字段一些业务系统在做待办接口时用 JPA 的 Specification 动态拼接最后 ORM 自动生成一堆 IN 子句性能极差。我建议写一个专门的待办查询 Mapper手写 SQL只查必要的字段别 SELECT * 一股脑全捞回来。4.4 定时任务不触发边界事件像没生效这种问题通常跟 ACT_RU_TIMER_JOB 表相关。先看这张表里有没有生成对应的定时任务记录SELECT * FROM ACT_RU_TIMER_JOB WHERE PROC_INST_ID_ 5001;有记录但不执行可能是异步执行器被关了。Flowable 的定时器依赖 Job Executor 线程池如果async-executor-activate没开或者你用的是内存模式且进程重启导致定时器丢失就会出现“到时间了却不触发”的假象。排查时先看引擎日志搜 “Timer” 或 “execute job” 关键词。如果用的是多个节点部署的集群环境还要检查 ACT_RU_TIMER_JOB 的 LOCK_OWNER_ 字段确认是哪个节点拿走了任务锁。锁定超时会导致任务无人执行这时候清理一下 LOCK_OWNER_ 和 LOCK_EXP_TIME_ 字段让任务重新被调度UPDATE ACT_RU_TIMER_JOB SET LOCK_OWNER_ NULL, LOCK_EXP_TIME_ NULL WHERE ID_ xxx;4.5 流程变量存进去的是中文查出来乱码Flowable 的字符串变量在 MySQL 里默认走 TEXT 类型存储如果你建库的时候字符集不是 utf8mb4那么遇到生僻字和 emoji 就会乱码。解决方式建库时加上CREATE DATABASE flowable DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;已经建好的库把所有 ACT_* 表的 TEXT、VARCHAR 字段统一转成 utf8mb4。这个操作在数据量大的表上会很慢建议在低峰期执行。另外数据库连接串最好加上characterEncodingutf8和useUnicodetrue防止连接层面的编码问题。4.6 部署后流程定义没生效还是跑的旧版本这个问题基本就是没看清 ACT_RE_PROCDEF 的版本逻辑。部署一次key 相同的新 BPMN会自动生成 VERSION_ 1 的新记录。但已经发起的流程实例和当前激活中的待办任务不受新版本影响。你看到“流程还是跑老逻辑”大概率是发起流程时没有传对 PROCESS_DEFINITION_KEY或者引擎缓存了旧定义。Flowable 默认有流程定义缓存如果你部署了新版本但没清理缓存可能还会获取到旧版本。推荐的做法是为部署的每个流程定义单独指定 KEY不要把所有流程都塞进同一个 KEY并且启动流程时明确用processDefinitionId带版本号或者processDefinitionKey默认取最高版本避免歧义。5. 实战用 SQL 快速搭建流程监控大盘前面讲了很多理论这里给出一套可以直接抄作业的流程监控 SQL 集锦。不需要写 Java 代码光靠数据库就能把流程运维看板跑起来。5.1 统计正在运行的流程实例数量按流程分组SELECT d.NAME_ AS processName, d.KEY_ AS processKey, COUNT(DISTINCT e.PROC_INST_ID_) AS runningCount FROM ACT_RU_EXECUTION e LEFT JOIN ACT_RE_PROCDEF d ON e.PROC_DEF_ID_ d.ID_ WHERE e.PARENT_ID_ IS NULL GROUP BY d.NAME_, d.KEY_ ORDER BY runningCount DESC;5.2 当前待办任务分布按办理人SELECT ASSIGNEE_ AS assignee, COUNT(*) AS todoCount FROM ACT_RU_TASK WHERE SUSPENSION_STATE_ 1 GROUP BY ASSIGNEE_ ORDER BY todoCount DESC;5.3 平均审批耗时按流程定义SELECT d.KEY_ AS processKey, AVG(TIMESTAMPDIFF(SECOND, p.START_TIME_, p.END_TIME_)) AS avgDurationSec, COUNT(*) AS totalCount FROM ACT_HI_PROCINST p LEFT JOIN ACT_RE_PROCDEF d ON p.PROC_DEF_ID_ d.ID_ WHERE p.END_TIME_ IS NOT NULL GROUP BY d.KEY_ ORDER BY avgDurationSec DESC;5.4 查询超过 7 天未完成的待办任务SELECT t.ID_ AS taskId, t.NAME_ AS taskName, t.ASSIGNEE_ AS assignee, t.CREATE_TIME_ AS createTime, p.NAME_ AS processName, t.PROC_INST_ID_ AS procInstId FROM ACT_RU_TASK t LEFT JOIN ACT_RE_PROCDEF p ON t.PROC_DEF_ID_ p.ID_ WHERE t.CREATE_TIME_ DATE_SUB(NOW(), INTERVAL 7 DAY) AND t.SUSPENSION_STATE_ 1 ORDER BY t.CREATE_TIME_ ASC;5.5 查询全部历史流程的审批意见列表SELECT c.PROC_INST_ID_, c.TASK_ID_, c.USER_ID_, c.MESSAGE_ AS comment, c.TIME_ FROM ACT_HI_COMMENT c WHERE c.TYPE_ comment AND c.PROC_INST_ID_ 5001 ORDER BY c.TIME_;这组 SQL 可以直接接到 Grafana 或者开发一个自己的监控看板里非常简单粗暴。有些团队还会配合 Canal 监听 ACT_RU_TASK 的 INSERT 和 DELETE 事件实时统计待办数量变化效果很直观。6. 索引与性能优化一张大表一般撑多久才开始卡Flowable 的表在数据量不大比如单表几万行时根本不需要刻意优化。但线上跑到几十万、上百万行时性能问题就会集中暴露。我的经验是ACT_HI_ACTINST 是最容易爆的大表一个流程流转十几个节点每个节点可能产生 2~3 条活动记录进入、离开、如果有网关还有分支百万级流程实例跑完这张表可能就上千万行了。针对大表性能给三个实际可用的方案第一建好联合索引。不要只建单列索引查询经常同时按 PROC_INST_ID_ 和 START_TIME_ 过滤可以建ALTER TABLE ACT_HI_ACTINST ADD INDEX idx_hi_actinst_proc_start (PROC_INST_ID_, START_TIME_);第二分区表设计。MySQL 8.0 之后支持 RANGE 分区可以按月把历史表分区。比如 ACT_HI_PROCINST、ACT_HI_TASKINST 这类表按结束时间分区这样月度归档和清理就是直接 DROP PARTITION比 DELETE 快得多。第三如果业务允许关闭不需要的历史级别。全量历史记录虽然很“香”但并非所有业务都需要保留每一个变量的每一次变化。如果只需要知道最终结果不建议开启 full 和 audit 级别的变量明细记录。Flowable 引擎配置里可以设置flowable.history-levelactivityactivity 级别会记录活动和任务历史但不记录变量实例ACT_HI_VARINST 基本只留流程级变量能省不少空间。需要审计的时候再临时调高级别跑一段时间。这个技巧我在维护一个老项目时就这么干的数据库体积直接砍了 60%待办查询速度也回来了。7. 实际项目中的表设计心得与避坑清单最后分享一些我在真实项目中的经验这些都是文档里不太会特意强调的细节。第一条别乱删 ACT_RU_EXECUTION 表里的数据。很多新手为了“清理没用的流程实例”直接 DELETE 掉 ACT_RU_EXECUTION 里 PROCESS_INST_ID 对应的记录结果流程实例的待办任务还在或者 ACT_HI_PROCINST 里流程状态还是运行中导致各种数据不一致。要删除一个运行中的流程实例正确的方式是调用 runtimeService.deleteProcessInstance()引擎会自动清理运行时表并把删除原因写入历史表。手工 SQL 清理是最后手段除非你确定自己在干什么。第二条版本升级前先备份 ACT_GE_PROPERTY。Flowable 的数据库结构版本就存在 ACT_GE_PROPERTY 表里升级引擎版本时Flowable 会自动执行数据库升级脚本。如果升级失败这张表的状态会变成中间态导致后续启动直接报错。升级前备份这张表几乎等于备份了整个 Flowable Schema 的版本状态非常管用。第三条ACT_ID_ 表不要硬编码使用。很多团队集成 Flowable 时直接把业务系统的用户表映射到 ACT_ID_USER想省事。结果业务系统的用户改了个名字Flowable 这边不同步审批人历史记录就歪了。我的建议是业务数据里一律存用户唯一 ID比如工号不要把显示名字存进流程变量和注释表显示名都从业务系统现查这样最灵活。第四条使用 Flowable 自带的 JSON 表单时要注意 ACT_FO 表。如果你用了 Flowable 6.x 的 Form 模块ACT_FO_* 表要注意这些表单定义和表单实例的数据是独立存储的跟流程变量不直接互通。表单引擎会把字段值提交后转成变量但提交前的草稿数据存在表单实例表里。如果项目里有“草稿箱”需求别只查 ACT_RU_VARIABLE得把表单实例表一起查了。第五条数据库字符集统一用 utf8mb4。这个在前面提过但必须再强调一遍。Flowable 的表结构在初始化 SQL 里是以 utf8 为默认字符集的如果你用 Navicat 导入时默认选了 utf8后续遇到手机号、表情符号、生僻字就全乱码。初始化的时候一定手动指定 utf8mb4后面省无数麻烦。第六条定时清理历史数据要分批删。直接DELETE FROM ACT_HI_ACTINST WHERE END_TIME_ 2023-01-01在千万级大表上会直接锁死数据库。用循环分批删除每次只删 1000~5000 条DELETE FROM ACT_HI_ACTINST WHERE END_TIME_ 2023-01-01 LIMIT 1000;配合定时任务循环跑影响面才可控。而且注意 ACT_HI_ACTINST 和其他历史表有外键关系删除时需要先把子表数据清理干净否则会卡在外键约束上。8. 小结把 Flowable 表结构当成你的“流程导航地图”最后说点我个人的体会。Flowable 这套表结构设计初看觉得繁琐实际用起来会发现其实非常合理。运行时表和历史表分离的设计让你在处理“进行中的业务”和“已经完成的审计”时互不干扰ACT_RU_TASK 和 ACT_HI_TASKINST 一热一冷把高频查询和低频查询彻底分开了。在实际项目里我不建议把所有表都封装成实体类去操作Flowable 官方提供了 Service API大部分情况下你应该直接调用 runtimeService、taskService、historyService而不是写 SQL 去改表。但当你需要做定制查询、报表统计、性能排查、数据修复时能看懂表结构、能写一手精准的 SQL就会比其他人从容得多。这套表结构是固定套路学会了它你以后再碰 Activiti、Camunda 这些同族工作流引擎会发现表结构几乎一个模子刻出来的。换个引擎核心逻辑依然是 ACT_RU_TASK、ACT_HI_PROCINST 这些老朋友学习成本直接断崖式下降。所以我一直建议团队里负责 Flowable 的同事第一周别急着写代码先把这套表结构的所有关系摸一遍后面开发速度会快非常多。