悠然乱弹:一段SQL引发的性能危机及其背后隐藏的设计缺陷

简介:

故事发生的背景是,在文件上传的时候,有时间会有人上传了文件,但是最后没有使用上传的文件,这样就会产生一些垃圾文件。

原来软件作者就想写一个后台定时任务程序,来清除这些垃圾文件?

由于作者坚定的不让我发她的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来遍历所有的数据,就是有非常大的问题的。这个时候就要引入一系列的复杂问题来解决,比如:把单机计算变成集群计算,把整个计算变成分段时间,不管怎么样,都是非常复杂的处理过程。

无为而治的方法

下面就要推出最快的、最省事的、效率最高的方法。

其实一般来说,只要是算法都是有优化空间和余地的,因此一般来说本人很少把话说满的。这次本人使用了“最”字,那就是用来表明未来已经没有优化的空间了,那什么样的算法才能没有优化的空间呢?答案就是:啥也不做。

当然了,实际上也不可能啥也不做,问题就在哪里,你不做怎么可能好呢?

实际上就是把任务进行一定的分解。通过把架构进行合理的分析与设计,把所有的文件上传、删除都做成公共的方法(或服务),在需要与文件打交道的地方,凡是与文件打交道的时候,做如下处理:

  1. 文件上传:在文件上传数据中加一条数据,比如:文件相关信息,唯一标识,引用次数为0
  2. 文件关联:当数据与文件关联的时候,修改引用次数为+1
  3. 文件取消关联:当数据与文件取消关联的时候(一般来说是删除或编辑的时候置为空或者换成另外一个的时候),修改引用次数为-1

自次,当要清理垃圾的时候,就非常简单的了,只要:

  select ... from ... where ref_times=0

然后进行相应的清理工作就好。

这个时候就优化了处理模式,并且把文件引用数据的维护分解到业务工作的过程当中,可以极大幅度的提升清理垃圾的处理效率。当然有的人说了:如果这么做,会使得我的业务处理过程变慢,那怎么办?其实也没有关系了,你可以把这个变成异步消息的方式,通知文件引用处理去做这件事情就行了,这样就不会影响到你的业务处理效率了。

总结

通过上面的分析,我们对文件上传过程中的垃圾清理过程进行优化,并分析了原来的问题之所在,及后面3种优化方式及其优缺点对比。

当然,实际上许多朋友也会有更好的办法来解决,欢迎大家参与讨论,并批评指正。

如果,你喜欢我的博文,请关注我,以便收到我的最新动态。

如果对我的开源框架感兴趣,可以从这里获取到最新的代码,也可以访问Tiny官网获取更多的消息,或到Tiny社区进行即时交流。

相关文章
|
5月前
|
存储 SQL 关系型数据库
PolarDB这个sql行存和列存性能差别好大 ,为什么?
PolarDB这个sql行存和列存性能差别好大 ,为什么?
40 0
|
4天前
|
SQL Windows
安装SQL Server 2005时出现对性能监视器计数器注册表值执行系统配置检查失败的解决办法...
安装SQL Server 2005时出现对性能监视器计数器注册表值执行系统配置检查失败的解决办法...
12 4
|
25天前
|
SQL 分布式计算 数据处理
奇思妙想的SQL|兼顾性能的数据倾斜处理新姿势
本篇为系列第2篇,分享在支付宝支付数据链路改造升级过程中,针对数据倾斜的优化实践新方法,在解决数据倾斜问题的同时,还能兼顾更优的计算性能!
|
4月前
|
SQL 存储 缓存
如何通过优化SQL查询提升数据库性能
SQL查询是数据库的核心功能之一,对于大型数据量的应用程序来说,优化SQL查询可以显著提升数据库的性能。本文将介绍如何通过优化SQL查询语句来提升数据库的性能,包括索引优化、查询语句优化以及其他一些技巧。
|
4月前
|
SQL 监控 关系型数据库
解密SQL性能异常事件及阿里云数据库的性能调优实践
作为开发者想必都知道数据库是现代应用的核心组件之一,而且在当今互联网时代之下,SQL查询的性能直接影响系统的整体性能,它的性能对于系统的稳定性和响应速度至关重要。那么本文就来讨论一下SQL性能异常的排查和优化方法,包括我个人印象深刻的SQL性能异常事件,以及分享一下使用阿里云数据库产品/工具进行SQL性能调优的经验和心得体会。
90 1
解密SQL性能异常事件及阿里云数据库的性能调优实践
|
4月前
|
SQL 监控 关系型数据库
常见的SQL优化和排查性能异常秘籍
常见的SQL优化和排查性能异常秘籍
36 1
|
5月前
|
关系型数据库 MySQL 数据库
史上最全的MySQL性能手册(优化+SQL+并发+数据库)
史上最全的MySQL性能手册(优化+SQL+并发+数据库)
|
8月前
|
SQL 数据库
使用事务码 SAT 比较传统的 SELECT SQL 语句和 OPEN / FETCH CURSOR 分块读取 ABAP 数据库表两种方式的性能差异试读版
使用事务码 SAT 比较传统的 SELECT SQL 语句和 OPEN / FETCH CURSOR 分块读取 ABAP 数据库表两种方式的性能差异试读版
65 0
|
10月前
|
SQL 存储 关系型数据库
提升SQL查询性能:深入理解和策略实践
1. 使用索引   索引是一个用于快速查询和检索数据的数据库结构。你可以将其想象成一本书的目录,它可以让数据库引擎不必扫描整个表,而是直接定位到所需的数据,从而大大提高查询的性能。以下是几种索引类型:
123 0