Richie

Sometimes at night when I look up at the stars, and see the whole sky just laid out there, don't you think I ain't remembering it all. I still got dreams like anybody else, and ever so often, I am thinking about how things might of been. And then, all of a sudden, I'm forty, fifty, sixty years old, you know?

系统分析设计 一个JOIN问题解决方案的感想 重视业务分析设计

    Richie
    2006-12-31
    RicCC:http://www.cnblogs.com/RicCC/archive/2006/12/31/608925.html

    这是某个系统的一个做法,觉得对架构师在系统分析设计思想上有所启发,所以写出来跟大家分享。
    一个大系统,业务和系统都非常复杂,系统很灵活,但是后台提交到数据库的SQL语句,基本都是最简单的SELECT、DELETE、UPDATE、INSERT,你看不到复杂语句的出现,包括JOIN。而对于我们自己开发的系统,哪怕再简单不过,不使用JOIN似乎不可能。

    用一个简单的例子来说明。有一个销售订单对象,它会关联到客户对象。我们假设客户对象以一个客户编码作为主键,值是唯一的,然后会有客户名称、地区/城市、地址等资料。销售订单表有一个客户编码字段,跟客户对象关联。用户需求可能是通过客户名称、地区/城市等相关信息查询销售订单。

    这种情况下,有一种设计方案,是在SalesOrder表中建立一个CustomerName字段,销售订单创建的时候就把客户名称的值带过去,在查询上只允许按照客户名称对SalesOrder表进行查询,避免使用SalesOrder表与Customer关联。这种方案的优缺点,大家会有各自的看法。首先,如果需要添加按照客户地区/城市来查询SalesOrder,得在SalesOrder添加客户地区/城市的字段,如果还有其它的对象跟SalesOrder关联,可能也得添加字段用于查询;其次某些情况下,这种用法能够跟业务需求比较好的吻合起来,但大部分情况下,如果客户名称有发生变化,怎么办?维持SalesOrder表中原有的客户名称值不变,还是对所有添加了客户名称的表进行同步更新?如果不是因为业务需求确实需要这样做,这种做法是不大合理的。
    接下来看一些比较常规的做法。直接写SQL,使用SalesOrder Inner Join Customer进行查询,或者使用HQL,或者在ORM上配置对象关联关系等方式实现。
    首先,如果在ORM上配置关联关系实现,一旦查询复杂一些,并且系统这种查询很多,会使系统实现或配置很复杂。所以对于这种情况,ORM都会尽量避免,要么直接使用SQL,或者象Hibenate使用HQL实现。
    直接使用SQL时,一方面造成业务模型对逻辑封装的泄漏,另一方面是对数据持久化媒体的直接暴露。使用HQL能够做到一些弥补。
    最后我们从性能和效率方面分析一下。
    不管是ORM配置对象关联关系来实现,还是使用HQL,最终框架都是生成Join语句进行数据库查询。SalesOrder和Customer只是一个最简单的示例,实际中大家遇到的关联需求可能极为复杂。因为业务的复杂性,对关联表中索引的需求也是多样的,难以保证每个查询以最高的效率使用索引。比较极端的情况,也很可能常遇到,象上面的例子,可能需要先对SalesOrder和Customer两个表做全表扫描,然后对这两个表所有数据进行Join操作。假若SalesOrder和Customer都是几十万、几百万的数据量(当然这种数据量下肯定不会让这种事情发生,只是举例),这个查询性能会是什么样子?SQL Server也好Oracle也好,都会非常差的。可能不少的系统都会面临一些复杂查询的性能问题,解决起来确实比较棘手。
    在数据量没有这么多,能够在一定程度利用索引的时候,情况会好一些,但这也只是限于一定的情况下。各方面的原因,都可能造成我们难以确保所有复杂查询都充分利用索引,随着业务数据的变化,索引的使用效率也会发生变化。当单个的查询对数据库服务器造成比较大的消耗时,并发数越大,系统的性能下降越厉害,并且带来一系列互相关联又互相影响的问题,阻塞严重,死锁概率变大。对稍复杂的系统,出现这种情况时,在系统层面做优化有可能是件无法实施的事情,因此不少的系统会选择将过期的数据导出。
    另外效率方面是指,如果这个查询结果比较多,绝大部分情况下对于用户而言这是一个无效查询,因为用户不大可能在大量数据里面再去检索他想要的数据。面对这种情况,用户极有可能会凭记忆,查文档查邮件等其它方式找到一些更精确的条件,缩小查询结果的范围。有的人会提出,结果集是按特定字段排序的,用户拉动滚动条、翻页等可能会比较方便的找到他想要的数据。对于CS下面的产品,这的确是有可能,在BS上,我们不可能一个页面显示太多数据,翻页去找用户一般是不大愿意的,并且,对于那些被用户忽略的数据,我们还是白白耗费了服务器资源,是否有更好的方式避免这种情况呢?另外有的人可能会提出,添加一个类似SQL Server里面的Top语句,或者使用Oracle的Rowid限制结果集数量。其实这种做法在上面示例的情况下,除了减少网络传输方面比较明显,在数据库查询方面并不能带来多少效率的提高,因为数据库仍然得完成Join等操作之后,才能知道Top或者是Rowid所指定的范围,你从执行计划里面都可以发现这一点。这种低效的查询,耗费服务器资源做无用功,当你看着服务器内存、CPU、I/O消耗很高,而执行的查询有效性却比较低时,作为架构师、设计师的你,会有什么感想?

    其实,过于复杂SQL的出现,复杂SQL所带来的系统性能问题,本身就直接意味着系统架构、分析设计、开发方面的问题。
    象上面提到的那个系统,不使用一个JOIN,大家可能会觉得它走了一个极端。它是通过在系统分析、设计上下足功夫来实现的,在下面了解它的做法之后,我们能够知道,它的做法增加了一些与数据库的交互,在用户多的情况下并发情况可能会稍严重一些,但跟上面的分析相比较,这绝对是不足以提的。并且这种情况下遇到的数据库性能问题,我们可以认为是一种正常的良性的状态,是一种必然,通过提高服务器配置,调整数据库服务器架构等,能够比较容易和明显的改善,而不用调整系统。

    一般的做法下,使用JOIN方式,界面差不多是这个样子,这也是很直接很自然的一种方法:

    为了避免使用JOIN,界面就只允许按照客户编码的字段进行查询了,界面的样子如下:

    当用户需要根据其他客户资料查询销售订单时,将这个操作拆分成两个步骤。第一步根据客户的其他资料先准确查询到这个客户,这个步骤通过Customer NO.查询条件输入框后面的图标按钮,弹出一个详细的客户资料查询界面。当准确查询到某个客户资料并确定之后,弹出的客户查询界面将客户编码返回,填入到Customer NO.查询条件输入框中。第二步就是根据返回的客户编码查询销售订单。
    当然你可能会想说,这种做法用户使用起来不方便,因为只有一个客户编码的输入框。作为系统分析设计人员,需要能敏锐地觉察到这种问题,一个订单查询的用例,经过这样分拆之后,可能的确给用户的操作带来局限性,所以需要进一步思考完善。看一下下面的界面:

    首先,Customer NO.输入框有多组,用户可以输入多个值进行查询;在第三行Customer NO.输入框位置有一个向下的图标按钮,点击之后可以动态添加一组Customer NO.输入框。其次一般的系统,象这种客户编码、销售订单编号等,都是有规则的,相同的前缀可能意味着属于同一个类别,因此每一组Customer NO.条件都使用From、To的查询方式,就是说那些 From值 =< Customer NO. =< To值。另外,假如你有一个Excel文件,里面有10个客户编码的列表,现在你需要查询这10个客户某个月份的销售订单,一个一个输入可能还是比较麻烦,这种情况下可以提供一个上传查询条件列表文件的方式,避免用户频繁输入。
    说明一下,参考的系统是CS结构,在BS上实现可能有些繁琐复杂,我们不去探究这些方面,而是把它当作一种分析设计方法参考。
 
    从效率和性能方面分析一下。将原本需要JOIN完成的查询拆分成了两个步骤进行,每一步都是进行一个单表查询。在单表查询的基础上做性能方面的考虑,这个问题就变得非常单纯,容易很多,例如索引的建立和使用,使用Top、Rowid等方式提高数据库的使用效率和查询性能(默认返回500、200条记录,用户可以输入具体的数字到底返回多少条)等。如果你对数据库的理解比较透彻,用前面提到的SalesOrder和Customer都是几十万、几百万数据量的情况做一下对比,如果再加上Top、Rowid等方式的使用,用户完成同样的一个任务,数据库的开销可能是1个甚至几个数量级的改善。
    在系统开发方面,简洁、清晰。一个客户查询对话框,可以在所有类似使用客户资料进行查询的地方使用。数据列表的显示只是基于单个对象,有利于做成通用的控件、组件方式,比较自然的嵌入在整体技术架构中。如果查询条件部分也能够控件化,你的系统界面开发会是一个全新的面貌。
    在架构设计方面的优点,大家可以想到的可能更多。原本一个用例需要结合两个对象一起考虑,现在每一步骤只是针对一个对象操作,这样有利于业务对象逻辑的完整封装。对复杂关联关系的考虑,造成技术框架方方面面的复杂性,随着这个拆解消失。例如ORM,如果不需要关联关系,不需要HQL,根本不需要考虑开发者会自己定义一个查询,返回一个比较随意的DataSet而不是Domain Model Object,你对ORM的开发、选择还会有什么考虑?
    复杂的系统逻辑,并不会因为上面这样一个简简单单的方案就能解决所有的JOIN问题,不同的场景下你需要进行不同的分析,例如有的情况下适当的添加冗余表、冗余字段等。

    啰嗦的写了一大段,并不是想告诉大家这里有这样一个解决JOIN的方法,大家以后不要用JOIN了。而只是想结合在这样一个示例中说明一下业务分析、设计的重要性。很多时候我们太过依赖于技术而忽视业务分析设计,而去追求一组互相矛盾的目标:开发维护高效方便、系统灵活扩展性伸缩性良好等等,因此我们开始进入一个漫漫的旅程,对技术的要求越来越高,对框架的要求越来越多也越复杂,而你却始终都发现还是难以接近目标。如果你在业务分析设计上多花一些精力,可能取得的效果会完全不一样。
    最后祝大家新年快乐!

