1背景
业务背景
某企业现有一应用系统运行该企业业务审批流程,审批流程有6个;
数据量
系统运行3年,拥有业务数据20万余条;
问题描述
查询审批流程时间大约15秒,此查询速度对用户来说无法接受,按行业标准查询速度应该在5秒内,5秒是用户接受响应速度的最大值。
运行环境
表一客户端运行环境
序号 |
项目 |
参数
扫描二维码关注公众号,回复:
290964 查看本文章
|
备注 |
1 |
cpu |
intel奔腾2.39G |
|
2 |
内存 |
2G |
|
4 |
操作系统 |
window xp |
|
5 |
浏览器 |
ie8 |
|
客户端计算机的配置比较低,一般都是4年前的计算机 |
表二应用系统运行环境
序号 |
项目 |
参数 |
备注 |
1 |
数据库 |
oracle 11g |
|
2 |
数据库运行环境 |
unix |
|
4 |
应用系统开发平台 |
java |
|
5 |
web服务器 |
tomcat6 |
最大内存配置2G |
6 |
web服务器运行环境 |
Linux |
|
7 |
系统并发数量 |
50人左右 |
|
应用系统运行的服务环境良好 |
2分析
问题定位
应用系统在设计之初选定了JQGrid组件用于展示数据,针对考虑到客户端性能问题,焦点首先集中在此组件上,觉得是此组件在渲染数据时让查询数据变慢,为了验证这一猜想,使用了HttpWatch工具进行分析,分析数据如下面表格:
表三 httpwatch监测数据报告
序号 |
分析内容 |
持续时间(秒) |
备注 |
1 |
加载js、css、图片等内容 |
0.7 |
|
2 |
从数据库中取数据 |
13.5 |
|
3 |
js渲染加载数据 |
1.1 |
|
合计 |
15.3 |
分析的数据让我们大跌眼镜,本来以为是JQGrid性能问题,结果是数据这个层级出了性能问题。定位到了问题,我们接下来对设计进行多个维度分析。
问题分析
-
业务
系统运行了6种类型的审批流程,在查询界面需要查看申请单号、申请时间、申请人、申请部门、当前状态、用途、申请类型信息,6种类型的申请单中有5种申请单含有以上所有需要查看的信息,还有1种申请单不具备用途这一信息。
-
程序设计
软件设计人员对此业务进行分析后进行了如下实体类设计:
-
1个基类:命名为Apply,此类含有申请人、申请部门、申请时间、当前状态、申请类型属性;
-
5个流程类:继承基类Apply,流程类含有业务属性;
-
1个流程类:继承基类Apply,流程类含有用途属性与业务属性
从面向对象的程序设计上来看此种设计不存在任何瑕疵。
-
数据库设计
根据实体类设计,创建了6张业务表来存储业务数据:1张基表,6张业务表,基表的数据字段如下表:
表四:基表[1]
序号 |
字段名称 |
说明 |
备注 |
1 |
id |
系统标识 |
主键 |
2 |
code |
申请单号 |
|
3 |
deptId |
申请部门id |
|
4 |
userId |
申请人id |
|
5 |
state |
状态 |
|
6 |
time |
申请时间 |
|
7 |
type |
申请单类型 |
表名:t_apply
表五 业务流程表[2-6](含有用途属性,5张表)
序号 |
字段名称 |
说明 |
备注 |
1 |
id |
系统标识 |
主键,值与基表中的id值一样 |
2 |
purpose |
用途 |
字符类型,最多100个字符 |
3 |
xxx |
业务属性1 |
|
n |
…… |
业务属性n |
表名:t_apply_1 至 t_apply_5(5张表)
表六 业务流程表[7](不含有用途属性)
序号 |
字段名称 |
说明 |
备注 |
1 |
id |
系统标识 |
主键,值与基表中的id值一样 |
2 |
xxx |
业务属性1 |
|
n |
…… |
业务属性n |
表名:t_apply_6
序号 |
数据表 |
表名称 |
数据量(条) |
备注 |
1 |
申请基表 |
t_apply |
280,000 |
|
2 |
申请业务表一 |
t_apply_1 |
160,000 |
含目的字段 |
3 |
申请业务表二 |
t_apply_2 |
10,000 |
含目的字段 |
4 |
申请业务表三 |
t_apply_3 |
20,000 |
含目的字段 |
5 |
申请业务表四 |
t_apply_4 |
15,000 |
含目的字段 |
6 |
申请业务表五 |
t_apply_5 |
30,000 |
含目的字段 |
7 |
申请业务表六 |
t_apply_6 |
45,000 |
不含目的字段 |
-
应用系统层
应用系统在查询申请单时,每页显示100条数据,开发人员使用以下设计方案查询申请数据:
1 查询申请主表t_apply;
2 根据主表的id值分别查询6张子表,从子表中获取purpose字段值,并在页面显示;
根据上面设计方案,查询100条申请数据最少需要执行1+100=101次查询,最多需要1+100*6=601次数据库查询,平均查询次数为(101+601)/2=351,分析得出查询数据缓慢的原因。
3解决方案
经过对问题的分析,我们知道了问题所在的原因,根据当前面向对象的设计观点及数据库三范式的设计原则,此设计是满足标准设计原则的。为何满足了业务需求与设计需求,但是还是会出现以上问题,这值得我们设计人员及开发人员思索这个问题,此问题集中的焦点在查询条件中含“目的”信息,为了实现这一查询目标,在现行的设计框架下必须牺牲系统性能,为此我们能否折中处理,下面提出3种解决方案(其实方案还有更多,办法总比问题多)。
3.1方案1
改进目标
-
工期最短;
-
不改变原有系统结构,系统调整最少;
-
缩短一半查询速度;
改进方案
-
创建目的视图,将t_apply_1至t_apply_5 union连接为一张视图:
CREATE OR REPLACE VIEW t_apply_purpose_viewAS
SELECTid,purpose FROM t_apply_1
UNION
SELECTid,purpose FROM t_apply_2
UNION
SELECTid,purpose FROM t_apply_3
UNION
SELECTid,purpose FROM t_apply_4
UNION
SELECTid,purpose FROM t_apply_5
-
创建带有“目的”的申请单视图
CREATE OR REPLACE VIEW t_apply_view AS
SELECT a.*,b.purpose FROM t_apply a
LEFT OUTER JOIN t_apply_purpose_view b ona.id = b.id
-
修改应用系统代码
将以前的SQL语句中查询的表替换为t_apply_view
改进耗时
半天时间
改进效果
序号 |
分析内容 |
持续时间(秒) |
备注 |
1 |
加载js、css、图片等内容 |
0.7 |
|
2 |
从数据库中取数据 |
6.2 |
|
3 |
js渲染加载数据 |
1.1 |
|
合计 |
8 |
3.2方案2
改进目标
-
工期适中;
-
系统调整适中;
-
查询速度缩短到5秒内;
改进方案
-
在申请基表t_apply中增加purpose字段;
-
每次在不同申请单中申请时自动将purpose值写入申请基表中,修改每个申请单申请结果;
-
查询时直接查询t_apply中数据,一条SQL语句执行即可;
改进耗时
5天时间
改进效果
序号 |
分析内容 |
持续时间(秒) |
备注 |
1 |
加载js、css、图片等内容 |
0.7 |
|
2 |
从数据库中取数据 |
2 |
|
3 |
js渲染加载数据 |
1.1 |
|
合计 |
3.8 |
在此方案中查询速度得到了大大提升,是一个近似完美的方案。如果客户给定的资金支持是一个定值,我们架构师是否会考虑时间成本问题,此方案是否是一个最优解?我们架构师能否再仔细的考虑一下,能否实现双方共赢的局面:客户资金耗费少,改造时间成本低?在此问题上,我们引入下面的方案3。
3.3方案3
改进目标
-
工期适中;
-
系统调整适中;
-
查询速度缩短到5秒内;
改进方案
-
在申请基表t_apply中增加purpose字段,并创建索引;
-
创建目的视图,将t_apply_1至t_apply_5 union连接为一张视图:
CREATE OR REPLACE VIEW t_apply_purpose_viewAS
SELECTid,purpose FROM t_apply_1
UNION
SELECTid,purpose FROM t_apply_2
UNION
SELECTid,purpose FROM t_apply_3
UNION
SELECTid,purpose FROM t_apply_4
UNION
SELECTid,purpose FROM t_apply_5
-
初次使用前利用t_apply_purpose_view视图更新t_apply中purpsose字段;
-
在每次查询的时候更新t_apply表中的purpose字段为空的记录,
UPDATE t_apply a SET
purpose = (SELECTpurpose FROM t_apply_purpose_view WHERE a.id=b.id)
WHERE a. purpose IS NULL
-
直接查询t_apply数据,2条SQL执行即可;
改进耗时
1天时间
改进效果
序号 |
分析内容 |
持续时间(秒) |
备注 |
1 |
加载js、css、图片等内容 |
0.7 |
|
2 |
从数据库中取数据 |
2.8 |
|
3 |
js渲染加载数据 |
1.1 |
|
合计 |
4.6 |
在此方案中查询速度得到了大大提升,是一个近似完美的方案。如果客户给定的资金支持是一个定值,我们架构师是否会考虑时间成本问题,此方案是否是一个最优解?我们架构师能否再仔细的考虑一下,能否实现双方共赢的局面:客户资金耗费少,改造时间成本低?在此问题上,我们引入下面的方案3。
3.4 方案效果对比
序号 |
客户需求 |
方案名称 |
工时(天) |
改进后查询速度(秒) |
提高百分比 |
改进前查询速度(秒) |
1 |
工期短;查询速度缩短到原来的一半 |
方案1 |
0.5 |
8 |
47.7% |
15.3 |
2 |
工期适中;查询速度降低到5秒内 |
方案2 |
1 |
3.8 |
75.1% |
|
3 |
方案3 |
5 |
4.6 |
69.9% |
4问题分析
我们追根索源这一问题,对当时的情景进行还原再现,想对应的人员请大家看看如果我们是场景中的人,我们是否也是这样来做的,请做个对照。
被访谈人员(甲方):
我们现在有一批申请业务希望电子化,该审批业务涉及到6种不同的申请。
访谈人员(乙方):
您有6种不同类型的申请,请您提供一下6种纸质版本的申请表格,最好已经有了数据的纸质表格,数据可以供我们参考一下,另外给我们提供一份电子表格。请问你们会根据什么样的条件去查询这些申请表?
被访谈人员(甲方):
我们想根据申请时间、部门、人员工号、人员姓名、申请单类型来查询,这些条件基本够了,我们需要在查询列表上显示在查询界面需要查看申请单号、申请时间、申请人、申请部门、当前状态、用途、申请类型信息。
甲方找来已经存档的纸质申请单交给乙方,各种申请单提供了10多份。
访谈人员(乙方):
您能详细描述一下各种申请业务流程是如何审批的吗?
被访谈人员(甲方):
我给你把每种业务解释一下,便于你们能够理解。
甲方将审批流程业务详细的讲解给乙方,直到乙方完全理解各个流程的审批过程,双方的焦点集中在审批流程上。乙方将资料带回单位对业务进行梳理分析,并画出泳道图给客户确认,客户拿到泳道图,对着单位的相关制度进行逐一确认。
乙方根据甲方所提的需求进行设计、开发、测试、上线,在系统运行的第一年里没有什么问题,在第二年里速度会有些慢但是不太明显,到了第三年限系统运行速度缓慢问题就凸显出来,于是便有了上面一幕。我们回过头来看看,从专业的角度看看我们调研的时候所问的问题是否有所欠缺:
-
我们是否对系统数据量有个预估;
-
我们在设计的时候是否真的吃透了用户的需求;
-
我们是否了解客户潜在的需求;
访谈人员在访谈前制定调研提纲,提纲如下:
-
组织架构是什么样的?
-
系统使用人员的人数?
-
每天业务数据有多少?
-
所需系统响应时间为几秒?
-
客户使用计算机是什么样配置?
-
服务器配置是什么样配置?
-
。。。。。。。。。。。。。。。。。。。。。。。。。。
(后面我们将会制作一篇如何做好调研的文章)
根据提纲询问得到的答案,制定架构设计原则:
-
客户端硬件配置决定了系统开发使用的前端框架;
-
系统使用人数*每天业务量*12月=一年业务数据流
设计人员在获取客户需求、系统反应时间、业务数据量的情况下进行设计,回到审批流程查询这一特定问题上,在申请主表上加入“目的”冗余字段,在每个申请提交的时候将“目的”信息写入申请主表中。此种方案正是方案2的设计思路。
5总结
一个小小的设计导致了系统运行缓慢,设计人员要从中吸取一定的经验教训,自己挖下的坑,一定是需要自己或队友去填坑的(不怕复杂的系统,只怕猪一样的队友),出来混迟早是要还的。
造成这一问题原因是在需求及设计阶段未全面分析导致的,在应用级别的系统中低层数据库结构设计是非常重要的,数据库结构设计决定了上层应用开发的复杂度及系统运行的效率,在后面的文章中,我们将会介绍几种典型业务对应的数据库设计知识,敬请期待。
6后续
如果您喜欢我们的文章或者有什么好的意见,请订阅我们的微信公众号并给我们留言,我们是一个专注业务及业务技术实现的团队。
微信公众号:构建设计编程