记一次苦逼的Sql查询优化

原文:记一次苦逼的Sql查询优化

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

先把查询贴上:

       select Pub_AidBasicInformation.AidBasicInfoId,

       Pub_AidBasicInformation.UserName,

       Pub_AidBasicInformation.District,

       Pub_AidBasicInformation.Street,

       Pub_AidBasicInformation.Community,

       Pub_AidBasicInformation.DisCard,

       Pub_Application.CreateOn AS AppCreateOn,

       Pub_User.UserName as DepartmentUserName, 

       Pub_Consult1.ConsultId,

       Pub_Consult1.CaseId,

       Clinicaltb.Clinical,AidNametb.AidName,

       Pub_Application.IsUseTraining,

       Pub_Application.ApplicationId,

       tab.num

FROM   Pub_Consult1

INNER JOIN Pub_Application ON Pub_Consult1.ApplicationId = Pub_Application.ApplicationId

INNER JOIN Pub_AidBasicInformation ON Pub_Application.AidBasicInfoId = Pub_AidBasicInformation.AidBasicInfoId                                                           

INNER JOIN(select ConsultId,dbo.f_GetClinical(ConsultId) as Clinical

            from Pub_Consult1) Clinicaltb on Clinicaltb.ConsultId=Pub_Consult1.ConsultId

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

INNER JOIN(select ConsultId,dbo.f_GetAidNamebyConsult1(ConsultId) as AidName  from Pub_Consult1) AidNametb on AidNametb.ConsultId=Pub_Consult1.ConsultId                              

LEFT OUTER JOIN Pub_User ON Pub_Application.ReviewUserId = Pub_User.UserId

     WHERE Pub_Consult1.Directory = 0

     order by Pub_Application.CreateOn desc

执行后有图有真相:

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

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

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

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

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

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

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

CREATE NONCLUSTERED INDEX index1 ON [dbo].[Pub_AidBasicInformation]

(

    [AidBasicInfoId] ASC

)

CREATE NONCLUSTERED INDEX index1 ON [dbo].[Pub_Application]

(

    [ApplicationId] ASC,[ReviewUserId] ASC,[AidBasicInfoId] ASC,[CreateOn] ASC

)

CREATE NONCLUSTERED INDEX index1 ON [dbo].[Pub_Consult1]

(

    [Directory] ASC,[ApplicationId] ASC

)

CREATE NONCLUSTERED INDEX idnex1 ON [dbo].[Review_Aid_UseTraining_Record]

(

    [AidReferralId] ASC,[ApplicationId] ASC

)

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

CREATE NONCLUSTERED INDEX index1 ON dbo.Report_AdapterAssessment_Aid

(

    AdapterAssessmentId ASC, ProductDirAId  ASC

)

CREATE NONCLUSTERED INDEX index1 ON dbo.Report_AdapterAssessment

(

    ConsultId ASC

)

再次执行查询

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

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

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

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

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

select Pub_Consult1.ConsultId,AidName from (select ConsultId,dbo.f_GetAidNamebyConsult1(ConsultId) as AidName

from Pub_Consult1) AidNametb inner join Pub_Consult1

on AidNametb.ConsultId=Pub_Consult1.ConsultId 

得到下图的结果:

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

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

with cte1 as

(

    select A.AdapterAssessmentId,case when B.AidName is null then A .AidName else B.AidName end AidName

    from Report_AdapterAssessment_Aid as A left join Pub_ProductDir as B

    on A.ProductDirAId=B.ProductDirAId

),

 cte2 as

(

    --根据AdapterAssessmentId分组并合并AidName字段值

    select AdapterAssessmentId,(select AidName+‘,‘ from cte1

                              where AdapterAssessmentId= tb.AdapterAssessmentId

                              for xml path(‘‘))as AidName

    from cte1 as tb

    group by AdapterAssessmentId

),

cte3 as

