最近在维护公司项目时,需要加载某页面,总共加载也就4000多条数据,竟然需要35秒钟,要是数据增长到40000条,我估计好几分钟都搞不定。卧槽,要我是用户的话估计受不了,趁闲着没事,就想把它优化一下,走你。

先把查询贴上:

  1. select Pub_AidBasicInformation.AidBasicInfoId,
  2. Pub_AidBasicInformation.UserName,
  3. Pub_AidBasicInformation.District,
  4. Pub_AidBasicInformation.Street,
  5. Pub_AidBasicInformation.Community,
  6. Pub_AidBasicInformation.DisCard,
  7. Pub_Application.CreateOn AS AppCreateOn,
  8. Pub_User.UserName as DepartmentUserName,
  9. Pub_Consult1.ConsultId,
  10. Pub_Consult1.CaseId,
  11. Clinicaltb.Clinical,AidNametb.AidName,
  12. Pub_Application.IsUseTraining,
  13. Pub_Application.ApplicationId,
  14. tab.num
  15. FROM   Pub_Consult1
  16. INNER JOIN Pub_Application ON Pub_Consult1.ApplicationId = Pub_Application.ApplicationId
  17. INNER JOIN Pub_AidBasicInformation ON Pub_Application.AidBasicInfoId = Pub_AidBasicInformation.AidBasicInfoId
  18. INNER JOIN(select ConsultId,dbo.f_GetClinical(ConsultId) as Clinical
  19. from Pub_Consult1) Clinicaltb on Clinicaltb.ConsultId=Pub_Consult1.ConsultId
  20. left join (select distinct ApplicationId, sum(TraniningNumber) as num from dbo.Review_Aid_UseTraining_Record  where  AidReferralId is null  group by  ApplicationId) tab on tab.ApplicationId=Pub_Consult1.ApplicationId
  21. INNER JOIN(select ConsultId,dbo.f_GetAidNamebyConsult1(ConsultId) as AidName  from Pub_Consult1) AidNametb on AidNametb.ConsultId=Pub_Consult1.ConsultId
  22. LEFT OUTER JOIN Pub_User ON Pub_Application.ReviewUserId = Pub_User.UserId
  23. WHERE Pub_Consult1.Directory = 0
  24. order by Pub_Application.CreateOn desc

执行后有图有真相:

 

这么慢,没办法就去看看查询计划是怎么样:

 

这是该sql查询里面执行三个函数时生成查询计划的截图,一看就知道,执行时开销比较大,而且都是花费在聚集索引扫描上,把鼠标放到聚集索引扫描的方块上面,依次看到如下详细计划:

 
 

从这几张图里,可以看到查询I/O开销,运算符开销,估计行数,以及操作的对象和查询条件,这些都为优化查询提供了有利证据。第1,3张图IO开销比较大,第2张图估计行数比较大,再根据其它信息,首先想到的应该是去建立索引,不行的话再去改查询。

先看看数据库引擎优化顾问能给我们提供什么优化信息,有时候它能够帮我们提供有效的信息,比如创建统计,索引,分区什么的。

先打开SQL Server Profiler 把刚刚执行的查询另存为跟踪(.trc)文件,再打开数据库引擎优化顾问,做如下图操作

 

最后生成的建议报告如下:

 

在这里可以单击查看一些建议,分区,创建索引,根据提示创建了如下索引:

  1. CREATE NONCLUSTERED INDEX index1 ON [dbo].[Pub_AidBasicInformation]
  2. (
  3. [AidBasicInfoId] ASC
  4. )
  5. CREATE NONCLUSTERED INDEX index1 ON [dbo].[Pub_Application]
  6. (
  7. [ApplicationId] ASC,[ReviewUserId] ASC,[AidBasicInfoId] ASC,[CreateOn] ASC
  8. )
  9. CREATE NONCLUSTERED INDEX index1 ON [dbo].[Pub_Consult1]
  10. (
  11. [Directory] ASC,[ApplicationId] ASC
  12. )
  13. CREATE NONCLUSTERED INDEX idnex1 ON [dbo].[Review_Aid_UseTraining_Record]
  14. (
  15. [AidReferralId] ASC,[ApplicationId] ASC
  16. )

