*多成员表
银行核心系统通常生命周期在10年左右,现在随着银行系统的数量和复杂度增加,系统的生命期也有逐渐变长的趋势。通常核心系统上线时,很多数据表的记录数并不太多,而当系统运行年限越来越长,巨型数据表就越来越多。另外,随着城商、农信的省级集中,用以前针对城市级商业银行的系统,来应对省级的数据量,原先规模合理的表,也会变成巨型表。在实际项目中,已经遇到记录数上亿,和十亿级别的表了,不光严重影响系统性能,在7*24小时不间断运行条件下,数据清理都是个难题。以往的系统,在这方面考虑是比较少的,大概是因为系统设计实现者通常只管上线,不会长期维护,而且原先系统面向银行的规模较小。因此,要么没考虑大数据量表的拆分,要么常处理方法也只是简单区分下当前表和历史表,在日终时做个当前倒历史的批量(历史表巨大的话,倒历史时间也会变得很长)。
分析银行的数据表,容易产生很多记录数的案例,就会发现通常有两种状况比较常见。一种是明细流水性质的表,产生一笔就会新增一条记录,日积月累就会变得很大。这里面大多数都可以用日期时间为顺序和查询依据的,例如账户明细表,传票表等。这类表很明显可以按日期进行划分。超过一定的期限以后,历史的数据就可以清理了,通常历史查询功能由ODS之类的数据仓库系统提供。经过清理后,数据量可以保持稳定。另一种是账户主表,凭证主表,客户信息表等,随着系统多年使用逐渐增长,而且可清理的不多,数据量随业务发展总体呈增长趋势。
这里我们看看在Firebird系统中巨型表的解决办法。从根本上说,应当将大表分小,并且要适合使用的情景。在开放平台数据库,有分区表的概念,而在400平台,对应的就是*FILE的多MEMBER。对于可按日期划分的表,我们可以带日期为MEMBER名,每日一个MEMBER,预先创建好,无需倒历史切换,解决了7*24小时和数据清理的问题。对于主表,可按主键号码或某些字段hash计算分MEMBER。例如,客户表按客户号的hash分区,凭证表按凭证号的hash分区。但账户表要按账户所属机构号分区,这是为了优化结息批处理性能,利于每个按机构并发的结息程序只访问一个MEMBER。
400系统对多MEMBER的限制有哪些呢?首先,同一个*FILE支持的MEMBER数量,最大为32767个。按每日分区计算,大约可以支持90年。按hash计算,10亿级别的表hash到100个MEMBER,每个MEMBER仅1000万记录数量。其次,单个MEMBER最多记录数为2^32,大约42亿。但实际上记录数过多,访问操作性能会指数级下降。单个MEMBER内记录数在1000万级别比较合适。最后,对于LF文件的单个MEMBER,能关联PF的MEMBER数量是有限制的,大概是32个,但实际使用中LF的MEMBER命名与PF相同,完全的一对一建立。如果要搜索多个MEMBER,在应用程序中循环读取实现。
有关多MEMBER的命令有以下这些。
CRTPF或CHGPF时指定MAXMBRS为*NOMAX。
CRTLF或CHGLF时指定MAXMBRS为*NOMAX。
ADDPFM 在PF中新增一个MEMBER。
ADDLFM 在LF中新增一个MEMBER。
RMVM 删除一个指定的MEMBER。
CLRPFM 清空一个MEMBER的所有记录。
下面的CL程序是给PF和LF增加连续日期的MEMBER例子。MEMBER名类似M20150821。
-
PGM PARM(&FLNM &STNMC &EDNMC)
-
INCLUDE SRCMBR(CLHD) SRCFILE(DSCPPGM)
-
DCL VAR(&FLNM) TYPE(*CHAR) LEN(10)
-
DCL VAR(&STNMC) TYPE(*CHAR) LEN(8)
-
DCL VAR(&EDNMC) TYPE(*CHAR) LEN(8)
-
DCL VAR(&STNM) TYPE(*CHAR) LEN(10)
-
DCL VAR(&EDNM) TYPE(*CHAR) LEN(10)
-
DCL VAR(&MBNM) TYPE(*CHAR) LEN(10)
-
DCL VAR(&DAYS) TYPE(*DEC) LEN(5 0)
-
DCL VAR(&I) TYPE(*DEC) LEN(5 0)
-
DCL VAR(&NM) TYPE(*CHAR) LEN(10)
-
DCL VAR(&FLTP) TYPE(*CHAR) LEN(10)
-
DCL VAR(&FLLB) TYPE(*CHAR) LEN(10)
-
DCL VAR(&PFNM) TYPE(*CHAR) LEN(10)
-
RTVOBJD OBJ(&FLNM) OBJTYPE(*FILE) OBJATR(&FLTP) RTNLIB(&FLLB)
-
CHGVAR VAR(&STNM) VALUE(&STNMC)
-
CHGVAR VAR(&EDNM) VALUE(&EDNMC)
-
CALL PGM(DATECALC) PARM(&STNM &EDNM &DAYS)
-
IF COND(&DAYS *LT 0) THEN(GOTO CMDLBL(ENDPGM))
-
CHGVAR VAR(&I) VALUE(0)
-
ADD: IF COND(&I *LE &DAYS) THEN(DO)
-
CHGVAR VAR(&NM) VALUE(' ')
-
CALL PGM(DATECALC) PARM(&STNM &NM &I)
-
CHGVAR VAR(&MBNM) VALUE('M' *TCAT &NM)
-
IF COND(&FLTP *EQ 'PF') THEN(DO)
-
ADDPFM FILE(&FLNM) MBR(&MBNM)
-
ENDDO
-
IF COND(&FLTP *EQ 'LF') THEN(DO)
-
CHGVAR VAR(&PFNM) VALUE(%SST(&FLNM 1 7))
-
ADDLFM FILE(&FLNM) MBR(&MBNM) DTAMBRS((&FLLB/&PFNM +
-
(&MBNM)))
-
ENDDO
-
CHGVAR VAR(&I) VALUE(&I + 1)
-
GOTO CMDLBL(ADD)
-
ENDDO
-
ENDPGM: ENDPGM
下面的CL程序是给PF和LF增加连续hash的MEMBER例子。MEMBER名类似M01到M99。另外还有3位数M001到M999的程序就不贴了。
-
PGM PARM(&FLNM &STNMC &EDNMC)
-
INCLUDE SRCMBR(CLHD) SRCFILE(DSCPPGM)
-
DCL VAR(&FLNM) TYPE(*CHAR) LEN(10)
-
DCL VAR(&STNMC) TYPE(*CHAR) LEN(2)
-
DCL VAR(&EDNMC) TYPE(*CHAR) LEN(2)
-
DCL VAR(&STNM) TYPE(*INT)
-
DCL VAR(&EDNM) TYPE(*INT)
-
DCL VAR(&MBNM) TYPE(*CHAR) LEN(10)
-
DCL VAR(&NM) TYPE(*CHAR) LEN(2)
-
DCL VAR(&FLTP) TYPE(*CHAR) LEN(10)
-
DCL VAR(&FLLB) TYPE(*CHAR) LEN(10)
-
DCL VAR(&PFNM) TYPE(*CHAR) LEN(10)
-
RTVOBJD OBJ(&FLNM) OBJTYPE(*FILE) OBJATR(&FLTP) RTNLIB(&FLLB)
-
CHGVAR VAR(&STNM) VALUE(&STNMC)
-
CHGVAR VAR(&EDNM) VALUE(&EDNMC)
-
ADD: IF COND(&STNM *LE &EDNM) THEN(DO)
-
CHGVAR VAR(&NM) VALUE(&STNM)
-
CHGVAR VAR(&MBNM) VALUE('M' *TCAT &NM)
-
IF COND(&FLTP *EQ 'PF') THEN(DO)
-
ADDPFM FILE(&FLNM) MBR(&MBNM)
-
ENDDO
-
IF COND(&FLTP *EQ 'LF') THEN(DO)
-
CHGVAR VAR(&PFNM) VALUE(%SST(&FLNM 1 7))
-
ADDLFM FILE(&FLNM) MBR(&MBNM) DTAMBRS((&FLLB/&PFNM +
-
(&MBNM)))
-
ENDDO
-
CHGVAR VAR(&STNM) VALUE(&STNM + 1)
-
GOTO CMDLBL(ADD)
-
ENDDO
-
ENDPGM
建立起多MEMBER文件以后,在应用程序中使用就有注意的地方了。
首要原则,对上层组件尽量透明,把多MEMBER的操作尽可能封装在数据表访问组件内部,对外就像操作的是一个文件一样。这涉及到核心系统整体规划上的“交易--模块公有组件--模块私有组件--数据访问封装”体系,就是另外的话题了。
那么,在RPG程序中,多MEMBER文件的代码怎么写呢?主要区别在F表定义上。F表定义文件的时候,选项部分需要多写两个,EXTMBR(V_MBRNAME) USROPN。EXTMBR用于根据变量V_MBRNAME的值指示要操作的MEMBER名,USROPN则是在程序执行到OPEN语句时再打开文件。通过程序中先给V_MBRNAME赋值,再执行OPEN语句,就可以操作指定的MEMBER啦。值得注意的是,前面介绍过激活组会保持文件的打开状态以供下次调用使用,多MEMBER时,不能简单通过%OPEN()内置函数来判断文件是否已打开,因为%OPEN()函数对不同MEMBER不作区分,比如已打开M003,这次虽然V_MBRNAME赋值了M998,但%OPEN()判断时仍认为文件已打开,必须要通过打开的MEMBER名是否相同来额外区分。
RPG对多MEMBER表的使用例子节选如下。
-
**********************************************************************
-
FAPLBTTR O E DISK INFSR(#FLEX) INFDS(S_PLBTTR)
-
F EXTMBR(V_MBRNAME)
-
F QUALIFIED USROPN COMMIT
-
**********************************************************************
-
D*引入数据字典枚举常量
-
D/COPY DSCPPGM,ENUM
-
D*引入程序公共结构
-
D/COPY DSCPPGM,PGDS
-
D*文件结构定义
-
DS_PLBTTR DS LIKEDS(FILESDS)
-
DR_PLBTTR DS LIKEREC(APLBTTR.RAPLBTTR:*OUTPUT)
-
*************************
-
D*常量定义
-
DC_PREMBR C CONST('M')
-
*************************
-
**日期结构
-
DD_DATE DS QUALIFIED
-
D YEA 4A
-
D FLG1 1A
-
D MON 2A
-
D FLG2 1A
-
D DAY 2A
-
*************************
-
D*临时变量结构
-
DD_VARS DS
-
D V_MBRNAME LIKE(DICT.@@FLNM)
-
**********************************************************************
-
** @MAIN程序的主流程
-
**********************************************************************
-
C @MAIN BEGSR
-
C EVAL FILEDSP = %ADDR(S_PLBTTR)
-
C EVAL D_DATE = %CHAR(BTWTF1.BTWKDT)
-
C EVAL V_MBRNAME = C_PREMBR + D_DATE.YEA +
-
C D_DATE.MON + D_DATE.DAY
-
C*判断已打开的成员不一致则关闭重新打开
-
C IF FL_MEMBER <> V_MBRNAME
-
C CLOSE APLBTTR
-
C ENDIF
-
C*打开指定成员
-
C IF NOT %OPEN(APLBTTR)
-
C OPEN APLBTTR
-
C ENDIF
-
C CLEAR R_PLBTTR
-
C EVAL-CORR R_PLBTTR = BTWTF1
-
C EVAL R_PLBTTR.STTMSP = %TIMESTAMP()
-
C EVAL R_PLBTTR.RPSHNU = 0
-
C EVAL R_PLBTTR.BKEXST = EXST_NOTEX
-
/FREE
-
WRITE APLBTTR.RAPLBTTR R_PLBTTR;
-
/END-FREE
-
C ENDSR
-
**********************************************************************
阅读(3516) | 评论(0) | 转发(1) |