(

    select ConsultId,LEFT(AidName,LEN(AidName)-1) as AidName

    from

    (

       select Pub_Consult1.ConsultId,cte2.AidName from Pub_Consult1,Report_AdapterAssessment,cte2

       where Pub_Consult1.ConsultId=Report_AdapterAssessment.ConsultId

       and Report_AdapterAssessment.AdapterAssessmentId=cte2.AdapterAssessmentId

       and  Report_AdapterAssessment.AssessTuiJian is null

    ) as tb)

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

select distinct  Pub_AidBasicInformation.AidBasicInfoId,

       Pub_AidBasicInformation.UserName,

       Pub_AidBasicInformation.District,

       Pub_AidBasicInformation.Street,

       Pub_AidBasicInformation.Community,

       Pub_AidBasicInformation.DisCard,

       Pub_Application.CreateOn AS AppCreateOn,

       Pub_User.UserName as DepartmentUserName, 

       Pub_Consult1.ConsultId,

       Pub_Consult1.CaseId,

       Clinicaltb.Clinical,

       cte3.AidName,

       Pub_Application.IsUseTraining,

       Pub_Application.ApplicationId,

       tab.num

from   Pub_Consult1

INNER JOIN Pub_Application ON Pub_Consult1.ApplicationId = Pub_Application.ApplicationId

INNER JOIN Pub_AidBasicInformation ON Pub_Application.AidBasicInfoId = Pub_AidBasicInformation.AidBasicInfoId                                                           

INNER  JOIN(select ConsultId,dbo.f_GetClinical(ConsultId) as Clinical

            from Pub_Consult1) Clinicaltb on Clinicaltb.ConsultId=Pub_Consult1.ConsultId

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

left JOIN cte3 on cte3.ConsultId=Pub_Consult1.ConsultId                              

LEFT OUTER JOIN Pub_User ON Pub_Application.ReviewUserId = Pub_User.UserId

           where Pub_Consult1.Directory = 0

order by Pub_Application.CreateOn desc

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

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

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

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

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

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

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

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

时间: 2024-10-16 08:20:03

记一次苦逼的Sql查询优化的相关文章

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

PHP项目的"苦逼"经历与思考 PHP零基础.但因为项目人手不够的原因,被安排到一个用户"定制"项目. 该项目是用PHP生成的统计数据报表. 而用户又有新的3个需求,须要在已有的代码基础上完毕.       一.初识PHP 因为本人之前没有接触过PHP代码project,所以须要花费一点时间过一下PHP的基本的语法.个人感觉和C++非常像.有类的定义.继承和派生.但其又比C++简化非常多,没有C++.C的数据类型的概念.全部数据想用什么直接声明赋值就可以.而且.其字

让程序猿不再苦逼的四大神器

做程序猿「媛」是一个苦逼的活,大周六地早起在技术群里招呼.看到没有啥人响应,说了一句.「预计都没有醒」.然后一位哥们抛过来,「在加班」 ! 做 Web 开发更是一个苦逼的活.不像是做 iOS,搞定client.基本上就万事大吉了. 做 Web 开发不仅仅是要做后端.前端也须要了解和熟悉! 做前后端通吃的 DevOps 全栈project师绝对是最苦逼的活,不但须要做开发,而且还要了解运维.优化.不会运维的project师绝对不是一个好架构师. 但所幸的事.一个优秀的project师虽然非常忙,虽

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

原文:引用:初探Sql Server 执行计划及Sql查询优化 初探Sql Server 执行计划及Sql查询优化 收藏 MSSQL优化之————探索MSSQL执行计划 作者:no_mIss 最近总想整理下对MSSQL的一些理解与感悟,却一直没有心思和时间写,晚上无事便写了一篇探索MSSQL执行计划,本文讲执行计划但不仅限于讲执行计划. 网上的SQL优化的文章实在是很多,说实在的,我也曾经到处找这样的文章,什么不要使用IN了,什么OR了,什么AND了,很多很多,还有很多人拿出仅几S甚至几MS的时

苦逼的工作内容

