有没有好的数据更新方式呢?
我们有必要了解一下数据更新的过程:
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)步。
该方案的策略就是在数据正在本地更新的时候开启另外一个线程下载下一个数据块,这样等本次数据块更新完以后,能够很快更新下一个数据块,而不是原来那样,必须等到数据下载完成以后才能进行下面的操作。这就好比流水线上的操作,能够大幅提高生产效率!
本次优化策略总结:
1,采用Command更新方式;
2,采用Insert or Replace 更新的sqlite 语法;
3,“适时”显示更新进度;
4,取消获取更新主键的WebService请求。
5,采用多线程下载。
优化效率测试:
采用上面的优化策略,到底能够为我们提升多大的数据更新效率?我们还是得用测试数据来说明。
测试方法:
在更新方法的文件头,我们定义下面的预编译指令:
#define DEBUG
#define FAST
/*
* #define DEBUG //定义是否写入调试信息,正式版请删除该标记;
* #define FAST //定义是否采用快速下载模式,如果不是快速下载模式,取消该定义即可,或者改名,例如:
#define NO_FAST
*/
在具体程序部分,使用下面的方式,告诉编译器我们采用何种下载模式:
#if(!FAST)
//获取DataAdapter 架构
DataSet ds1 = Tranconn.TranDataSet(tablename);
#else
//获取更新命令对象 dth,2008.12.3
IDbCommand cmd = Tranconn.BuildCommand(al,tablename);
#endif
这样,我们就能够轻松的编译我们的两个不同版本:快速下载模式/普通下载模式.
测试结果:
下表统计了本次的测试结果,由于时间关系,没有进行更大规模的数据量测试.
===============================================================
局域网版本 | 优化率 | 平均 | |||||||
模式 | FAST | ||||||||
下载表 | 记录条数 | 分块数 | 每次条数 | 分 | 秒 | 毫秒 | 总毫秒 | ||
FT_SysMessage | 37 | 1 | 37 | 0 | 0 | 328 | 328 | ||
jjjzcz | 30684 | 13 | 2360 | 0 | 40 | 359 | 40359 | ||
模式 | Normal | ||||||||
FT_SysMessage | 37 | 1 | 37 | 0 | 0 | 687 | 687 | 0.522562 | |
jjjzcz | 30684 | 13 | 2360 | 1 | 15 | 62 | 75062 | 0.462324 | |
0.492443 | |||||||||
外网版本 | |||||||||
模式 | FAST | ||||||||
下载表 | 记录条数 | 分块数 | 每次条数 | 分 | 秒 | 毫秒 | 总毫秒 | ||
FT_SysMessage | 32 | 1 | 32 | 0 | 0 | 437 | 437 | ||
jjjzcz | 29124 | 12 | 2427 | 0 | 47 | 234 | 47234 | ||
模式 | Normal | ||||||||
FT_SysMessage | 32 | 1 | 32 | 0 | 0 | 609 | 609 | 0.28243 | |
jjjzcz | 29124 | 12 | 2427 | 1 | 32 | 593 | 92593 | 0.489875 | |
0.386153 | |||||||||
数据更新效率测试 | |||||||||
测试时间: | 2008.12.4 | 邓太华 | |||||||
测试结果: | |||||||||
采用FAST模式,局域网版本效率提升了49.24%,外网版本效率提升了38.62%。 |
===============================================================
下面的数据是对FAST模式的进一步优化的测试日志:
-------------------FAST 1.0----------------------
13:14:39 开始下载表:jjjzcz,记录条数:30863,分块:13,每次条数:2374
13:15:27 完成下载表:jjjzcz,用时:0分48秒62毫秒
13:16:52 开始下载表:jjjzcz,每次条数:2374
13:17:46 完成下载表:jjjzcz,用时:0分54秒15毫秒
-------------------FAST 2.0 线程优化---------------
13:28:18 开始下载表:jjjzcz,每次条数:2374
13:28:56 完成下载表:jjjzcz,用时:0分37秒468毫秒
13:30:34 开始下载表:jjjzcz,每次条数:2374
13:31:12 完成下载表:jjjzcz,用时:0分38秒234毫秒
**********************************************************************
从该日志看出,我们为每一个数据块的处理大约节约了0.8秒的时间,如果网络状况不是很好,或者数据量更大,我们能够取得更多的优势。
从本次的测试数据得出, 优化后的FAST模式性能比优化前的FAST模式进一步提高了20%的效率! (2008.12.5 全文完) 原文链接:https://www.f2er.com/sqlite/203157.html