posted on 2006-12-31 14:27 riccc 阅读(2996) 评论(30)  编辑 收藏 网摘 所属分类: Architecture & Design

Feedback

#1楼 2006-12-31 14:57 charleschen      

很好的文章,
我读了几遍。写点我的感想。

但是正如你所说‘这种做法用户使用起来不方便,因为只有一个客户编码的输入框。’
对于用户来说,是不会关心构架的,他们只关心易用性。
我想除非是专业的操作手,不然使用编码化的CustomerNO:来对应CustomerName是很难让人接受的,除非确认可以完全避免‘需求变更’
否则这将是一个风险,将系统承压风险由'客户不会提出新需求'来简化。

但是一旦风险规避失败,那么将引发‘修改代码’等一堆风险。
  回复  引用  查看    

#2楼 2006-12-31 14:58 凌风[匿名][未注册用户]

有些道理   回复  引用    

#3楼 2006-12-31 15:02 生米煮成稀饭      

不错,通过多次查询来解决Join性能问题!   回复  引用  查看    

#4楼[楼主] 2006-12-31 15:18 RicCC      

@charleschen
是的,一个公司编码规范的程度会影响到用户在使用性上的效果
这个是说的系统分析、设计阶段,这个阶段,产品化的开发需要充分的调研,项目型开发需要跟最终用户反复的确认,对于项目型的规避风险还是比较容易,因为仅仅是在分析设计阶段,而作为产品型,就需要综合非常多的因素来做这个决定了

