悠然乱弹:一段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社区进行即时交流。

时间: 2025-01-21 07:10:45

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

SQL Server 2008性能故障排查(二)——CPU

原文:SQL Server 2008性能故障排查(二)--CPU 承接上一篇:SQL Server 2008性能故障排查(一)--概论 说明一下,CSDN的博客编辑非常不人性化,我在word里面都排好了版,贴上来就乱得不成样了.建议CSDN改进这部分.也请大家关注内容不要关注排版.同时在翻译的过程中本人也整理了一次思路,所以还似乎非常愿意翻译,虽然有点自娱自乐,但是分享给大家也是件好事 CPU 瓶颈:CPU瓶颈可能因为某个负载所需的硬件资源不足而引起.但是过多的CPU使用通常可以通过查询优化(特

和表值函数连接引发的性能问题

原文:和表值函数连接引发的性能问题     最近调优过程中遇到一个问题,就是表值函数作为连接中的一部分时,可能会引起麻烦,本文会简单阐述表值函数是什么,以及为什么使用表值函数进行连接时会引发性能问题. 表值函数     SQL Server中提供了类似其他编程语言的函数,而函数的本质通常是一段代码的封装,并返回值.在SQL Server中,函数除了可以返回简单的数据类型之外(Int.Varchar等),还可以返回一个集合,也就是返回一个表.     而根据是否直接返回集合或是定义后再返回集合,表

如何提高SQL Server数据仓库性能

数据仓库通常是企业内部最大的数据库了.构建和管理系统是项大的任务,这些项目会由于众多用户提供的不兼容的输入而很快变得难以控制.提高系统的查询性能是可以实现的,但是必须要经过周密计划,随后还有具有远见的设计和开发阶段.在这篇文章中,我们将会列出获得并且为性能需求计划的一些技术,然后我们会在SQL Server上提高你的数据仓库性能. 需求 对于需要支持数百个GB到几个TB的数据系统而言,性能永远不会是你需要考虑的最后一件事情.当你收集数据仓库需求的时候,你就会被训练掌握用户性能需求的规则了.基于这

专家教你优化你的SQL Server硬件性能

优化SQL Server硬件性能 对SQL Server的性能调整有很多种方式,就像建造一所房子.你必须找到一个合适的地点,拥有肥沃的土壤.构建一个结实的地基,支持2到3层的小楼,安装电气和插座,对墙壁刷油漆并进行装饰,最后进行不断的维护. 调整SQL Server的性能也具有类似的项目,其中包括: 硬件 Windows服务器 SQL Server 数据库设计 索引设计 T-SQL 开发 网络基础设施 前端代码 平台维护 虽然在获得高性能方面,没有哪个单个的组件比其他的具有更大的重要性,但是要争

SQL点滴22—性能优化没有那么神秘

原文:SQL点滴22-性能优化没有那么神秘 经常听说SQL Server最难的部分是性能优化,不禁让人感到优化这个工作很神秘,这种事情只有高手才能做.很早的时候我在网上看到一位高手写的博客,介绍了SQL优化的问题,从这些内容来看,优化并不都是一些很复杂的问题,掌握了基本的知识之后也可以尝试优化自己的SQL程序,甚至是其他相关的程序.优化是一些工作积累之后的经验总结和代码意识,只要平时注意积累,你也可以做优化的工作.这一篇随笔是转载,不过我强烈推荐给所有对数据库优化有兴趣的博友,读了这一篇之后下一

SQL Server数据库性能优化

设计1个应用系统似乎并不难,但是要想使系统达到最优化的性能并不是一件容易的事.在开发工具.数据库设计.应用程序的结构.查询设计.接口选择等方面有多种选择,这取决于特定的应用需求以及开发队伍的技能.本文以SQL Server为例,从后台数据库的角度讨论应用程序性能优化技巧,并且给出了一些有益的建议. 1 数据库设计 要在良好的SQL Server方案中实现最优的性能,最关键的是要有1个很好的数据库设计方案.在实际工作中,许多SQL Server方案往往是由于数据库设计得不好导致性能很差.所以,要实

SQL Server数据库性能优化技术第1/2页_数据库其它

设计1个应用系统似乎并不难,但是要想使系统达到最优化的性能并不是一件容易的事.在开发工具.数据库设计.应  用程序的结构.查询设计.接口选择等方面有多种选择,这取决于特定的应用需求以及开发队伍的技能.本文以SQL  Server为例,从后台数据库的角度讨论应用程序性能优化技巧,并且给出了一些有益的建议.  1 数据库设计  要在良好的SQL Server方案中实现最优的性能,最关键的是要有1个很好的数据库设计方案.在实际工作中,许多SQL  Server方案往往是由于数据库设计得不好导致性能很差

SET STATISTICS IO和SET STATISTICS TIME 在SQL Server查询性能优化中的作用

原文:SET STATISTICS IO和SET STATISTICS TIME 在SQL Server查询性能优化中的作用 近段时间以来,一直在探究SQL Server查询性能的问题,当然也漫无目的的查找了很多资料,也从网上的大神们的文章中学到了很多,在这里,向各位大神致敬.正是受大神们无私奉献精神的影响,所以小弟也作为回报,分享一下关于SET STATISTICS IO和SET STATISTICS TIME这两条T_SQL命令,在查询优化性能中的作用.       首先我想说明一下这篇文章

如何提高SQL SERVER的性能

server|性能  如何提高SQL SERVER的性能   第一篇:通过维护表的索引来提高数据的访问速度   大多数SQL Server表需要索引来提高数据的访问速度,如果没有索引,SQL Server要进行表格扫描读取表中的每一个记录才能找到索要的数据.索引可以分为簇索引和非簇索引,簇索引通过重排表中的数据来提高数据的访问速度,而非簇索引则通过维护表中的数据指针来提高数据的索引.      索引的体系结构:   为什么要不断的维护表的索引?首先,简单介绍一下索引的体系结构.SQL Serve