原来软件作者就想写一个后台定时任务程序。来清除这些垃圾文件?
因为作者坚定的不让我发她的SQL语句(这个我也理解。这么丑陋的SQL),所以这里就不发源码了,发伪代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | void deleteMissLinkFile{ List fileList=getFileList(); List deleteFileList=new ArrayList(); for(file:fileList){ int count1=execute(select count(*) from ...); int count2=execute(select count(*) from ...); int count3=execute(select count(*) from ...); int count4=execute(select count(*) from ...); int count5=execute(select count(*) from ...); if(count1==0&&count2==0&&count3==0&&count4==0&&count5==0){ deleteFileList.add(file); } } delete(deleteFileList); } |
当然。这里我已经给进行了一定的加工,使得看起一美丽了很多,实际上,嗯嗯,实在是丑。 这个时候的性能情况是怎么样的呢?说是表里的数据仅仅有500多条,可是运行时间要100多秒,可是实际上实际的应用场景都远不止这个数量级,并且随着数据的添加,性能会呈指数级下降。
我说你去加10万条记录測试一下。保证你一晚上算不出来。
好吧,废话少说。接下来看看怎么优化这段程序。
在開始之前,我们能够假设有N个文件,有M个文件引用表。并且假设全部的文件引用表中的记录条数都一样。
很显然,原来的实现方法中运行了:1次文件数查询+N*M次统计操作
最笨的优化方法 先用成本最低的方式来优化一把:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | void deleteMissLinkFile{ List fileList=getFileList(); List deleteFileList=new ArrayList(); for(file:fileList){ int count1=execute(select count(*) from ...); if(count1>0)continue; int count2=execute(select count(*) from ...); if(count2>0)continue; int count3=execute(select count(*) from ...); if(count3>0)continue; int count4=execute(select count(*) from ...); if(count4>0)continue; int count5=execute(select count(*) from ...); if(count1>0)continue; deleteFileList.add(file); } delete(deleteFileList); } |
嗯嗯,通过上面的重构。性能立即就能够提升一倍。难看是难看了一点。可是1倍也是不小的提升哦。 原因,原来是要把全部的统计值都算出来,再进行推断。通过上面的重构,平均仅仅要查一半就能够退出了,所以性能会有1倍的提升。
1次文件数查询+N*M/2次统计操作
一般的优化方法 偶当时提醒她说,你能够把内外换换,性能就会提升很多,结果死活听不懂。。
实际上逻辑是这样的,因为统计操作的运行效率是很低的,而带主键的查询速度是很快的,也就是把逻辑从:遍历全部的文件看看引用次数是多少,改变成从全部文件列表中删除全部已经引用的文件,其余就是要删除的垃圾文件。
1 2 3 4 5 6 7 8 9 10 11 12 13 | void deleteMissLinkFile{ List fileList=getFileList(); List refList1=execute(select file from tb1…) for(ref:refList1){ fileList.remove(ref) } List refList2=execute(select file from tb2…) for(ref:refList2){ fileList.remove(ref) } …… delete(deleteFileList); } |
通过上面的优化,须要运行的SQL语句是: 1+m 条SQL语句,其他都是大量的内存数据比对。相对来说。性能会高太多,通过一定的技巧进行一些优化,会有更大的提升。
这样的方式。我毛估估比原始的方式,能够提高两个数量级以上。
为什么提高了两个左右数量级还是说比較笨的方法呢?
因为这样的方法尽管比原始的方法有了显著的提升,可是还是存在严重的设计问题的。
首先,当数据量比較小的时候(这里的小是指与互联网应用中的数据相比),做全然遍历是没有问题的。可是当数据量比較大的时候,用一条SQL来遍历全部的数据,就是有很大的问题的。
这个时候就要引入一系列的复杂问题来解决。比方:把单机计算变成集群计算,把整个计算变成分段时间,无论怎么样。都是很复杂的处理过程。
无为而治的方法 以下就要推出最快的、最省事的、效率最高的方法。
事实上一般来说,仅仅要是算法都是有优化空间和余地的。因此一般来说本人很少把话说满的。这次本人使用了“最”字。那就是用来表明未来已经没有优化的空间了。那什么样的算法才干没有优化的空间呢?答案就是:啥也不做。
当然了。实际上也不可能啥也不做。问题就在哪里,你不做怎么可能好呢?
实际上就是把任务进行一定的分解。
通过把架构进行合理的分析与设计。把全部的文件上传、删除都做成公共的方法(或服务),在须要与文件打交道的地方。凡是与文件打交道的时候。做例如以下处理:
- 文件上传:在文件上传数据中加一条数据,比方:文件相关信息,唯一标识。引用次数为0
- 文件关联:当数据与文件关联的时候,改动引用次数为+1
- 文件取消关联:当数据与文件取消关联的时候(一般来说是删除或编辑的时候置为空或者换成另外一个的时候),改动引用次数为-1
select ... from ... where ref_times=0
然后进行对应的清理工作就好。
这个时候就优化了处理模式。并且把文件引用数据的维护分解到业务工作的过程其中,能够极大幅度的提升清理垃圾的处理效率。当然有的人说了:假设这么做,会使得我的业务处理过程变慢,那怎么办?事实上也没有关系了,你能够把这个变成异步消息的方式,通知文件引用处理去做这件事情即可了,这样就不会影响到你的业务处理效率了。
总结 通过上面的分析。我们对文件上传过程中的垃圾清理过程进行优化。并分析了原来的问题之所在,及后面3种优化方式及其优缺点对照。
当然,实际上很多朋友也会有更好的办法来解决,欢迎大家參与讨论,并批评指正。
假设,你喜欢我的博文,请关注我,以便收到我的最新动态。
假设对我的开源框架感兴趣,能够从这里获取到最新的代码,也能够訪问Tiny官网获取很多其他的消息,或到Tiny社区进行即时交流。