大幅提高数据更新效率技术探究
我们的产品采用的是客户端不定期从服务器端批量更新数据的 C/S 应用模式,客户在使用软件的时候,每次软件启动就会开始向我们服务器查询是否有最新的数据需要下载或者更新,如果客户每天都在更新数据的话每次需要更新的 数据量是不大的,但是如果客户很久没有更新数据,就会有多达上万条以上的数据需要更新。之前,曾经听说一个客户端更新数据花费了半个多小时的时间。目前, 我们采用的是如果客户太久没有更新数据,我们直接下载一个新的数据库的方式,但是这个过程比直接更新数据要繁杂一些,而且也要花不少时间。
有没有好的数据更新方式呢? 我们有必要了解一下数据更新的过程: 1,客户端启动软件-〉客户端查询是否有最新数据,如果有,进入下面的更新过程: 2,获取本次要更新的“表”列表;对每一个表,执行下面的过程: 3,客户端发起一个Web服务请求,请求本次要获取的数据,如果数据量较大,分块获取; 4,服务器端收到请求,查询SqlServer上的数据,先压缩数据,再通过该Web服务返回给客户端; 5,同上3和4的过程,客户端获取本次数据要更新的标的主键序列; 6,客户端解压缩收到的数据流,并生成内存中的数据集; 7,根据5步中获取的主键序列,一次性删除本地SQLite数据库表中的数据; 8,查询本地表中的数据架构,创建数据适配器对象(DataAdapter); 9,对6步中生成的本次要更新的数据集(DataSet),对数据集中的每一行数据执行下面的过程: 10,将数据集中的当前行数据复制到一个新的数据集对象中; 11,使用数据适配器对象,更新这个新的数据集对象的数据到SQLite中; 12,向窗体对象发送当前更新进度消息; 13,返回第9步,直到更新完每一行数据; 14,返回地2步,直到更新完所有要更新的表; 了解了数据的更新过程,我们来看可能进行优化的地方: 1,要更新的数据流量是否可以更小? 2,更新的过程是否可以精简? 3,数据更新的瓶颈在哪里? 4,解决瓶颈的方法有没有? 根据上面的4个问题,我们来一个个检查问题所在: 1,寻找要导出的数据(要更新的数据)的最优导出方式: 我在前面一篇文章 《数据导入导出测试》作了说明,数据集保存为二进制文件,它的压缩比是最高的,目前我们采用方式就是如此; 2,是否有必要向发送两次数据请求,也就是数据更新过程中的第5步,因为我们可以采取其他手段获取这个数据(下面详细说明); 3,数据更新的瓶颈是“下载”阶段还是“本地更新”阶段? 经过测试,如果网络情况良好,现有的更新方式,数据在本地更新时间远远大于下载的时间;如果网络情况很差,那么数据下载部分将是数据更新的瓶颈;实际情况 是,大部分客户的网络状况还是良好的,从程序的更新进度显示主要还是数据在本地更新花费了大约2/3以上的时间。 4,找到了瓶颈主要是数据在本地的更新过程缓慢,那么我们需要仔细探究一下怎样优化程序的执行效率,根据前面 《数据导入导出测试》的说明: ===================================== ,对表 JJTZZH_CG 的导入(XML格式), 采用DataAdapter方式,内存80.086K,用时319秒; 采用Command方式,内存74.732K,用时85秒; ===================================== 在内存占用差不多,而且都是单条更新的情况下,采用Command方式能够比 DataAdapter方式快大约2/3,这是一个惊人的差异! 5,还有其他优化措施吗? 对了,既然是数据更新到数据库,那么更新效率一定跟数据库有关系。采用更好更强大的数据库?比如Oracle,SqlServer?基于客户端的应用情 况,这是不可能的。为了更安全和更轻便,我们使用的是开源的数据库SQLite。我们找找它有没有我们能够利用的特性。 查阅SQLite官方文档,说支持一种特殊的插入SQL: Insert or Replace into Table(Fields) Values(Results); 在同一个SQL语句中,插入和更新可以使用相同的语法,更为重要的是,它省去了我们更新数据前删除原有数据的过程,两次数据操作省去一次;同时,我们可以省去数据更新过程的第5步和第7步。 最后,我们还可以利用一个不引人注意的特性:Windows消息机制 我们对Windows窗体控件设置的很多属性,其实在Windows内部,都是使用Windows消息队列的,所以我们在一个大循环的时候,为了显示窗体空间的最新值,都必须使用一个方法: Application.DoEvents(); //启用Windows消息循环 本来我们向窗体控件赋值,告诉用户我们的程序当前更新的进度是多少。由于循环是非常快的,意味着我们在很短的时间向Windows发送了大量的消息,尽管窗体控件处理这些消息也很快,但还是要花费很多时间。 还是测试最能够说明问题,我们仍然测试原来的那个数据导入导出程序,这次我们不查看程序的进度条,耐心等待它自动执行完毕,结果一个原本需要12秒的过 程,这次只需要4-5秒,这又是一个惊人的发现!(其实这不能说是发现,是常识,只是我们都没有注意而已)当然,我们不能啥也不对用户说,我们只需要在 “适当”的时候告诉用户,我们已经更新到多少了,而不是每时每刻的区做,这样没有多大意义。 6,下载时间又成了新问题! 经过上面的优化措施,数据在本地插入的时间已经很快了,如果网络情况不是很好,下载时间将成为新的问题,插入一块数据只需要1-2秒,下载一块数据可能还需要1秒左右,看来我们得采用最后的优化手段: 使用多线程下载数据! (为啥不使用多线程插入数据?前面已经说了,我们采用的是SQLite数据库,不支持并发写入:< ) 以前曾经考虑过把数据全部下载下来的方案,但是这种方案有以下不足: a) 使用多线程,很难控制下载数据库的处理顺序(数据是按照顺序存储的); b) 使用多线程,可能出现中间块数据下载失败或者错误,从而导致后续数据下载无意义(数据必须完整); c) 使用单线程下载,性能上没有优势,而且还要让用户等待很长一段时间才能开始数据更新操作; d) 数据全部下载下来,怎么保存又是一个新问题,放在磁盘中每次加载数据需要消耗很多时间,全部放在内存中如果数据很大用户内存不足的情况可能出现。 看来怎么样使用多线程下载,真是很麻烦的事情,问题太多。但是,我们还是可以结合我们程序实际的情况,找到突破点: (1),首先下载第一个数据块; (2),如果数据多于2个数据块,那么尝试下载下一个数据块; (3),因为采用多线程异步下载,程序立刻进入当前数据块的本地数据更新阶段; (4),当前数据块更新完成,检查下一块数据是否已经下载完成,如果没有,等待; (5),返回第(2)步。 该方案的策略就是在数据正在本地更新的时候开启另外一个线程下载下一个数据块,这样等本次数据块更新完以后,能够很快更新下一个数据块,而不是原来那样,必须等到数据下载完成以后才能进行下面的操作。这就好比流水线上的操作,能够大幅提高生产效率! 我们在文章最后,将附加上采用该策略的实际效率测试。 本次优化策略总结: |
- .net – 带PostgreSQL的Entity-Framework 5.0
- Oracle密码文件的使用和维护
- oracle:ORA-00054: 资源正忙, 但指定以 NOWAIT 方
- c – 使用boost :: format作为符号值打印bool?
- PostgreSQL FAQ贴 【转】
- SQLITE学习笔记一(打开、操作及关闭数据库,C程序
- c – 未解析的外部符号错误,即使该函数存在char
- ruby-on-rails – 在Rails应用程序中查找未使用的
- 阿里云Linux CentOS 7 Docker部署使用gogs搭建自
- ruby-on-rails – 如何使用Factory Girl定义多个