记一次苦逼的SQL查询优化

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

先把查询贴上: 
javascript

Sql代码  
  1. select Pub_AidBasicInformation.AidBasicInfoId,  
  2.    
  3.        Pub_AidBasicInformation.UserName,  
  4.    
  5.        Pub_AidBasicInformation.District,  
  6.    
  7.        Pub_AidBasicInformation.Street,  
  8.    
  9.        Pub_AidBasicInformation.Community,  
  10.    
  11.        Pub_AidBasicInformation.DisCard,  
  12.    
  13.        Pub_Application.CreateOn AS AppCreateOn,  
  14.    
  15.        Pub_User.UserName as DepartmentUserName,   
  16.    
  17.        Pub_Consult1.ConsultId,  
  18.    
  19.        Pub_Consult1.CaseId,  
  20.    
  21.        Clinicaltb.Clinical,AidNametb.AidName,  
  22.    
  23.        Pub_Application.IsUseTraining,  
  24.    
  25.        Pub_Application.ApplicationId,  
  26.    
  27.        tab.num  
  28.    
  29. FROM   Pub_Consult1  
  30.    
  31. INNER JOIN Pub_Application ON Pub_Consult1.ApplicationId = Pub_Application.ApplicationId  
  32.    
  33. INNER JOIN Pub_AidBasicInformation ON Pub_Application.AidBasicInfoId = Pub_AidBasicInformation.AidBasicInfoId                                                             
  34.    
  35. INNER JOIN(select ConsultId,dbo.f_GetClinical(ConsultId) as Clinical  
  36.    
  37.             from Pub_Consult1) Clinicaltb on Clinicaltb.ConsultId=Pub_Consult1.ConsultId  
  38.    
  39. 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  
  40.    
  41. INNER JOIN(select ConsultId,dbo.f_GetAidNamebyConsult1(ConsultId) as AidName  from Pub_Consult1) AidNametb on AidNametb.ConsultId=Pub_Consult1.ConsultId                                
  42.    
  43. LEFT OUTER JOIN Pub_User ON Pub_Application.ReviewUserId = Pub_User.UserId  
  44.    
  45.      WHERE Pub_Consult1.Directory = 0  
  46.    
  47.      order by Pub_Application.CreateOn desc  


执行后有图有真相: 
java

 


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

 


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

 


 


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

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

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

 


最后生成的建议报告以下: 
服务器

 


在这里能够单击查看一些建议,分区,建立索引,根据提示建立了以下索引: 
app

Sql代码  
  1. CREATE NONCLUSTERED INDEX index1 ON [dbo].[Pub_AidBasicInformation]  
  2.    
  3. (  
  4.    
  5.     [AidBasicInfoId] ASC  
  6.    
  7. )  
  8.    
  9.    
  10. CREATE NONCLUSTERED INDEX index1 ON [dbo].[Pub_Application]  
  11.    
  12. (  
  13.    
  14.     [ApplicationId] ASC,[ReviewUserId] ASC,[AidBasicInfoId] ASC,[CreateOn] ASC  
  15.    
  16. )  
  17.    
  18. CREATE NONCLUSTERED INDEX index1 ON [dbo].[Pub_Consult1]  
  19.    
  20. (  
  21.    
  22.     [Directory] ASC,[ApplicationId] ASC  
  23.    
  24. )  
  25.    
  26.     
  27.    
  28. CREATE NONCLUSTERED INDEX idnex1 ON [dbo].[Review_Aid_UseTraining_Record]  
  29.    
  30. (  
  31.    
  32.     [AidReferralId] ASC,[ApplicationId] ASC  
  33.    
  34. )  


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

Sql代码  
  1. CREATE NONCLUSTERED INDEX index1 ON dbo.Report_AdapterAssessment_Aid  
  2.    
  3. (  
  4.    
  5.     AdapterAssessmentId ASC, ProductDirAId  ASC  
  6.    
  7. )  
  8.    
  9. CREATE NONCLUSTERED INDEX index1 ON dbo.Report_AdapterAssessment  
  10.    
  11. (  
  12.    
  13.     ConsultId ASC  
  14.    
  15. )  


再次执行查询 
大数据

 


好了,只需3.5秒,差很少提升10倍速度,看来此次是凑效了哈。 

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

 


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

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

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

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


获得下图的结果: 

 


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

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