索引创建后,再次执行查询,原以为可提高效率,没想到我勒个去,还是要30几秒,几乎没什么改善,优化引擎顾问有时候也会失灵,在这里只是给大家演示有这种解决方案去解决问题,有时候还是靠谱的,只是这次不靠谱。没办法,只有打开函数仔细瞅瞅,再结合上面的查询计划详细图,删除先前创建的索引,然后创建了如下索引:

  1. CREATE NONCLUSTERED INDEX index1 ON dbo.Report_AdapterAssessment_Aid
  2. (
  3. AdapterAssessmentId ASC, ProductDirAId  ASC
  4. )
  5. CREATE NONCLUSTERED INDEX index1 ON dbo.Report_AdapterAssessment
  6. (
  7. ConsultId ASC
  8. )

再次执行查询

 

好了,只需3.5秒,差不多提高10倍速度,看来这次是凑效了哈。

再来看看查询计划是否有改变,上张图来说明下问题:

 

从上图当中我们可以看到,索引扫描不见了,只有索引查找,聚集索引查找,键查找,而且运算符开销,I/O开销都降低了很多。索引扫描(Index Scan),聚集索引扫描(Clustered Index Scan)跟表扫描(Table Scan)差不多,基本上是逐行去扫描表记录,速度很慢,而索引查找(Index Seek),聚集索引查找,键查找都相当的快。优化查询的目的就是尽量把那些带有XXXX扫描的去掉,换成XXXX查找。

这样够了吗?但是回头又想想,4000多条数据得3.5秒钟,还是有点慢了,应该还能再快点,所以决定再去修改查询。看看查询,能优化的也只有那个三个函数了。

为了看函数执行效果先删除索引,看看查询中函数f_GetAidNamebyConsult1要干的事情,截取查询中与该函数有关的子查询:

  1. select Pub_Consult1.ConsultId,AidName from (select ConsultId,dbo.f_GetAidNamebyConsult1(ConsultId) as AidName
  2. from Pub_Consult1) AidNametb inner join Pub_Consult1
  3. on AidNametb.ConsultId=Pub_Consult1.ConsultId

得到下图的结果:

 

没想到就这么点数据竟然要46秒,看来这个函数真的是罪魁祸首。

该函数的具体代码就不贴出来了,而且该函数里面还欠套的另外一个函数,本身函数执行起来就慢,更何况还函数里子查询还包含函数。其实根据几相关联的表去查询几个字段,并且把一个字段的值合并到同一行,这样没必要用函数或存储过程,用子查询再加sql for xml path就行了,把该函数改成如下查询:

  1. with cte1 as
  2. (
  3. select A.AdapterAssessmentId,case when B.AidName is null then A .AidName else B.AidName end AidName
  4. from Report_AdapterAssessment_Aid as A left join Pub_ProductDir as B
  5. on A.ProductDirAId=B.ProductDirAId
  6. ),
  7. cte2 as
  8. (
  9. --根据AdapterAssessmentId分组并合并AidName字段值
  10. select AdapterAssessmentId,(select AidName+',' from cte1
  11. where AdapterAssessmentId= tb.AdapterAssessmentId
  12. for xml path(''))as AidName
  13. from cte1 as tb
  14. group by AdapterAssessmentId
  15. ),
  16. cte3 as
  17. (
  18. select ConsultId,LEFT(AidName,LEN(AidName)-1) as AidName
  19. from
  20. (
  21. select Pub_Consult1.ConsultId,cte2.AidName from Pub_Consult1,Report_AdapterAssessment,cte2
  22. where Pub_Consult1.ConsultId=Report_AdapterAssessment.ConsultId
  23. and Report_AdapterAssessment.AdapterAssessmentId=cte2.AdapterAssessmentId
  24. and  Report_AdapterAssessment.AssessTuiJian is null
  25. ) as tb)

