1背景
业务背景
某企业现有一应用系统运行该企业业务审批流程,审批流程有6个;
数据量
系统运行3年,拥有业务数据20万余条;
问题描述
查询审批流程时间大约15秒,此查询速度对用户来说无法接受,按行业标准查询速度应该在5秒内,5秒是用户接受响应速度的最大值。
运行环境
表一客户端运行环境
序号 |
项目 |
参数 |
备注 |
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后续
如果您喜欢我们的文章或者有什么好的意见,请订阅我们的微信公众号并给我们留言,我们是一个专注业务及业务技术实现的团队。
微信公众号:构建设计编程
相关推荐
1. **梦,已经展开**:这部分可能讲述的是作者对学习缓冲区溢出的热忱和期待,激发读者的学习兴趣。 2. **啤酒和杯子——缓冲区溢出原理**:通过比喻解释了缓冲区溢出的基本概念,即当程序处理数据时,超过了预分配...
1.5 窥豹一斑――本地缓冲区溢出简单利用 21 1.6 小结——摘自小强的日记 28 1.7 首次实战――FoxMail溢出漏洞编写 29 1.8 牛刀小试――Printer溢出漏洞编写 41 1.9 JMP /CALL EBX——另一种溢出利用方式 42 1.10 拾...
1.3 神秘的Windows系统 10 1.4 ShellCode编写简介 17 1.5 窥豹一斑――本地缓冲区溢出简单利用 21 1.6 小结——摘自小强的日记 28 1.7 首次实战――FoxMail溢出漏洞编写 29 1.8 牛刀小试――Printer溢出漏洞编写 41 ...
1.3 神秘的Windows系统 10 1.4 ShellCode编写简介 17 1.5 窥豹一斑――本地缓冲区溢出简单利用 21 1.6 小结——摘自小强的日记 28 1.7 首次实战――FoxMail溢出漏洞编写 29 1.8 牛刀小试――Printer溢出漏洞编写 41 ...
1.5 窥豹一斑――本地缓冲区溢出简单利用 21 1.6 小结——摘自小强的日记 28 1.7 首次实战――FoxMail溢出漏洞编写 29 1.8 牛刀小试――Printer溢出漏洞编写 41 1.9 JMP /CALL EBX——另一种溢出利用方式 42 1.10 拾...