ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Flowable 6.8.0表结构深度解析:核心字段、数据流转与排障实战

Flowable 6.8.0表结构深度解析:核心字段、数据流转与排障实战 1. 为什么搞懂Flowable的表结构如此重要——三个真实场景先说说我自己的经历。几年前第一次在项目里集成Flowable 6.8.0时我犯了一个几乎所有新手都会犯的错把API调用跑通之后就以为万事大吉流程定义部署了待办任务能查询了流程能正常走完了于是整整两个月没碰过那几十张ACT_开头的表。直到线上出现一个诡异的Bug——某个流程实例卡在审批节点用户说“我明明已经点通过了”而流程始终没有往下走。用Flowable自带的HistoryService查询时历史数据一切正常任务确实完成了但流程实例状态就是不对。最后实在没辙了我打开数据库一条SQL一杯茶地排查才在一张名叫ACT_RU_EXECUTION的表中找到了问题根源。那次经历之后我得出一条结论如果你打算在生产环境长期使用Flowable数据库表结构不是“可以不懂”而是“必须懂”。这不是让你把几十张表的每一个字段都背下来而是要对表的分类、职责边界、核心字段的含义有清晰的认知。原因很简单Flowable本身是一个状态机引擎流程实例、任务、变量的当前状态都存在ACT_RU_*系列表中一张表的字段写错了值或者一条记录被误删轻则查询结果异常重则整个流程实例无法继续流转。而在排查这类问题的时候日志和API只能告诉你“发生了什么”只有表数据能告诉你“为什么发生”。这篇文章基于Flowable 6.8.0版本把主要表结构、核心字段的注释和设计意图完整过一遍。适合正在用Flowable做项目开发、或者已经上线但准备自己维护和排查问题的团队参考。我不会按官方文档那样把所有字段平铺出来而是把字段按“这个字段是干什么的、什么时候会写入、踩坑时怎么用”来解读这样你读完可以直接去查自己的库。2. Flowable 6.8.0表结构全景五类表的职责边界2.1 按前缀分类一张表搞定全貌Flowable 6.8.0的数据库表加起来大约有五十多张但你不需要被数量吓到。它的命名规则极其规律所有表都按前缀分成几个大类看到表名前缀你基本就能猜到它的用途范围。前缀含义典型用途生命周期ACT_RE_*Repository流程定义和静态资源存部署包、流程定义、模型部署时写入长期保留ACT_RU_*Runtime运行时数据存流程实例、执行实例、任务、变量流程结束后大部分删除ACT_HI_*History历史数据存历史流程实例、历史任务、历史活动流程结束后写入长期保留ACT_ID_*Identity身份信息存用户、用户组、成员关系与流程实例无关ACT_GE_*General通用数据存二进制资源、引擎属性、锁全局使用ACT_EVT_*Event事件日志存引擎事件日志审计用这个分类还有一层更重要的含义表之间的数据流转是有节奏的。一个流程从部署、启动、运行到结束数据不是同时出现在所有表里的而是有先后顺序。搞清楚这个顺序你就理解了Flowable的存储架构。2.2 生命周期视角一次完整审批流转过哪些表我们拿一个最简单的请假流程来走一遍数据流转你通过RepositoryService部署一个BPMN文件时引擎会往ACT_RE_DEPLOYMENT写入一条部署记录把BPMN XML和流程图片存进ACT_GE_BYTEARRAY然后解析XML生成流程定义写入ACT_RE_PROCDEF。流程启动时引擎往ACT_RU_EXECUTION写一条根执行实例Root Execution同时往ACT_HI_PROCINST写一条对应的历史流程实例记录。如果启动时设置了流程变量变量写入ACT_RU_VARIABLE同时ACT_HI_VARINST也会同步一条。流程走到第一个用户任务节点引擎往ACT_RU_TASK写入一条待办任务同时往ACT_HI_ACTINST写一条当前活动节点的历史记录。如果指定了办理人ASSIGNEE字段直接写入如果指定的是候选人则往ACT_RU_IDENTITYLINK写入候选关系。审批人点击完成引擎删除ACT_RU_TASK里的待办记录更新ACT_HI_ACTINST的结束时间和耗时往ACT_HI_TASKINST写一条完整的历史任务记录。整个流程走完引擎删除ACT_RU_EXECUTION里的根执行实例记录更新ACT_HI_PROCINST的结束时间。这个流程走完你就会发现一个核心规律**集群中真正决定“现在流程状态是什么”的是ACT_RU_表而决定“这个流程曾经发生过什么”的是ACT_HI_表。运行态和归档态是两套并行的存储这也是为什么很多时候API查到的结果和直觉不一致——你可能查的是HistoryService而不是RuntimeService。3. 部署与流程定义ACT_RE_*的字段注释与使用要点3.1 ACT_RE_DEPLOYMENT每次发布的档案袋这张表记录了每一次部署操作每次调用RepositoryService.createDeployment().addClasspathResource(...).deploy()就会生成一条记录。字段不算多但有一个点经常有人搞错。字段类型注释ID_varchar部署ID主键NAME_varchar部署名称不唯一CATEGORY_varchar分类可以在部署时指定KEY_varchar部署Key同一分类下可做重复部署保护TENANT_ID_varchar租户ID多租户场景用DEPLOY_TIME_datetime部署时间ENGINE_VERSION_varchar引擎版本标记实际使用中ACT_RE_DEPLOYMENT最容易被忽略的是DEPLOY_TIME_字段。排查“为什么流程定义没有更新”这类问题时第一件事就是看这个字段——很多时候你以为部署了新版本实际上部署操作失败了或者部署的是同一个BPMN文件但引擎没有生成新版本流程定义。另外这张表只记录“部署事件”不直接关联流程定义流程定义是通过ACT_RE_PROCDEF.DEPLOYMENT_ID_反查过来的。3.2 ACT_RE_PROCDEF流程定义的关键字段这是流程定义的核心表一个部署包可以包含多个流程定义文件但同一个流程Key下只允许存在一个最新版本。这张表里最关键的字段是ID_、KEY_和VERSION_。ACT_RE_PROCDEF主要字段字段类型注释ID_varchar流程定义ID格式是“流程Key:版本号:随机数”REV_int乐观锁版本号每次更新自动加1CATEGORY_varchar分类来自XML的targetNamespaceNAME_varchar流程定义名称来自XML的name属性KEY_varchar流程定义Key来自XML的id属性VERSION_int版本号同一个Key下每次部署自动递增DEPLOYMENT_ID_varchar对应的部署记录IDRESOURCE_NAME_varcharBPMN XML在ACT_GE_BYTEARRAY中的资源名DGRM_RESOURCE_NAME_varchar流程图片资源名没配图则为空HAS_START_FORM_KEY_tinyint是否有启动表单KeySUSPENSION_STATE_tinyint挂起状态1为激活2为挂起TENANT_ID_varchar租户ID为什么要单独强调ID_字段的组成因为项目里经常有同事拿这个ID去做字符串截取或者用split(:)去解析里面包含的流程Key和版本号。这个约定在大多数情况下是成立的但引擎并不保证格式永远不变如果你在生产环境依赖这个格式做逻辑判断一旦版本升级就可能出问题。正确做法是通过ProcessDefinitionQuery来获取或者在BPMN设计时用自定义属性把业务标识存好。还有一个实际经验如果流程定义被挂起SUSPENSION_STATE_ 2新发起的流程会直接报错但已经在执行中的流程实例不受影响。运维时如果遇到“创建流程失败但不知道原因”可以先查这张表的状态。3.3 ACT_RE_MODEL与ACT_PROCDEF_INFO建模器和配置扩展ACT_RE_MODEL是Flowable Modeler在线流程设计器用于保存模型草稿的表如果你用代码直接部署BPMN这张表基本不会被写入。字段包括NAME_、KEY_、CATEGORY_、CREATE_TIME_、LAST_UPDATE_TIME_、VERSION_、META_INFO_存放模型的缩略图信息、EDITOR_SOURCE_VALUE_ID_模型的BPMN XML存到BYTEARRAY的ID、EDITOR_SOURCE_EXTRA_VALUE_ID_模型的额外资源如JSON。ACT_PROCDEF_INFO这张表容易被忽略但它记录的是流程定义在引擎内部的扩展信息核心字段就三个字段类型注释ID_varchar主键同时关联ACT_RE_PROCDEF.ID_REV_int乐观锁版本INFO_JSON_ID_varchar关联ACT_GE_BYTEARRAY中存放的流程定义JSON配置这里面的INFO_JSON_ID_存的是Flowable引擎解析出来的流程定义详细信息包含节点ID、事件监听器、执行监听器之类的结构化描述。当你用API获取一个BPMN模型、或者调试执行监听器为什么没触发时查这张表对接BYTEARRAY里的JSON内容会有帮助。4. 运行时状态ACT_RU_*核心表字段拆解4.1 ACT_RU_EXECUTION执行实例的树形结构如果你只想记住一张表那就是ACT_RU_EXECUTION。它是Flowable运行时最重要的表记录了所有“正在执行”的流程实例节点。但也是误解最多的一张表最大的坑在于它里面不是一条记录对应一个流程实例而是一条记录对应一个执行实例一个流程实例下面会有多个执行实例。为什么会有多个因为Flowable把一次流程运行看成是一棵树。流程启动时创建根执行实例根执行实例的ID_就是PROC_INST_ID_。遇到并发网关时引擎为每个分支创建子执行实例PARENT_ID_指向父执行实例遇到子流程时创建独立执行实例遇到多实例节点每个循环体都是一个子执行实例。所以你在表里会看到多行数据拥有相同的PROC_INST_ID_但它们各自代表不同的执行上下文。ACT_RU_EXECUTION关键字段字段类型注释ID_varchar执行实例ID主键REV_int乐观锁版本PROC_INST_ID_varchar流程实例ID根执行实例的自己等于ID_BUSINESS_KEY_varchar业务主键PARENT_ID_varchar父执行实例ID根执行为空PROC_DEF_ID_varchar流程定义IDSUPER_EXEC_varchar上级执行实例ID用于调用子流程回跳ROOT_PROC_INST_ID_varchar根流程实例IDACT_ID_varchar当前正在执行的节点IDIS_ACTIVE_tinyint是否活跃活跃正在执行IS_CONCURRENT_tinyint是否并发分支IS_SCOPE_tinyint是否作用域执行实例IS_EVENT_SCOPE_tinyint是否事件作用域SUSPENSION_STATE_tinyint挂起状态NAME_varchar调用活动名称这里必须展开解释IS_ACTIVE_和IS_SCOPE_这两个字段因为排查问题经常用它们。IS_ACTIVE_表示这个执行实例是否正在等待某个行为完成。流程等待在用户任务上时对应执行实例的IS_ACTIVE_ 1。IS_SCOPE_表示该执行实例是否是一个作用域容器根执行实例、子流程执行实例这些一般是作用域执行实例而并发分支的普通子执行实例IS_SCOPE_ 0。我在排查“流程卡住”的问题时标准的SQL套路是这样的SELECT * FROM ACT_RU_EXECUTION WHERE PROC_INST_ID_ #{procInstId} AND IS_ACTIVE_ 1;把这条SQL查出来的ACT_ID_和流程定义XML里的节点做对照一眼就能看到流程到底停在了哪个节点上。前面提到那个“审批通过但流程没走”的Bug最后就是查了这张表发现流程停在一个不存在的节点ID上——因为BPMN改动后历史流程实例还在按旧的节点ID执行新代码里没有对应的行为处理器流程就直接挂在那了。4.2 ACT_RU_TASK待办任务的字段与常见查询ACT_RU_TASK存的是所有未完成的任务审批人看到的“待办列表”本质上就是查这张表。流程节点每经过一个用户任务就往这张表插一条记录任务完成或流程终止记录被删除。这张表的字段大家都熟我只挑容易踩坑的讲。ACT_RU_TASK关键字段字段类型注释ID_varchar任务ID主键EXECUTION_ID_varchar所在执行实例IDPROC_INST_ID_varchar流程实例IDPROC_DEF_ID_varchar流程定义IDNAME_varchar任务名称BUSINESS_KEY_varchar业务主键TASK_DEF_KEY_varchar任务定义Key代码里定位节点用OWNER_varchar任务发起人/代理人ASSIGNEE_varchar办理人DELEGATION_varchar委派类型PENDING表示已委派RESOLVED表示已处理PRIORITY_int优先级默认50CREATE_TIME_datetime创建时间DUE_DATE_datetime到期时间SUSPENSION_STATE_tinyint挂起状态FORM_KEY_varchar表单Key第一个容易踩的坑查询用户的待办任务不应该去查ACT_RU_IDENTITYLINK而是直接查ASSIGNEE_。很多初学者在实现“查询我的待办”时会把任务表和身份关系表join起来查出一堆重复数据。Flowable的默认行为是指定了办理人的任务ASSIGNEE_直接写用户ID只有使用候选人candidate机制时候选关系才写进ACT_RU_IDENTITYLINK。第二个坑TASK_DEF_KEY_和ACT_RU_EXECUTION.ACT_ID_的对应关系。TASK_DEF_KEY_是BPMN文件里userTask idapprove name审批/这个id属性它是静态的同一个流程定义下不变化。而ACT_ID_是运行时当前节点的ID两者在大多数情况下是一致的值但遇到子流程或多实例时ACT_ID_可能有额外的后缀表达。所以代码里定位节点用TASK_DEF_KEY_更可靠不要用任务名称NAME_因为NAME_是展示给用户看的可以改。4.3 ACT_RU_VARIABLE变量落在哪张物理表流程变量是业务系统和流程引擎交换数据的主要方式ACT_RU_VARIABLE这张表把不同数据类型的变量分别存在不同的物理字段里。理解它的存储规则直接关系到你能不能正确写出“按变量值查流程实例”的SQL。字段类型注释ID_varchar变量记录IDREV_int乐观锁版本TYPE_varchar变量类型string、integer、long、double、date、boolean等NAME_varchar变量名EXECUTION_ID_varchar所属执行实例IDPROC_INST_ID_varchar所属流程实例IDTASK_ID_varchar所属任务ID任务变量才有值BYTEARRAY_ID_varchar序列化大对象时对应ACT_GE_BYTEARRAY的IDDOUBLE_double存储double类型变量LONG_bigint存储long、integer、date类型变量TEXT_varchar(4000)存储string类型变量长度较长TEXT2_varchar(4000)存储string类型变量的额外部分VAR_SCOPE_varchar变量作用域ID这里要补充一点布尔类型的值在Flowable里用LONG_字段存储0表示false1表示true。日期类型也是存成LONG_内容是时间戳。如果你在数据库客户端里直接看ACT_RU_VARIABLE表会发现一个boolean变量在LONG_列显示为1不要觉得是数据错了。变量的作用域规则是读取变量时引擎先在任务变量中找找不到再往流程实例变量找再到父执行实例找。这种“就近查找”的设计导致了另一个常见坑你在代码里给某个任务设置了变量但任务完成的瞬间这条变量可能就没了因为任务变量的生命周期绑定在任务上。如果想让变量跟着流程实例走用runtimeService.setVariable(procInstId, name, value)别用taskService.setVariable(taskId, ...)设完再由用户任务往回传。4.4 定时任务、事件订阅、身份关联等辅助运行表除了上面三张核心表ACT_RU_*系列还有几张辅助表在排查具体问题时同样有用。ACT_RU_IDENTITYLINK任务候选关系表。字段有ID_、REV_、GROUP_ID_、USER_ID_、TYPE_candidate/owner/starter/participant等、TASK_ID_、PROC_INST_ID_、PROC_DEF_ID_。TYPE_的枚举值很容易让人疑惑记住一个小技巧ASSIGNEE不在这张表里CANDIDATE都在这里。实现了“按角色查待办”的业务系统核心查询就是WHERE TYPE_ candidate AND GROUP_ID_ #{roleId}。ACT_RU_TIMER_JOB定时器任务表。字段包括ID_、REV_、TYPE_timer、LOCK_EXP_TIME_锁过期时间、LOCK_OWNER_锁持有者、EXCLUSIVE_是否排他、HANDLER_TYPE_处理器类型、HANDLER_CFG_处理器配置、DUEDATE_执行时间、REPEAT_重复表达式。排查“边界定时器没触发”“定时器任务重复执行”这类问题优先查这张表。要注意的是定时器并不总在指定时间执行它要等流程引擎的Job Executor扫到所以DUEDATE_是“最早可执行时间”而不是“保证执行时间”。ACT_RU_EVENT_SUBSCR事件订阅表。信号事件、消息事件都记录在这里字段有EVENT_TYPE_、EVENT_NAME_、EXECUTION_ID_、PROC_INST_ID_、ACTIVITY_ID_、CONFIGURATION_存放事件的key如消息名。写完信号抛出代码但接收方始终没反应时先看这张表里有没有对应的订阅记录——没有订阅抛了信号也是白抛。ACT_RU_SUSPENDED_JOB和ACT_RU_DEADLETTER_JOB挂起的异步任务和死信任务表。异步执行失败后重试次数用尽作业会被转到DEADLETTER表。排产异步逻辑失败时先查死信表里有什么这是最直接的线索。ACT_RU_HISTORY_JOB异步历史执行器处理的任务记录如果你开了异步历史配置这张表会有数据。Flowable 6.8.0里还有一张ACT_RU_JOB但从6.0开始就是兼容遗留表了新代码不再使用遇到这张表里的旧数据直接忽略即可。4.5 运行时表常见查询模式这几张运行时表配合使用可以解决绝大多数“查当前状态”的问题。我把自己最常用的两套查询贴出来。查询某个业务单号当前的审批节点和办理人SELECT e.PROC_INST_ID_, e.ACT_ID_ AS current_node_id, t.ID_ AS task_id, t.NAME_ AS task_name, t.ASSIGNEE_, t.CREATE_TIME_ FROM ACT_RU_EXECUTION e LEFT JOIN ACT_RU_TASK t ON t.EXECUTION_ID_ e.ID_ AND t.SUSPENSION_STATE_ 1 WHERE e.IS_ACTIVE_ 1 AND e.PROC_INST_ID_ #{procInstId};查询某个用户的全部待办含候选任务SELECT t.* FROM ACT_RU_TASK t WHERE t.ASSIGNEE_ #{userId} OR t.ID_ IN ( SELECT li.TASK_ID_ FROM ACT_RU_IDENTITYLINK li WHERE li.USER_ID_ #{userId} OR li.GROUP_ID_ IN (SELECT GROUP_ID_ FROM ACT_ID_MEMBERSHIP WHERE USER_ID_ #{userId}) );5. 历史归档ACT_HI_*报表与审计查询的套路5.1 ACT_HI_PROCINST流程实例级历史ACT_RU_*表在流程结束后会清掉大部分数据但ACT_HI_*表会把完整轨迹保留下来。ACT_HI_PROCINST是历史流程实例表每个流程实例在这里都有一条记录相当于运行态的根执行实例在结束后的“墓碑”。字段类型注释ID_varchar历史流程实例IDPROC_INST_ID_varchar流程实例ID通常等于ID_BUSINESS_KEY_varchar业务主键PROC_DEF_ID_varchar流程定义IDSTART_TIME_datetime流程开始时间END_TIME_datetime流程结束时间没走完为空DURATION_bigint流程总耗时单位毫秒START_USER_ID_varchar发起人START_ACT_ID_varchar开始节点IDEND_ACT_ID_varchar结束节点IDSUPER_PROCESS_INSTANCE_ID_varchar父流程实例ID用于子流程回查DELETE_REASON_varchar删除原因字段不多但查询技巧不少。统计“最近30天发起的流程数量”直接用START_TIME_过滤统计“平均审批时长”要用END_TIME_ - START_TIME_也就是DURATION_字段。但注意一个细节DURATION_只有在流程正常结束时才会被写入如果流程被删除或异常终止这个字段可能是空的。DELETE_REASON_这个字段最常见的一个值就是completed表示正常完成。如果流程被调用deleteProcessInstance主动删除DELETE_REASON_会写入你传入的原因字符串。做审计报表时区分“正常结束”和“被删除”的流程实例就靠这个字段。5.2 ACT_HI_TASKINST与ACT_HI_ACTINST任务和节点的耗时统计ACT_HI_TASKINST是历史任务表对应运行时ACT_RU_TASK的归档版本。它的字段和运行时任务表很接近但多了两个和“耗时”直接相关的关键字段CLAIM_TIME_签收时间和DURATION_任务办理时长。字段类型注释ID_varchar历史任务IDTASK_DEF_KEY_varchar任务定义KeyPROC_INST_ID_varchar流程实例IDEXECUTION_ID_varchar执行实例IDNAME_varchar任务名称PARENT_TASK_ID_varchar父任务IDOWNER_varchar发起人ASSIGNEE_varchar办理人START_TIME_datetime任务开始时间END_TIME_datetime任务结束时间DURATION_bigint任务处理耗时毫秒DELETE_REASON_varchar删除原因completed、deleted等CLAIM_TIME_datetime办理人签收时间DUE_DATE_datetime到期时间FORM_KEY_varchar表单KeyACT_HI_ACTINST是历史活动实例表这个表记录流程定义中每一个节点包括开始、结束、网关、任务的实际执行情况是所有历史表中最细粒度的记录。字段类型注释ID_varchar活动实例IDPROC_DEF_ID_varchar流程定义IDPROC_INST_ID_varchar流程实例IDEXECUTION_ID_varchar执行实例IDACT_ID_varchar节点IDTASK_ID_varchar若该活动是用户任务关联历史任务IDCALL_PROC_INST_ID_varchar调用子流程时子流程实例IDACT_NAME_varchar活动名称ACT_TYPE_varchar活动类型startEvent、userTask、serviceTask等ASSIGNEE_varchar办理人START_TIME_datetime活动开始时间END_TIME_datetime活动结束时间DURATION_bigint活动耗时毫秒DELETE_REASON_varchar删除原因ACT_HI_ACTINST最实用的价值是做流程瓶颈分析。统计一个流程实例在每个节点上的耗时可以很快定位“到底哪个环节拖慢了整体进度”。比如SELECT ACT_NAME_, ACT_TYPE_, AVG(DURATION_) AS avg_duration_ms, COUNT(*) AS occur_count FROM ACT_HI_ACTINST WHERE PROC_DEF_ID_ #{procDefId} AND END_TIME_ IS NOT NULL GROUP BY ACT_NAME_, ACT_TYPE_ ORDER BY avg_duration_ms DESC;这张表还有一个隐蔽的坑ACT_HI_ACTINST包含所有节点包括开始事件、结束事件、网关而这些节点的DURATION_往往很小或者为0。统计业务审批耗时的时候一定要用ACT_TYPE_ userTask过滤否则平均耗时会被网关拉低。5.3 ACT_HI_VARINST与ACT_HI_DETAIL变量历史的两种粒度ACT_HI_VARINST是历史流程变量表记录的是每个变量的“最终状态”。也就是说一个流程变量名在流程生命周期内被更新了10次这张表里只保留最近一次的值。与之对应ACT_HI_DETAIL记录的是变量每次更新的明细。ACT_HI_VARINST核心字段字段类型注释ID_varchar变量历史IDPROC_INST_ID_varchar流程实例IDEXECUTION_ID_varchar执行实例IDTASK_ID_varchar任务IDNAME_varchar变量名字段值可能带后缀bytearray、double、long、textVAR_TYPE_varchar变量类型REV_int变量版本号BYTEARRAY_ID_varchar大对象关联BYTEARRAYDOUBLE_double数字值LONG_bigint长整型值TEXT_varchar(4000)文本值TEXT2_varchar(4000)文本值扩展ACT_HI_DETAIL的表结构和变量存储方式类似但它比ACT_HI_VARINST多一个字段叫ACT_INST_ID_关联ACT_HI_ACTINST的ID_。这意味着你可以知道“这步操作执行时某个变量的值是什么”。做流程审计时这是黄金数据比如审批金额从10万变成12万通过ACT_HI_DETAIL就能查出来是哪一步、哪个节点改的。但ACT_HI_DETAIL这张表的数据量膨胀极快这是所有Flowable历史表里最需要关注归档策略的一张。如果你不需要变量级审计建议在引擎配置里关闭变量更新明细记录功能只保留ACT_HI_VARINST。5.4 流程协同记录ACT_HI_COMMENT与ACT_HI_ATTACHMENTACT_HI_COMMENT存审批意见和事件日志ACT_HI_ATTACHMENT存附件信息。这两张表用API调用对应的TaskService.addComment、addAttachment时会写入。ACT_HI_COMMENT主要字段字段类型注释ID_varchar主键TYPE_varchar类型event事件或comment评论TIME_datetime创建时间USER_ID_varchar操作人TASK_ID_varchar关联任务IDPROC_INST_ID_varchar关联流程实例IDACTION_varchar动作类型MESSAGE_varchar(4000)评论内容FULL_MSG_longtext完整内容注意TYPE_的区别Flowable在流程流转时自动写入的事件记录如“任务已创建”“任务已办理”TYPE_是event人工添加的审批意见TYPE_是comment。查询审批意见时如果只取comment的就不会混入系统自动生成的事件流水。6. 身份、通用与系统表ACT_ID_、ACT_GE_、ACT_EVT_LOG6.1 ACT_ID_*内置用户体系在什么场景下才真的会用ACT_ID_USER、ACT_ID_GROUP、ACT_ID_MEMBERSHIP这三张表是Flowable自带的用户、用户组和成员关系表。很多项目兴致勃勃地在这三张表上做业务用户同步然后过段时间就后悔了。先说结论这三张表建议只用来存流程引擎需要的基础身份数据不要把业务系统的用户大表同步到这几张表里。Flowable本身不依赖这些表来实现权限控制在流程定义里指定一个不存在的用户ID也完全不会报错。它的IdentityService主要是为那些没有独立用户体系的轻量应用准备的——比如一个内部小工具没有用户中心直接在Flowable表里加点账号应付一下。表名关键字段说明ACT_ID_USERID_, REV_, FIRST_, LAST_, EMAIL_, PWD_用户表PWD_不存明文密码是加密后的哈希ACT_ID_GROUPID_, REV_, NAME_, TYPE_用户组表TYPE_可以用assignment、security-role等ACT_ID_MEMBERSHIPUSER_ID_, GROUP_ID_成员关系表联合主键真正的项目里我们一般只把业务系统的用户中心作为唯一数据源流程引擎配置一个UserManager接口的实现类引擎需要校验用户或者查询组信息时走自定义实现不走这三张表。这是比较合理且省事的做法。6.2 ACT_GE_PROPERTY与ACT_GE_BYTEARRAY引擎元数据和二进制存储ACT_GE_PROPERTY是引擎的属性表专门存全局性的内部配置。刚开始接触Flowable的人可能会惊讶数据库里居然有一张表就三列NAME_、VALUE_、REV_里面存的全是schema版本、next.dbid这种看似无关紧要的值。字段类型注释NAME_varchar属性名如schema.version、next.dbidVALUE_varchar属性值REV_int版本号next.dbid是一个很重要的属性。Flowable的主键不是UUID而是一个全局递增ID它的生成方式就是读next.dbid这个值。如果这张表的记录被误删或改错引擎可能无法再生成新的主键。所以备份数据库时ACT_GE_PROPERTY这张表必须包含在内而且不要对里面的值做任何手工修改。ACT_GE_BYTEARRAY是二进制资源表包括部署的BPMN文件、流程图、表单定义JSON、以及序列化的自定义变量值。字段有ID_、REV_、NAME_、DEPLOYMENT_ID_、BYTES_、GENERATED_。其中GENERATED_表示该资源是否由引擎自动生成如自动生成的流程图片就是1。在实际运维里这张表经常用来找回丢失的流程定义文件。如果某个项目的BPMN源文件丢了但数据库还在连接ACT_RE_PROCDEF的RESOURCE_NAME_就能从BYTES_字段导出原始的BPMN XML。6.3 ACT_EVT_LOG事件日志表ACT_EVT_LOG是引擎事件日志记录表默认情况下它是被禁用的需要在引擎配置里显式开启事件日志功能才会写入数据。字段类型注释LOG_NR_bigint自增主键TYPE_varchar日志类型PROC_DEF_ID_varchar流程定义IDPROC_INST_ID_varchar流程实例IDEXECUTION_ID_varchar执行实例IDTASK_ID_varchar任务IDTIME_STAMP_datetime日志时间USER_ID_varchar操作用户DATA_longtext日志数据这张表在某些极端场景下是救命稻草。比如流程数据因为误操作被批量删除或者有人直接在数据库里手工改数据导致流程状态异常开启了事件日志之后都能追查到操作痕迹。但要注意它的数据量和日志量成正比生产环境长期开启需要配合定期清理策略。7. 实战中的坑与经验字段注释背后的设计意图7.1 最容易误解的几个字段写了这么多年Flowable的SQL我总结出几个高频误解点这里一一说明。ACT_RU_EXECUTION.ID_不等于ACT_RU_TASK.PROC_INST_ID_。我刚入行的时候写过一条SQL把ACT_RU_TASK的ID_和ACT_RU_EXECUTION的ID_做关联结果查出来的数据乱七八糟。正确关联关系是ACT_RU_TASK.EXECUTION_ID_指向ACT_RU_EXECUTION.ID_ACT_RU_TASK.PROC_INST_ID_指向根执行实例的ID_。也就是说任务表里有一个“所在分支”和一个“所属流程实例”两回事。REV_字段是乐观锁版本号不是记录的创建版本。Flowable的所有业务表都有REV_字段它的作用是防止并发更新冲突。每次UPDATE操作都会把REV_加1更新时引擎会带上旧的REV_作为条件。如果你用SQL直接修改这张表的数据必须同步修改REV_一般是加1否则引擎后续再更新这条记录时会因为版本对不上而报OptimisticLockingException。这是直接改库的大忌。SUSPENSION_STATE_字段在运行时表和历史表里同时存在值的含义一致1是激活2是挂起。TENANT_ID_字段容易被忽略。如果你的引擎配置了租户ID所有查询都要带上租户条件。遇到过一种情况代码里设置了租户ID部署流程但运行时查询没带租户条件导致查不到任何流程定义。这类问题不看表结构很难定位。7.2 我常用的一组查询SQL模板排查问题的时候我通常按照“流程实例→执行实例→任务→变量”的顺序层层推进这里给出几条可以直接抄的SQL。按业务单号反查整个流程的全局信息SELECT pi.ID_ AS proc_inst_id, pi.BUSINESS_KEY_, pd.KEY_ AS proc_def_key, pd.VERSION_, pi.START_TIME_, pi.END_TIME_, pi.DURATION_, pi.START_USER_ID_ FROM ACT_HI_PROCINST pi LEFT JOIN ACT_RE_PROCDEF pd ON pi.PROC_DEF_ID_ pd.ID_ WHERE pi.BUSINESS_KEY_ #{businessKey};查某个流程实例的所有执行分支和当前节点状态SELECT e.ID_, e.PARENT_ID_, e.IS_ACTIVE_, e.IS_CONCURRENT_, e.IS_SCOPE_, e.ACT_ID_, t.ID_ AS task_id, t.NAME_ AS task_name, t.ASSIGNEE_ FROM ACT_RU_EXECUTION e LEFT JOIN ACT_RU_TASK t ON t.EXECUTION_ID_ e.ID_ WHERE e.PROC_INST_ID_ #{procInstId} ORDER BY e.ID_;查某个流程在某个用户身上积压了多少待办SELECT PROC_DEF_ID_, COUNT(*) AS todo_count FROM ACT_RU_TASK WHERE ASSIGNEE_ #{userId} GROUP BY PROC_DEF_ID_;统计流程每个节点的平均耗时不含网关节点的干扰SELECT ACT_NAME_, AVG(DURATION_) AS avg_duration_ms, COUNT(*) AS execute_count FROM ACT_HI_ACTINST WHERE PROC_DEF_ID_ #{procDefId} AND ACT_TYPE_ userTask AND END_TIME_ IS NOT NULL GROUP BY ACT_NAME_ ORDER BY avg_duration_ms DESC;7.3 数据增长与清理策略Flowable的数据增长大头一般在ACT_HI_*系列尤其是ACT_HI_ACTINST和ACT_HI_DETAIL。如果项目运行一两年没有清理策略这两张表动辄上千万行查询性能会明显下降。我的经验是分三步处理。第一步先明确哪些历史数据要保留。对大多数业务系统来说历史流程实例至少要保留两三年供审计查询但变量历史明细ACT_HI_DETAIL可以只保留几个月甚至关闭。流程节点的明细轨迹ACT_HI_ACTINST可以按流程定义做取舍核心流程保留低频流程归档。第二步用Flowable自带的HistoryService删除旧数据不要直接DELETE。historyService.deleteHistoricProcessInstance(procInstId)这条API会级联删除该流程实例的所有历史记录包括变量、任务、活动明细。批量删除时加上时间过滤按天分批执行避免一次性删除太多造成锁表。第三步对确实需要长期保留的ACT_HI_PROCINST做冷热分离。可以定期归档到独立的历史库或者数据仓库主库只保留近期的活跃数据。归档时注意保留ACT_GE_BYTEARRAY表里的流程定义资源否则历史数据反查BPMN源文件时会对不上。7.4 最后的实操心得不要直接改库分享一个我踩过的最贵的坑。有一年排查一个线上问题我发现某条流程实例的ACT_RU_TASK记录里ASSIGNEE_写错了人导致审批一直卡在错误的办理人那里其他同事催了好几天。当时图省事直接用SQL把ASSIGNEE_改成了正确的人以为改完任务就能正常流转。结果任务确实能看到了但当这个任务被完成时引擎抛了一个OptimisticLockingException之后无论怎么重试流程都无法继续往下走。原因就是我前面说的REV_版本号。手工改数据时没同步递增REV_引擎内部的更新条件永远匹配不上整个流程实例直接僵死最后只能靠删除历史重新发起流程才解决。从那之后我给自己定了一条铁律Flowable的表数据只能通过引擎的Service API去修改数据库只用来做只读查询。不管多么紧急的问题走API顶多多写几行代码但至少引擎的内部状态是一致的。实在需要直接改库至少要做两件事一是备份完整的流程实例数据二是同步递增REV_并且把关联的执行实例、任务、变量都一并检查一遍。Flowable 6.8.0这套表结构整体设计得相当清晰它的复杂度主要来自运行态同一份流程数据被拆成了多个维度存储。你不需要一开始就精通全部表核心先把ACT_RU_EXECUTION、ACT_RU_TASK、ACT_HI_PROCINST、ACT_HI_ACTINST这几张表吃透遇到其他表的时候再按前缀归类、按字段注释检索基本就能应对绝大多数开发、排障和统计需求。我在实际维护中还有个习惯每个季度从ACT_HI_ACTINST跑一次节点耗时统计看看哪些审批节点越来越慢尽早介入而不是等问题被用户投诉了才去查。这套方法论现在已经成了我接手任何Flowable项目时第一个落地的事情。
返回列表