这样查询出来的结果在没有索引的情况下不到1秒钟就行了。再把主查询写了:

  1. select distinct  Pub_AidBasicInformation.AidBasicInfoId,
  2. Pub_AidBasicInformation.UserName,
  3. Pub_AidBasicInformation.District,
  4. Pub_AidBasicInformation.Street,
  5. Pub_AidBasicInformation.Community,
  6. Pub_AidBasicInformation.DisCard,
  7. Pub_Application.CreateOn AS AppCreateOn,
  8. Pub_User.UserName as DepartmentUserName,
  9. Pub_Consult1.ConsultId,
  10. Pub_Consult1.CaseId,
  11. Clinicaltb.Clinical,
  12. cte3.AidName,
  13. Pub_Application.IsUseTraining,
  14. Pub_Application.ApplicationId,
  15. tab.num
  16. from   Pub_Consult1
  17. INNER JOIN Pub_Application ON Pub_Consult1.ApplicationId = Pub_Application.ApplicationId
  18. INNER JOIN Pub_AidBasicInformation ON Pub_Application.AidBasicInfoId = Pub_AidBasicInformation.AidBasicInfoId
  19. INNER  JOIN(select ConsultId,dbo.f_GetClinical(ConsultId) as Clinical
  20. from Pub_Consult1) Clinicaltb on Clinicaltb.ConsultId=Pub_Consult1.ConsultId
  21. left join (select distinct ApplicationId, sum(TraniningNumber) as num from dbo.Review_Aid_UseTraining_Record
  22. where  AidReferralId is null
  23. group by  ApplicationId) tab
  24. on tab.ApplicationId=Pub_Consult1.ApplicationId
  25. left JOIN cte3 on cte3.ConsultId=Pub_Consult1.ConsultId
  26. LEFT OUTER JOIN Pub_User ON Pub_Application.ReviewUserId = Pub_User.UserId
  27. where Pub_Consult1.Directory = 0
  28. order by Pub_Application.CreateOn desc

这样基本上就完事了,在没有建立索引的情况下需要8秒钟,比没索引用函数还是快了27秒。

 

把索引放进去,就只需1.6秒了,比建立索引用函数而不用子查询和sql for xml path快了1.9秒。

 

查询里面还有个地方用了函数,估计再优化下还能提高执行效率,因为时间有限再加上篇幅有点长了,在这里就不多讲了。

最后做个总结吧,查询优化不外乎以下这几种办法:

1:增加索引或重建索引。通常在外键,连接字段,排序字段,过滤查询的字段建立索引,也可通过数据库引擎优化顾问提供的信息去建索引。有时候当你创建索引时,会发现查询还是按照索引扫描或聚集索引扫描的方式去执行,而没有去索引查找,这时很可能是你的查询字段和where条件字段没有全部包含在索引字段当中,解决这个问题的办法就是多建立索引,或者在创建索引时Include相应的字段,让索引字段覆盖你的查询字段和where条件字段。

2:调整查询语句,前提要先看懂别人的查询,搞清楚业务逻辑。

3:表分区,大数据量可以考虑。

4:提高服务器硬件配置。

记一次苦逼的SQL查询优化的更多相关文章

  1. 一次苦逼的SQL注入

    0x01: 偶一打点,看到一个可爱的系统-. 1.通过F12 把链接提出来仔细瞅瞅- 2.看见id,果断测注入- 感觉有戏 嗯? 啥数据库连接出错,啥意思??? (其实,这是运维做的混淆..) 3.这 ...

  2. 引用:初探Sql Server 执行计划及Sql查询优化

    原文:引用:初探Sql Server 执行计划及Sql查询优化 初探Sql Server 执行计划及Sql查询优化 收藏 MSSQL优化之————探索MSSQL执行计划 作者:no_mIss 最近总想 ...

  3. 从苦逼到牛逼,详解Linux运维工程师的打怪升级之路

    做运维也快四年多了,就像游戏打怪升级,升级后知识体系和运维体系也相对变化挺大,学习了很多新的知识点. 运维工程师是从一个呆逼进化为苦逼再成长为牛逼的过程,前提在于你要能忍能干能拼,还要具有敏锐的嗅觉感 ...

  4. Pylons安装苦逼之路

    本文介绍一下我在安装pylons的过程中出现的一些错误和解决办法,当然这些都是不完全版. 1.在Serve1(服务器Python版本2.4.3)上面装环境的时候总是出现with_statement有关 ...

  5. PHP项目的“苦逼”经历与思考

    PHP项目的"苦逼"经历与思考 PHP零基础.但因为项目人手不够的原因,被安排到一个用户"定制"项目. 该项目是用PHP生成的统计数据报表. 而用户又有新的3个 ...

  6. SQL查询优化——数据结构设计

    本文部分内容会涉及mysql,可能在其它数据库中并不适用. 本章节仅仅针对数据库结构设计做讨论.查询优化的其它内容待续. 数据库设计及使用是WEB开发程序猿必备的一项基础技能,在大数据量和高并发场景, ...

  7. 无奈而又苦逼的二分版本号回退法定位新引入的bug!!!

    昨天測试人员和开发者都发现, 某新版本号有严重的bug.  群里已经開始嚷嚷了, 但没有谁知道是谁引入的问题.本来呢, 这个问题不应该是由我去定位, 但主管让我帮定位一下, 毕竟时间太紧急, 必须尽快 ...

  8. 30条SQL查询优化原则

    在我们平常的SQL查询中,其实我们有许多应该注意的原则,以来实现SQL查询的优化,本文将为大家介绍30条查询优化原则. 首先应注意的原则 1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 wher ...

  9. 搞IT的技术人员为什么会如此苦逼

    http://www.cnblogs.com/springmvc-hibernate/archive/2012/05/10/2493733.html ————————————————————————— ...

