3月 252012
 

固定链接:http://www.gamtin.info/archives/235

这是最近以来非常忙的根源之一。

到现在为止,似乎即将有一个结果了,于是简单整理一下。

项目背景

简单介绍一下现在这个项目。

这是一个基于.NET Framework 2.0的SmartClient项目,当然这样说是为了好听。实际上就是一个C/S的东西,通讯靠的是WebService协议,将传输数据序列化为XML格式传来传去。客户端程序靠微软的ClickOnce来发布,还算方便。后面保存数据的是Oracle 10g。顺带一提,对DB的CRUD操作,基本都是Stored Procedure完成的。

问题

某客户积累了N年的数据,业务表达到了100万条,关联的Master表数据差不多30万条左右吧。某机能的任务就是按照客户要求收集这些数据,并且进行一些出报表或者整体更新的任务。

实际操作中,数据收集(结果量50万左右)会消耗2小时以上的时间,因为设置了2小时的Timeout,所以实际多少时间不详。改到结果20万左右的时候,2小时以内倒是可以结束,但是不时的在Client端发生OutOfMemoryException。为此,客户一直在抱怨,我们的压力很大。

分析/解决

表面现象是,大量数据的情况下性能非常差劲。分析下来,突破点有三:

  1. Orcale方面:优化SQL
  2. 程序方面:优化业务逻辑算法
  3. 传输方面:提高数据传输效率

逐条分别来说。

Oracle方面

在DB表结构已经存在,并且已经运行多年的情况下,如果想改变设计是非常不现实的。而且,现在来看,问题似乎并不是表结构设计有多么的不合理。业务数据表主要就是一个,虽然需要连接的Master表不少,且数据量也不小,但是都是主键之间的连接,这种小事对Oracle来说应该不会有太大的问题。

然后分析SQL文。这时候,做外包的短板就出现了,虽然接触了好多年的Oracle,但是对于性能上的考虑完全没有过。被逼到没办法,赶快上网学习怎样研究分析Oracle的执行计划。传统观念认为(包括网上的不少类似“Oracle性能优化经验XX条”),有子查询会降低性能,但是实际的计划看来,子查询对性能的影响其实是挺小的(注意,这里是有条件的,某些子查询的写法似乎对查询效率影响不大),Oracle引进的CBO优化器的效果果然很牛逼。折腾了2、3天,发现SQL完全没有优化的可能。

实际的执行来看,用本地的有5万条结果的DB,用不到5分钟便可以查询完成并且返回到客户端了,这种性能其实已经不错了。

程序方面

分析业务逻辑算法,这其实是相对最简单的了。

检查发现,客户端表示这些数据的性能非常差。还是上面的5万条数据,处理后,主画面实际上只需要表示1500条左右(其他数据会在几个子画面表示)。但就是这个处理,竟然消耗了20分钟!!

一看代码,才发现,这是个“金矿”啊。当时的程序员,将这数据Copy了2份,然后用3层循环来不停的遍历这些可怜的数据。整个处理下来,每条数据被访问了n^3次。于是果断的改掉,成功的将20分钟的处理降低到1分钟左右。

这个问题,正好可以用来以后做面试用:

假设DataTable中有5列(Columun_1~Column_5),现在想得到这样的结果,你怎么来实现?如果数据量非常大,怎么考虑性能?

在这个DataTable后面增加一列,Count(int),用来保存Column_1,Column_2结果相同的数据的个数。

例:

Column_1 Column_2 Column_3 Column_4 Column_5
1 1 1 1 1
1 1 2 2 2
1 1 3 2 2
1 2 1 1 1
1 2 2 2 2
1 1 4 4 4
2 1 1 1 1
2 1 2 2 2

结果:

Column_1 Column_2 Column_3 Column_4 Column_5 Count
1 1 1 1 1 4
1 1 2 2 2 4
1 1 3 2 2 4
1 2 1 1 1 2
1 2 2 2 2 2
1 1 4 4 4 4
2 1 1 1 1 2
2 1 2 2 2 2

其实是挺简单的算法,但是似乎对一些外包的程序员来说,有点难度的。

实际上,在DataTable中,用循环是性能很差的。如果只要取得Count之类的,还是用Select().Length/View.Count效率来得更好。

传输方面

大量的数据,不仅仅是运算的成本高,传输的成本也不低。实际测试来看,5万条数据的大小近50MB,这些数据在网络上传来传去的效率是不能被无视的。而且还发生过客户端接受数据的时候出现OutOfMemoryException。

于是有了这几种方案:

  1. 多次传送方案之一:多次从DB取得数据,类似分页。
    这个问题,头两天在Twitter上咨询过,因为结果是需要Orderby的,且排序的列包括了没有索引的列,所以性能会更差。一次整体取得4分钟不到,分开取得(每次1000条)的话,会直奔20分钟去了。此方案否决!
  2. 多次传送方案之二:单次DB取得,多次Client和WebService通讯。
    因为是基于WebSercie,就是单纯的请求(Request)/响应(Response)模式,一次请求结束后,该线程便被回收了,无法保存数据。且项目的框架已经固定,没法实现类似轮询功能(让WebService继续保持),再加上时间紧,没法(也不敢)大规模的改造项目框架。便想到,将数据以文件形式保存到Server侧,直到全部发送完毕再删除。虽然增加了些IO操作,但是就现在的硬件性能而言,这不到100MB的文件实在是小菜一碟。
    这个方案的问题也是明显的,这么多次的操作,如果某次出现错误,或者客户端异常了,搞不好这个文件就作为垃圾永远的留在Server上了,这是个无法接受的现象。所以,这个方案也基本上被否决了。
  3. 数据压缩
    现在的序列化方法是都转成Base64格式,经验来看,转成Base64本身就会让数据量增加。本来数据量就不小,还加,压力更大了。于是增加了压缩和解压缩的逻辑。实际测试来看,压缩后的数据量大概是原来的14%,可以达到预期,似乎是个比较完美的解决方案。
    但是 ,问题也是有的,虽然减轻了网络压力,但是增加了服务器/客户端的内存和CPU压力。这时候就是个平衡的问题了,相对于增加网络带宽,似乎换个性能更好的机器的成本更低吧。
    到这里,想到了西乔的那个漫画:猛击这里

最后

到这里,基本就基本结束了。本来想简单介绍了,但也写下了不少。

折腾这段时间下来,感觉做外包的,视野确实窄,接触的东西也太少。

  3 Responses to “大量数据,毫无办法吗?”

  1. 对于那题目…不知道你是不是循环select(),这样循环1次就可以了,但是数据量大时,select是很慢的,倒不如用Dictionary来辅助,循环2次,一次计算数量,一次赋值

  2. 您好,我的疑问是:就大数据量而言,比如200M,200万条的记录传输。压缩技术比分页技术更能提高效率么?压缩和解压本身是需要时间的。

    • 在这里,讨论哪个更好是需要结合需求的。
      我的这个项目的这个需求,是要求一次性数据都要表示在画面上的。主要要解决的问题一个是数据量过大会使Server出现OutOfMemoryException,另外才是性能。
      当然,如果,每次表示的数据量仅仅是几百条的话,分页是很好的方案。

 Leave a Reply

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

(required)

(required)