不过我的本意,是觉得这样一种分析思路很不错,很普通的问题,很容易从大家眼皮子底下溜走而丝毫不会注意,业务分析设计能够做到这种程度是一种境界。
就是说能够提出这样的方案就是一种设计细致、成熟的体现,至于最终会不会选择这样的方案是不重要的。
  回复  引用  查看    

#5楼 2006-12-31 16:30 天外飞仙[匿名][未注册用户]

写的挺好,开阔了思路。   回复  引用    

#6楼 2006-12-31 17:14 shewo[未注册用户]

有所启发,不过兄弟大可不必写得这么多。   回复  引用    

#7楼 2006-12-31 17:52 kklc[未注册用户]

很多时候公司和客户考虑更多的是开发周期,而不是系统性能。   回复  引用    

#8楼[楼主] 2006-12-31 19:08 RicCC      

@凌风
@生米煮成稀饭
@天外飞仙
谢谢,可以多交流下大家的看法
  回复  引用  查看    

#9楼[楼主] 2006-12-31 19:10 RicCC      

@shewo
不好意思,简简单单的一个做法写的看起来比较累。
  回复  引用  查看    

#10楼[楼主] 2006-12-31 19:19 RicCC      

@kklc
谁说的来着,组织结构决定产品架构,是很有道理的。很多时候项目过程中一切的选择都得依据实际的状况进行妥协。
  回复  引用  查看    