随机推荐

  1. STL(1)

    这一篇因为游戏设计而写的,里面采用了STL,先借用一下,过段时间专项研究. 模板 模板就是一种通用化的类,同一种模板可以创建无数种具有共同特征的容器类型.首先需要指定基础类型,比如int ,char, ...

  2. LDO-XC6216C202MR-G

    XC6216C202MR-G 1.改产品是特瑞士(TOREX)公司电源管理芯片,输入电压可达28V,输出可调23V,最大输出电流150mA.压差最小为300mV.该系列有固定式输出和可调式       ...

  3. java servlet 代码样例 (demo)

    今天又搞了下jsp +servlet 的代码样例,感觉虽然搭了好多次,可是每次还是不记得那些参数,都要去网上搜索,索性自己把这次的简单demo给记录下来,供下次使用的时候直接复制吧. 这个web逻辑 ...

  4. SQL Server 利用锁提示优化Row_number()-程序员需知

    网站中一些老页面仍采用Row_number类似的开窗函数进行分页处理,此时如果遭遇挖坟帖的情形可能就需要漫长的等待且消耗巨大.这里给大家介绍根据Row_number()特性采用特定锁Hint提升查询速 ...

  5. Android 控件架构

    如果说Android上的app是一个有血有肉的人的话,那么人靠衣装马靠鞍,那么控件就是把app装扮的漂漂亮亮的“衣服”.那么安卓的控件到底是如何架构,又是如何渲染的了. 无论是什么控件,在Androi ...

  6. C#下搭建文件格式转换服务器

    文件格式转换,相信很多涉及到office文档在线观看的都会需要,因为浏览器还不能完全支持直接打开office文档,所以很多情况下我们都需要将这些文档转换成flash能够播放的格式,但是另一个问题又来了 ...

  7. Oracle数据创建表空间

    一.直接在服务器端通过sqlplus命令行创建: 如果您用的是Linux系统,那么Oracle用户名为oracle.同时,您是在oracle服务器上操作. 如果是在Windows系统下, 请先点击“开 ...

  8. Linux 进程间通信(一)

    Linux 进程间通信 进程是一个独立的资源分配单位,不同进程之间的资源是相互独立的,没有关联,不能在一个进程中直接访问另一个进程中的资源.但是,进程不是孤立的,不同的进程之间需要信息的交换以及状态的 ...

  9. C++ 重载操作符与转换

    <C++ Primer 4th>读书笔记 重载操作符是具有特殊名称的函数:保留字 operator 后接需定义的操作符号. Sales_item operator+(const Sales ...

  10. 【原】关于使用jieba分词+PyInstaller进行打包时出现的一些问题的解决方法

    错误现象: 最近在做一个小项目,在Python中使用了jieba分词,感觉非常简洁方便.在Python端进行调试的时候没有任何问题,使用PyInstaller打包成exe文件后,就会报错: 错误原因分 ...