日期:2014-05-16  浏览次数:20452 次

产品详情页面加载慢(之前约7s-12s)
问题场景:客户反应网站上的产品详情页面打开时,速度很慢。
运行环境:
  数据库服务器 32位SQL Server 2005 开发者版SP2 开启了AWE
  应用服务器 JDK 1.6,Tomcat 6.0
解决步骤:
  1.火狐,Firebug>网络 查看打开该页面后的时间线 确实是数据查询耗时长导致页面加载慢。
  2.懒得拉代码配置测试环境了(这个项目不能实行热部署,改了程序不能直接看到效果,不太爽),直接在客户机器上通过SQL Server Profiler创建跟踪来获取耗时长的查询。跟踪时间只是点开产品详情页面前后大约15s,这段时间内有一条查询的CPU、IO占用都高的离谱,所以很容易就确定了就是这条慢查询导致的页面打开慢。通过报表看,这条查询平均占用CPU时间8s多,已经执行2W多次。。。。。
  3.将查询拷到Management Studio,添加参数值,设置统计io和time开关,开启实际执行计划,执行查询,分析结果。定位到union前后的两段查询条件重复,导致对连接的另一张大表逻辑读达到40W+,于是改写查询条件,时间缩短为0.15s左右。
  4.提交开发的同事修改,问题解决。

后记:
开发的同事修改之后,速度并无明显改善,于是乎,重来步骤2,发现当product_status字段(varchar类型)的值为'50'时,查询的代价要明显高于传入值为50的情况。这个很费解。。
上执行计划(计划中不同的部分)
Sort(TOP 1, ORDER BY:([t].[dbgndate] ASC))
 |--Filter(WHERE:(CONVERT_IMPLICIT(int,[db].[dbo].[product_info].[product_status] as [t].[product_status],0)=(50) AND CONVERT(varchar(100),[db].[dbo].[product_info].[dbgndate] as [t].[dbgndate],23)>=CONVERT(varchar(100),getdate(),23) AND ([db].[dbo].[product_info].[product_issue] as [t].[product_issue]='2' OR [db].[dbo].[product_info].[product_issue] as [t].[product_issue]='3')))
 |--Nested Loops(Inner Join, OUTER REFERENCES:([t].[uid]))
 |--Index Seek(OBJECT:([db].[dbo].[product_info].[ix_t_product_ulineid] AS [t]), 
 	SEEK:(
	  [t].[ulineid]
	  =[db].[dbo].[t_line].[uid] as [tl].[uid]
	) ORDERED FORWARD)

对product_info逻辑读5W次

Top(TOP EXPRESSION:((1)))
 |--Filter(WHERE:([db].[dbo].[product_info].[product_issue] as [t].[product_issue]='2' OR [db].[dbo].[product_info].[product_issue] as [t].[product_issue]='3'))
 |--Nested Loops(Inner Join, OUTER REFERENCES:([t].[uid]))
 |--Index Seek(OBJECT:([db].[dbo].[product_info].[ix_product_info_status_dbgndate] AS [t]), 
 	SEEK:(
	  [t].[cstatus]='50'), 
	  WHERE:([db].[dbo].[product_info].[ulineid] as [t].[ulineid]
	 	=[db].[dbo].[t_line].[uid] as [tl].[uid] 
	  AND CONVERT(varchar(100),[db].[dbo].[product_info].[dbgndate] as [t].[dbgndate],23)
	  >=CONVERT(varchar(100),getdate(),23)
	) ORDERED FORWARD)

对product_info逻辑读124W次

详细的不说了(现在我也说不清,只能感觉到要调整索引),上解决方案:
新增索引
create index ix_product_info_ulineid_status_dbgndate  on product_info(ulineid, product_status, dbgndate) include(product_issue, uid);


上新增索引后的执行计划:
   |--Nested Loops(Inner Join, OUTER REFERENCES:([tl].[uid], [Expr1038]) WITH ORDERED PREFETCH)
        |--Sort(ORDER BY:([tl].[dupdate] DESC))
        |    |--Index Seek(OBJECT:([db].[dbo].[t_line].[ix_t_line_cstatus_cissue] AS [tl]), SEEK:([tl].[cstatus]='50' AND [tl].[cissue]='2' OR [tl].[cstatus]='50' AND [tl].[cissue]='3') ORDERED FORWARD)
        |--Top(TOP EXPRESSION:((1)))
             |--Index Seek(OBJECT:([db].[dbo].[product_info].[ix_t_product_info_ulineid_cstatus_dbgndate] AS [t]), SEEK:([t].[ulineid]=[db].[dbo].[t_line].[uid] as [tl].[uid] AND [t].[product_status]='50'),  WHERE:(CONVERT(varchar(100),[db].[dbo].[product_info].[dbgndate] as [t].[dbgndate],23)>=CONVERT(varchar(100),getdate(),23) AND ([db].[dbo].[product_info].[product_issue] as [t].[product_issue]='2' OR [db].[dbo].[product_info].[product_issue] as [t].[product_issue]='3')) ORDERED FORWARD)
                 |         |    |    |--Clustered Index Seek(OBJECT:([db].[dbo].[product_info].[pk_y_product_info_uid] AS [tt]), SEEK:([tt].[uid]=[db].[dbo].[product_info].[uid] as [t].[uid]) ORDERED FORWARD)

对product_info逻辑读降为7K,CPU占用时间约为100ms
对比最初的开销,性能提升非常大,刚开始看执行计划,脑袋晕了,优化就暂时先做到这一步。