#11楼 2007-01-01 18:58 臭石头      

强!
非常强!!
我只会整天苦恼于如何从技术上优化系统,而从未想过从业务上优化。

谢谢~^_^
  回复  引用  查看    

#12楼 2007-01-01 23:45 极地银狐.NET      

所有编码最后的产品都是要拿来用的,客户就是用它们来提高效率,从业务优化,也可以说是体贴客户的一种做法。
不错,呵。
  回复  引用  查看    

#13楼 2007-01-02 09:07 zdq2601[未注册用户]

你可以使用,单表的二级查询。先对一个表查询,并建立查询的组件,然后根据返回值执行第二次查询   回复  引用    

#14楼 2007-01-02 14:16 flyhawk1010[未注册用户]

不错,至少是一个很不错的解决问题的思路!   回复  引用    

#15楼 2007-01-03 11:50 join[匿名][未注册用户]

受教了....系统只针对一张表操作,而用户可以选择对只一张表操作.确实是一个很好的idea   回复  引用    

#16楼 2007-01-04 10:55 jhyc[未注册用户]

很不错的文章,从业务上进行优化是个思路,不过这一方面需要分析员更多的能力,还需要有效的客户进行沟通,毕竟客户更多的是关心怎么用起来更方便。   回复  引用    

#17楼 2007-01-04 11:00 jhyc[未注册用户]

很不错的文章,从业务上进行优化是个思路,不过这一方面需要分析员更多的能力,还需要有效的客户进行沟通,毕竟客户更多的是关心怎么用起来更方便。   回复  引用    

#18楼[楼主] 2007-01-04 12:31 RicCC      

@jhyc
@All
业务分析设计,涉及到很多方面,作为优秀的架构师设计师,可能技术方面的技能要求充其量占1/3还可能更少,也没有比较详细的标准、方法论之类可以参考,靠的是经验。
文章中的例子侧重于技术实现层面的分析,对于热爱技术的人,这样的分析可能更有吸引力,更能引起在业务方面的重视和思考。

也希望能能够多多交流业务分析方面的经验
  回复  引用  查看    

#19楼[楼主] 2007-01-04 12:46 RicCC      

@极地银狐.NET
绝大部分时候是考虑让客户的业务怎样更合理,操作也方便。有的情况下权衡利弊,鱼和熊掌不可兼得时,也得带些强制性要求了。
不少的IT经理、项目领导通常都能够理解这种强制性要求后面的合理性,或者担心不这样做而引入的其他风险,从而支持这些作法。
  回复  引用  查看    

#20楼 2007-01-04 16:45 _someone[未注册用户]

这种方案用了好多年了,在表中适当加入一些冗余字段对性能还是很有帮助的.   回复  引用    

#21楼 2007-01-04 17:03 nanbo[未注册用户]

方法不错,但是客户为此将多加了几步操作,会不会有不妥?

因为,复杂的查寻不仅仅只为简化操作步骤,更为简化与用户的交互次数,让他们认为简单的同时高效!

我所惧怕的是客户一步做到的事情因为各位所说的“业务优化”反而将操作数提高,我只看了一遍所以认为这是版主的意思,请指教!?
  回复  引用    

#22楼 2007-01-04 17:42 nanbo[未注册用户]

深感前面发言不妥,特追加如下:
还是版主有思想!
  回复  引用    

#23楼[楼主] 2007-01-05 20:10 RicCC      