Sql代码  
  1. with cte1 as  
  2.    
  3. (  
  4.    
  5.     select A.AdapterAssessmentId,case when B.AidName is null then A .AidName else B.AidName end AidName  
  6.    
  7.     from Report_AdapterAssessment_Aid as A left join Pub_ProductDir as B  
  8.    
  9.     on A.ProductDirAId=B.ProductDirAId  
  10.    
  11. ),  
  12.    
  13.  cte2 as  
  14.    
  15. (  
  16.    
  17.       
  18. --根据AdapterAssessmentId分组并合并AidName字段值  
  19.    
  20.     select AdapterAssessmentId,(select AidName+',' from cte1  
  21.    
  22.                               where AdapterAssessmentId= tb.AdapterAssessmentId  
  23.    
  24.                               for xml path(''))as AidName  
  25.    
  26.     from cte1 as tb  
  27.    
  28.     group by AdapterAssessmentId  
  29.    
  30. ),  
  31.    
  32. cte3 as  
  33.    
  34. (  
  35.    
  36.     select ConsultId,LEFT(AidName,LEN(AidName)-1) as AidName  
  37.    
  38.     from  
  39.    
  40.     (  
  41.    
  42.        select Pub_Consult1.ConsultId,cte2.AidName from Pub_Consult1,Report_AdapterAssessment,cte2  
  43.    
  44.        where Pub_Consult1.ConsultId=Report_AdapterAssessment.ConsultId  
  45.    
  46.        and Report_AdapterAssessment.AdapterAssessmentId=cte2.AdapterAssessmentId  
  47.    
  48.        and  Report_AdapterAssessment.AssessTuiJian is null  
  49.    
  50.     ) as tb)  


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

Sql代码  
  1. select distinct  Pub_AidBasicInformation.AidBasicInfoId,  
  2.    
  3.        Pub_AidBasicInformation.UserName,  
  4.    
  5.        Pub_AidBasicInformation.District,  
  6.    
  7.        Pub_AidBasicInformation.Street,  
  8.    
  9.        Pub_AidBasicInformation.Community,  
  10.    
  11.        Pub_AidBasicInformation.DisCard,  
  12.    
  13.        Pub_Application.CreateOn AS AppCreateOn,  
  14.    
  15.        Pub_User.UserName as DepartmentUserName,   
  16.    
  17.        Pub_Consult1.ConsultId,  
  18.    
  19.        Pub_Consult1.CaseId,  
  20.    
  21.        Clinicaltb.Clinical,  
  22.    
  23.        cte3.AidName,  
  24.    
  25.        Pub_Application.IsUseTraining,  
  26.    
  27.        Pub_Application.ApplicationId,  
  28.    
  29.        tab.num  
  30.    
  31. from   Pub_Consult1  
  32.    
  33. INNER JOIN Pub_Application ON Pub_Consult1.ApplicationId = Pub_Application.ApplicationId  
  34.    
  35. INNER JOIN Pub_AidBasicInformation ON Pub_Application.AidBasicInfoId = Pub_AidBasicInformation.AidBasicInfoId                                                             
  36.    
  37. INNER  JOIN(select ConsultId,dbo.f_GetClinical(ConsultId) as Clinical  
  38.    
  39.             from Pub_Consult1) Clinicaltb on Clinicaltb.ConsultId=Pub_Consult1.ConsultId  
  40.    
  41. left join (select distinct ApplicationId, sum(TraniningNumber) as num from dbo.Review_Aid_UseTraining_Record  
  42.    
  43.            where  AidReferralId is null  
  44.    
  45.            group by  ApplicationId) tab  
  46.    
  47.            on tab.ApplicationId=Pub_Consult1.ApplicationId  
  48.    
  49. left JOIN cte3 on cte3.ConsultId=Pub_Consult1.ConsultId                                
  50.    
  51. LEFT OUTER JOIN Pub_User ON Pub_Application.ReviewUserId = Pub_User.UserId  
  52.    
  53.            where Pub_Consult1.Directory = 0  
  54.    
  55. order by Pub_Application.CreateOn desc  


这样基本上就完事了,在没有创建索引的状况下须要8秒钟,比没索引用函数仍是快了27秒。 

 


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

 


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

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

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

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

3:表分区,大数据量能够考虑。 4:提升服务器硬件配置。

相关文章
相关标签/搜索