1.负责企业EIS系统(企业ERP系统叫法)研发测试,需求整理,系统应用,角色权限分配,跨部门协同测试协调工作等,与研发商.设备供应商沟通部署实施事宜,独立撰写部署实施方案: 2.负责企业蓟县新建园区网络架构部署,数据中心机房部署等事宜(包括园区路由器.交换机.服务器.负载均衡,存储等资源的采购.规划.部署与功能分配,同时包括数据中心机房装修,安防,消防,弱电线路等施工事宜),撰写数据中心机房部署实施方案. 部署方案主要涉及:硬件架构.系统架构.应用软件.存储灾备和IP资源分布等,其中,网络方面

【我拼搏的2016】-苦逼运维如何变身为SRE成长经历

提起运维很多人能联想到的字眼就有"苦逼"."辛苦"."加班"."背锅",随着国内互联网大潮的兴起,特别是最近几年互联网行业的火爆,催生了大批运维从业人员.类似于当年网络管理员的职业发展,由于普通人对于该领域专业知识的匮乏和良莠不齐的从业人员素质,拉低了整个社会对于这一职业的认知,和当今的运维职业何其相似. 作为运维大军中的一员,我也是经历过从自己摸索自学到专业培训机构系统化学习,再到逐渐完善知识体系和不断提高眼界认知,过程是极

你还在苦逼地findViewById吗?使用ButterKnife从此轻松定义控件

前段时间笔者在苦逼地撸代码~最后发现有些复杂的界面在写了一屏幕的findviewbyid~~~另一堆setOnXXXListener~有没有方便一点的方法让我们简单点不用每次都定义一次.find一次,强转一次,set一次~~ 后来笔者在收藏夹里找到同事好久曾经发给我的网址(呵呵,果然是一旦增加了收藏夹就再也不会看了)~~打开发现有个叫做butterknife的东东~ 那么接下来我们来看看ButterKnife如何将我们从findviewbyid中挽救出来的. ButterKnife简单介绍 呵呵

一个苦逼运维的2015结束语——写给过去和未来的自己

> new Date() ISODate("2015-12-31T15:10:26.721Z") 敲下回车才突然反应过来,2015年即将要过去..... 回想12年带个妹子和同寝室的基友一起走出北京站,看着外面灰蒙蒙的天空,激动.迷茫等等复杂情绪的交织让我至今都分不清,当时是以一种怎么样的心情来北漂的.说来好笑,虽然在辽宁上的大学,距离北京也就两三个小时的动车,可就是从没去过北京,对这祖国首都的认识也一直停留在脑海中,从未踏足过这块土地. 也许是缘分,也许是一种初生牛犊的盲目,被

linux下苦逼搭建ftp站点二三事

昨天中午,领导突然扔给我一台linux服务器,让我给某个站点目录配置个ftp服务 权限可上传.可下载.令其无法跳转上级目录 我想这简单啊,于是我按照固定的四维,useradd.passwd.修改conf文件.修改权限,最后restart 结果就傻逼了,一直连接不上,报530错误,我更傻逼的做法一直在创建.删除用户 - -|| 在百度苦苦寻求答案的我,无意中看见一片博文关于配置vsftp虚拟用户配置 它的vsftp目录结构和我这台服务器上的一样,包括好多文件像txt.db 这才发现原来使用的这个方

谋哥:App开发者的苦逼不值得怜悯!!

[谋哥每天一干货,第四十篇] 为什么取这个标题呢?因为昨天一些本来"支持"谋哥的人看到谋哥搞收费VIP群,觉得谋哥赚苦逼开发者的钱很不道德,且说谋哥我写的东西都不切实际,全部是一些思想性的东西,毫无实战意义,于是愤然离去. 华为老总任正非说:"我们需要将军,更需要战略家.思想家".有App赚钱实战经验的人都会知道,谋哥我写的文章都是基于实战,我不会告诉你具体某个 App怎么搞(除非你加入VIP群,我指导你),我只会说:"你买本<简约至上>看看.