@nanbo
不必客气。
例子中一个关键问题是客户对编码的适应性(可以输入编码进行查询,如果不记得编码,需要几个额外的操作获得这个编码)。
我从事的是ERP相关行业,这个行业里面基本上对所有的业务对象,编码是一个很重要的概念,如果客户对编码不适应,必须强制执行,其实这对客户在管理上方方面面都有好处。
对于其它一些领域,这种方式不一定会适用。就像Martin Fowler在企业应用架构模式里面不断强调的,到底应该使用Transaction Script、Table Module还是Domain Object方式,得根据具体情况决定。
  回复  引用  查看    

#24楼[楼主] 2007-01-05 20:16 RicCC      

@nanbo
其实对于例子中提到的系统,一线操作用户基本都觉得它用起来非常方便。当然它不仅仅是在这样一个操作上,而是对于任何复杂业务,都有从使用这些功能的用户角色的切身体会上去考虑系统。
任何一个系统,如果设计者能够完全进入最终用户的立场、操作情景中去考虑,系统的操作性用户都是能够接受的。
  回复  引用  查看    

#25楼 2007-06-26 22:22 凌风      

我今天是第二次读你这片文章了。
你的分析的确有你的道理!但是在目前软件行业中,非常重视软件的可操作性/用户体验,想通过简化关联来达到优化的目的,我个人认为在这种情况下是不可取的。但是,我一直在思考(但还是没有想出来的),有没有别的办法来规避这种情况呢!话题又说回原地了---系统分析!
有空再分析分析!
  回复  引用  查看    

#26楼[楼主] 2007-06-26 23:14 RicCC      

是啊,系统业务方面的分析、架构没有标准,没有具体的指南,UML、RUP只是工具流程,MDA、Domain Driven只是粗略的思想,说大的方面大家都有概念和想法,具体实施起来都不那么容易。系统分析思想靠的就是经验和摸索,有了好的想法别忘了多分享。   回复  引用  查看    

#27楼 2007-09-25 17:12 appledou      

如果用户非要使用CustomerName进行搜索而我们又不想使用JOIN是不是可以这样来实现:先根据CustomerName搜索对应的CustomerNo,然后再根据CustomerNo在SalesOrder表中进行搜索。这样既满足用户需求也可以在单表上实现搜索

不过我有一个疑问,如果不使用JOIN操作,上面的查询结果中既有Customer的数据(District,Address),也有SalesOrder的数据(OrderNo,OrderDate)这怎么实现啊?
  回复  引用  查看    

#28楼[楼主] 2007-09-26 12:54 RicCC      

是说列表里面?可以每个循环取一次,也可以用一个in一条SQL搞定
这个对数据量小的表性能没什么改善,而是增加了并发量,但对于大的表性能好处是明显的,最难的是任何复杂的业务都分解成这种方式
另外一些就是界面组件的标准化有了基础,可以提高开发效率,用缓存去降低数据库并发量
  回复  引用  查看    

#29楼 2008-07-29 23:09 gaga[未注册用户]

楼主,俺来晚了啊,你这几篇文章简直就是专门给俺准备的啊   回复  引用    




发表评论

昵称: [登录] [注册]

主页:

邮箱:(仅博主可见)

评论内容:

  登录  注册

[使用Ctrl+Enter键快速提交评论]

0 608925




相关文章:

相关链接:

导航

News

搜索

 

常用链接

随笔分类

随笔档案

Ruby & Rails

其它

数据库

最新评论

  • 1. re: WSE 3.0 UsernameToken应用
  • 涉及到网络访问权限方面的问题,还得针对实际情况下多试试了
  • --Richie未登录
  • 2. re: WSE 3.0 UsernameToken应用
  • 非常感谢楼主,我试了你说的哪几种情况,好像也是不好使!!
    过两天再次和您探讨!!
    真的非常感谢!!
  • --autuam
  • 3. re: WSE 3.0 UsernameToken应用
  • storename跟机器名没有关系,你打开证书管理单元可以看到Personal(个人)这样一些目录,wse中的storename跟这些目录有个对应关系,msdn上可以查到机器在域环境下需要设置Netw...
  • --Richie未登陆
  • 4. re: WSE 3.0 UsernameToken应用
  • 使用catch (System.Web.Services.Protocols.SoapException error)没有异常。但是 catch(Exception e) { Console.W...
  • --autuam

阅读排行榜

评论排行榜