提到工作流数据,就不得不提业务数据。作为最直接的区分,我们将存储于业务系统中的数据称为业务数据,将存储于工作流系统中的数据称为工作流数据。根据 WFMC 定义,我们将工作流数据分为工作流控制数据和工作流相关数据。
1、 工作流控制数据 。指被工作流系统管理的系统数据,这些数据包括了与流程实例和任务实例相关的执行数据,例如流程实例的状态、执行时间等信息、任务实例的执行者、执行时间、状态、紧急程度等。
2、 工作流相关数据 。指与业务流程相关的数据。工作流相关数据又具体分为 3 种:
办公系统是基于SOA和BPM技术架构,采用Cordys平台建设的信息系统,数据库为Oracle,实现了办公业务集中化、全面化管理,初步达到集中办公平台的目的,系统主要包括公文管理、通用办公、专业流程、综合信息、专业办公五大子系统。
在此工作流数据有哪些应用场景呢?
办公系统已经上线使用4年多了,其中某流程处理数据表(process_activity)的记录已经有5千多万条,其中在两年前处理过,并迁移走近2千万条,实践证明此处理方案有效,对业务影响可以忽略,特殊情况人工处理就可以。
如上图所示,表的情况如下:
序号 |
表名 |
功能介绍 |
1 |
Process_instance |
流程实例 |
2 |
process_activity |
流程实例处理活动 |
3 |
MESSAGE_TRACK |
流程实例处理消息活动 |
4 |
MESSAGE |
流程实例处理消息 |
5 |
NOTIFICTION_SEARCH_DATA |
|
6 |
TASK_LIST |
待办任务处理活动(业务) |
7 |
WORKFLOW_INSTANCE_TRANSLOG |
流程流转记录(业务) |
8 |
PROCESS_INSTANCE_DATA |
在途流程实例(未办结流程实例) |
上表中,Cordys流程核心表有Process_instance、process_activity、MESSAGE_TRACK、MESSAGE、NOTIFICTION_SEARCH_DATA、PROCESS_INSTANCE_DATA,其中PROCESS_INSTANCE_DATA是在途流程信息,不需要清理,其他的需要适当的情况,而业务表的TASK_LIST中数据,办结流程的任务迁移到task_list_finished表中,表结构完全一致。
流程办结数据与在途数据,经过对比分析,最多记录表process_activity表的在途记录数也不过百万级别,而办结数据已经3千多万,因此处理方案的基本原理是:
由于PROCESS_INSTANCE_DATA表是在途流程实例,不需要进行处理。
办结流程可以根据ApptoolKit中workflow_instance表来识别(例如:STATUS = 'COMPLETE'),同时,还需要识别其中的子流程,以及嵌套子流程(例如:通过PARENT_GUID和GUID两个字段来进行识别),识别出三级嵌套就足够了,再特殊的,人工处理。
把识别出来的办结流程标识,去重后放置在临时表中,后续使用。
任务表(task_list)主要是存储在办和办结任务数据,办结任务数据要定期迁移到办结任务表中(task_list_finish)。
任务表task_list是分区表,为了快速插入数据,在执行插入操作时,一般就逐个分区进行插入操作。
由于Cordys核心表用到LOB字段,用于存储XML格式数据,这样将占有大量磁盘空间(按现有业务量估算,3年预计为1T),因此,在删除表的时候,最好也移除表空间。
1、策划分析阶段工作
全面获取存储、表空间使用情况,以及相关表数据量,并通过查询等多种模式模拟数据处理过程及所消耗的时间,并据此预估数据处理停业时间,制定作业计划。
导出建表、索引SQL语句(因为系统在运维过程中,可能要发生微小的改变,因此,每次都要重新生产SQL脚本)。
2、发通知,停业,停应用服务
3、表更名、索引更名
4、新建表,处理数据
根据实际情况,如有必要则新建表空间,把表建到新表空间上。
5、重建索引、存储过程、触发器等
按事先准备好的脚本执行。
6、启动应用服务,检查平台
7、测试流程相关应用,并处理问题
8、清理测试数据
9、恢复业务。
--修改表名
alter table notification_search_data rename to bak_notification_search_data;
alter table message_track rename to bak2011_message_track;
alter table message rename to bak2011_message;
alter table process_activity rename to bak2011_process_activity;
alter table process_instance_data rename to bak2011_process_instance_data;
alter table process_instance rename to bak2011_process_instance;
alter table workflow_instance rename to bak2011_workflow_instance;
alter table workflow_instance_translog rename to bak_workflow_instance_translog;
--修改约束
alter table MESSAGE rename constraint MESSAGEPKEY to MESSAGEPKEYtmp;
alter table MESSAGE_TRACK rename constraint MESSAGETRACKPKEY to MESSAGETRACKPKEYtmp;
alter table PROCESS_ACTIVITY rename constraint SYS_C006752 to SYS_C006752tmp;
略
--修改索引
alter index IX_PROCESS_INSTANCE_START_TIME rename to IX_PROCESS_INSTANCE_STARTtmp;
alter index IX_PROCESS_NAME rename to IX_PROCESS_NAMEtmp;
alter index IX_PROCESS_INSTANCE_END_TIME rename to IX_PROCESS_INSTANCE_END_Ttmp;
alter index SYS_C006752 rename to SYS_C006752tmp;
修改时,最好先修改索引、约束,最后修改表名。
使用存储过程处理数据,最少分成两个阶段,第一阶段处理流程实例数据,根据处理结果,提取流程消息数据,再进行第二阶段流程消息数据处理,由于插入数据时,表不适宜建索引,所以在process_activity数据处理完成后,为此表建主键、索引,才方便提取消息message_id。
select distinct t.message_id
from cordys.process_activity t
where not exists (select t1.message_id from cordys.message t1 where t1.message_id = t.message_id);
1、处理流程实例数据
create or replace procedure pro_data_new1 is
--游标,取出待处理的办结流程guid
cursor wait_worklow_instance is
select t.workflow_instance, t.process_instance from wait_worklow_instance_temp t where t.process_instance is not null
and not exists (select t1.guid from cordys.workflow_instance t1 where t1.guid = t.workflow_instance);
v_instance_count number(9);
v_num number(9); --提交计数
v_sqlcode number;
v_sqlmsg varchar2(2000);
v_instance_guid cordys.workflow_instance.guid%type;
v_process_instance cordys.workflow_instance.process_instance%type;
v_log_id number;
begin
v_instance_count := 0;
v_num := 0;
v_log_id := 0;
select count(*) into v_log_id from tmp_workflow_data_log;
if v_log_id > 0 then
select max(id) into v_log_id from tmp_workflow_data_log;
end if;
v_log_id := v_log_id + 1;
insert into tmp_workflow_data_log(id,state,logdate) values(v_log_id,'Start Test!',sysdate);
open wait_worklow_instance;
loop
fetch wait_worklow_instance into v_instance_guid, v_process_instance;
exit when wait_worklow_instance%notfound;
select count(*)
into v_instance_count from cordys.bak2011_workflow_instance where guid = v_instance_guid;
if v_instance_count > 0 then
--迁移流程实例数据
insert into cordys.process_instance
select * from cordys.bak2011_process_instance where instance_id = v_process_instance;
--迁移流程活动数据
insert into cordys.process_activity
select * from cordys.bak2011_process_activity where instance_id = v_process_instance;
end if;
if v_num > 1000 then
v_log_id := v_log_id + 1;
insert into tmp_workflow_data_log(id,state,logdate) values(v_log_id,'insert 1001 record workflow',sysdate);
commit;
v_num := 0;
else
v_num := v_num + 1;
end if;
end loop;
v_log_id := v_log_id + 1;
insert into tmp_workflow_data_log(id,state,logdate) values(v_log_id,'insert workflow data complete',sysdate);
commit;
close wait_worklow_instance;
commit;
exception
when others then
v_sqlcode := sqlcode;
v_sqlmsg := substr(sqlerrm, 1, 2000);
dbms_output.put_line(v_sqlcode || '::' || v_sqlmsg);
end;
--其中,每千条数据提交一次,为了是数据处理稳定,并根据日志监控进程。
2、处理流程消息数据
create or replace procedure pro_data_new2 is
--注意:process_instance流程实例为空的滤除
cursor tmp_message is
select distinct t.message_id from cordys.tmp_message_id_201112 t
where not exists (select t1.message_id from cordys.message t1 where t1.message_id = t.message_id);
v_instance_count number(9);
v_num number(9); --提交计数
v_sqlcode number;
v_sqlmsg varchar2(2000);
v_instance_guid cordys.workflow_instance.guid%type;
v_process_instance cordys.workflow_instance.process_instance%type;
v_message_id cordys.message.message_id%type;
v_log_id number;
begin
v_instance_count := 0;
v_num := 0;
v_log_id := 0;
select count(*) into v_log_id from tmp_workflow_data_log;
if v_log_id > 0 then
select max(id) into v_log_id from tmp_workflow_data_log;
end if;
v_log_id := v_log_id + 1;
insert into tmp_workflow_data_log(id,state,logdate) values(v_log_id,'Start Test!',sysdate);
v_num := 0;
open tmp_message;
loop
fetch tmp_message
into v_message_id;
exit when tmp_message%notfound;
--迁移消息数据表
insert into cordys.message select * from cordys.bak2011_message where message_id = v_message_id;
--迁移消息跟踪数据表
insert into cordys.message_track
select * from cordys.bak2011_message_track where message_id = v_message_id;
--迁移消息通知数据表
insert into cordys.notification_search_data
select * from cordys.bak_notification_search_data where message_id = v_message_id;
if v_num > 1000 then
v_log_id := v_log_id + 1;
insert into tmp_workflow_data_log(id,state,logdate) values(v_log_id,'insert 1001 record message',sysdate);
commit;
v_num := 0;
else
v_num := v_num + 1;
end if;
end loop;
close tmp_message;
v_log_id := v_log_id + 1;
insert into tmp_workflow_data_log(id,state,logdate)
values(v_log_id,'insert message data complete',sysdate);
commit;
exception
when others then
v_sqlcode := sqlcode;
v_sqlmsg := substr(sqlerrm, 1, 2000);
dbms_output.put_line(v_sqlcode || '::' || v_sqlmsg);
end;
1、手工恢复触发器
触发器、Sequence是最容易忽略、出问题的地方,因为在表更名时,对应的代码可能发生改变,一定要注意核对。
AFTER_INSERT_MESSAGE_TRACK
AFTER_UPDATE_MESSAGE_TRACK
AFTER_UPDATE_PROCESS_INSTANCE
2、异常数据永远是存在的,只能人工分析进行处理,如果一个流程跑3个月了,是因为异常数据,估计这个流程实例也就是个作废的流程实例;
3、任务数据处理
把办结任务数据task_list(备份表)导入到办结任务数据表(task_list_finish)中时,如果遇到长时间未清理数据,或者说数据量较大时,最好也编写存储过程批量导入到办结表中。
本文处理方案和实施,是从2011年11月开始,中间经过2012年、2013年不断改进、优化,处理数据的方案和技术相对较成熟了,但整个系统的业务功能和数据增涨是常态,所以,处理历史数据的方案也要继续优化。
本文所描述的历史数据处理方案、思想可以推广到其他系统,而处理数据如此麻烦,也和最初设计有不合理的地方有关,设计人员可以从中汲取些经验教训,有遗漏、不完善的后续完善。
欢迎同行多交流。
参考:
ronghao100 ,浅谈工作流数据 , http://blog.csdn.net/ronghao100/article/details/5625120
Oracle中查询索引名称,批量修改索引名称语句 , http://blog.csdn.net/xiaoyw71/article/details/15338405
关于Cordys平台办结流程数据管理方案 , http://blog.csdn.net/xiaoyw71/article/details/15338391
基于Cordys平台上的待办任务优化改造方案 , http://blog.csdn.net/xiaoyw71/article/details/15338373