【发布时间】:2016-08-02 07:35:12
【问题描述】:
我的任务是提高由 SSRS 前端和存储过程调用的报告存储过程的性能(这是我的第一个实际性能调整任务)目前在最大数量的数据上运行大约需要 30 秒(基于从报告前端设置的过滤器)。
此存储过程细分了 19 个在其中执行的查询,其中大部分将数据从基表内部的初始(旧)格式转换为有意义的数据集,以显示给业务方。
我创建了一个基于几个 DMV 的查询,以便找出存储过程中最消耗资源的查询(下面的小 sn-p),我发现一个查询大约需要 10 秒,平均而言,完成。
select
object_name(st.objectid) [Procedure Name]
, dense_rank() over (partition by st.objectid order by qs.last_elapsed_time desc) [rank-execution time]
, dense_rank() over (partition by st.objectid order by qs.last_logical_reads desc) [rank-logical reads]
, dense_rank() over (partition by st.objectid order by qs.last_worker_time desc) [rank-worker (CPU) time]
, dense_rank() over (partition by st.objectid order by qs.last_logical_writes desc) [rank-logical write]
...
from sys.dm_exec_query_stats as qs
cross apply sys.dm_exec_sql_text (qs.sql_handle) as st
cross apply sys.dm_exec_text_query_plan (qs.plan_handle, qs.statement_start_offset, qs.statement_end_offset) as qp
where st.objectid in ( object_id('SuperDooperReportingProcedure') )
, [rank-execution time]
, [rank-logical reads]
, [rank-worker (CPU) time]
, [rank-logical write] desc
现在,这个查询有点奇怪,因为执行计划显示大部分工作(~80%)是在将数据插入本地临时表时完成的,而不是在查询其他表时完成的从中获取源数据,然后对其进行操作。 (以下截图来自 SQL Sentry Plan Explorer)
此外,就行估计而言,执行计划对此的估计还有很长的路要走,因为只有 4218 行插入到本地临时表中,而不是执行计划认为它移动的约 248k 行到本地临时表中。所以,正因为如此,我在考虑“统计”,但即使 80% 的工作是实际插入表中,这些仍然重要吗?
我的第一个建议是重写整个过程和存储过程,以便不将数据的移动和转换包含在报告存储过程中,并且每晚将数据转换为一些持久化表(真实- 不需要时间数据,只需要前一天结束的相关数据)。但是业务方面不想投入时间和资源来重新设计它,而是“建议”我进行性能调整,以寻找可以添加的位置和索引以加快速度。
我不认为向基表添加索引会提高报表的性能,因为运行查询所需的大部分时间是将数据保存到临时表中(据我所知,它会命中 tempdb,这意味着它们将被写入磁盘 -> 由于 I/O 延迟而增加了时间)。
但是,即便如此,正如我所提到的,这是我的第一个性能调优任务,我在过去几天尝试尽可能多地阅读与此相关的内容,这些是我目前的结论,但我想向更广泛的受众寻求建议,并希望对我可以做些什么来改进这个程序有更多的见解和理解。
如果可以回答一些明确的问题,我将不胜感激:
- 我上面所说的(根据我对数据库的理解或我的假设)有什么不正确的地方吗?
- 向临时表添加索引是否真的会增加执行时间,因为该表(及其相关索引在每次执行时都在重建)?
- 在这种情况下是否可以在无需重新编写过程/查询并且仅通过索引或其他调优方法完成的情况下进行其他操作? (我读过一些文章标题,您也可以“调整 tempdb”,但我还没有深入了解这些细节)。
非常感谢任何帮助,如果您需要更多详细信息,我很乐意发布。
更新(2016 年 8 月 2 日):
有问题的查询(部分)如下。缺少的是GROUP BY 部分中更多的聚合列及其对应的行:
select
b.ProgramName
,b.Region
,case when b.AM IS null and b.ProgramName IS not null
then 'Unassigned'
else b.AM
end as AM
,rtrim(ltrim(b.Store)) Store
,trd.Store_ID
,b.appliesToPeriod
,isnull(trd.countLeadActual,0) as Actual
,isnull(sum(case when b.budgetType = 0 and b.budgetMonth between @start_date and @end_date then b.budgetValue else 0 end),0) as Budget
,isnull(sum(case when b.budgetType = 0 and b.budgetMonth between @start_date and @end_date and (trd.considerMe = -1 or b.StoreID < 0) then b.budgetValue else 0 end),0) as CleanBudget
...
into #SalvesVsBudgets
from #StoresBudgets b
left join #temp_report_data trd on trd.store_ID = b.StoreID and trd.newSourceID = b.ProgramID
where (b.StoreDivision is not null or (b.StoreDivision is null and b.ProgramName = 'NewProgram'))
group by
b.ProgramName
,b.Region
,case when b.AM IS null and b.ProgramName IS not null
then 'Unassigned'
else b.AM
end
,rtrim(ltrim(b.Store))
,trd.Store_ID
,b.appliesToPeriod
,isnull(trd.countLeadActual,0)
我不确定这是否真的有用,但由于@kcung 要求,我添加了信息。
另外,回答他的一些问题:
- 临时表上没有索引
- 内存大小:32 GB
更新(2016 年 8 月 3 日):
我尝试了@kcung 的建议,将CASE 语句从聚合生成查询中移出,不幸的是,总体而言,过程时间并没有明显改善,因为它仍然在±0.25 到±1.0 秒的范围内波动(是的,比存储过程的原始版本更低和更高的时间 - 但我猜这是由于我的机器上的可变工作负载)。
同一个查询的执行计划现在是:
【问题讨论】:
标签: sql-server performance stored-procedures sql-server-2008-r2