改进报告存储过程的执行时间 - 调整临时表?

Rad*_*hiu 5 sql-server performance stored-procedures sql-server-2008-r2

我的任务是改进SSRS前端调用的报告存储过程的性能(这是我的第一次真实性能调整),存储过程目前大约需要30秒才能运行数据量(基于从报告前端设置的过滤器).

该存储过程具有在其中执行的19个查询的细分,其中大多数将数据从初始(遗留)格式从基表内转换为有意义的数据集以显示给业务侧.

我已经创建了一个基于几个DMV的查询,以便找出哪些是存储过程中最耗费资源的查询(下面的小片段),我发现一个查询平均需要大约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
Run Code Online (Sandbox Code Playgroud)

现在,这个查询有点奇怪,因为执行计划显示在将数据插入本地临时表时完成大部分工作(~80%),而不是在查询其他表时获取源数据然后进行操作.(下面的截图来自SQL Sentry Plan Explorer)

在此输入图像描述

此外,就行估计而言,执行计划还没有对此进行估计,因为在本地临时表中只插入了4218行而不是执行计划认为它进入本地的~248k行.临时表.所以,由于这个原因,我正在考虑"统计",但是如果~80%的工作是实际插入表中,那么这些甚至是重要的吗?

我的第一个建议之一是重写整个过程和存储过程,以便不包括将数据移动和转换到报告存储过程中,并且每晚将数据转换到一些持久表中(实时数据)不是必需的,只有相关数据直到前一天结束).但是业务方不希望投入时间和资源来重新设计它,而是"建议"我会在找到可以添加的位置和索引的意义上进行性能调整以加快速度.

我不认为向基表添加索引会提高报告的性能,因为运行查询所需的大部分时间都是将数据保存到临时表中(据我所知,它会命中tempdb,这意味着它们将被写入磁盘 - >由于I/O延迟而增加的时间).

但是,即便如此,正如我所提到的,这是我的第一次性能调整任务,并且我在过去几天内尽可能多地阅读与此相关的内容,这些是我目前的结论,但我想向更广泛的受众寻求建议,并希望获得更多的见解和理解我可以做些什么来改进这个程序.

作为一些明确的问题,如果可以回答,我会很感激:

  • 我上面所说的内容(根据我对db或我的假设的理解)是否有任何不正确之处?
  • 向临时表添加索引是否真的会增加执行时间,因为表(及其关联的索引在每次执行时都要重建)?
  • 在这种情况下还有什么可以做的,而不必重新编写过程/查询,只能通过索引或其他调整方法完成吗?(我已经阅读了一些文章标题,你也可以"调整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)
Run Code Online (Sandbox Code Playgroud)

我不确定这是否真的有用,但是因为@kcung要求它,我添加了信息.

另外,回答他的一些问题:

  • 临时表没有索引
  • RAM大小:32 GB

更新(2016年8月3日):

我已经尝试了@kcung的建议来CASE从聚合生成查询中移动语句,不幸的是,整体而言,程序时间没有明显改善,因为它仍然在±0.25到±1.0秒的范围内波动(是的,都是低的和比原始版本的存储过程更高的时间 - 但我猜这是由于我的机器上的工作负载变化.

现在,同一查询的执行计划,但修改为删除CASE条件,只留下SUM聚合,:

在此输入图像描述

lib*_*ata 0

我有机会看到查询吗?以及两个表上的索引?你的公羊有多大?每个表中的行有多大(大约)?您可以更新两个表的统计信息并重新发送查询计划程序吗?

回答你的问题:

  1. 除了添加索引的部分之外,您基本上是对的。添加索引将有助于查询进行查找。它还将使查询规划器有机会考虑嵌套循环连接计划而不是哈希连接计划。不幸的是,在我的问题得到解答之前我无法回答更多。
  2. 您不需要向临时表添加索引。向此临时(或任何插入目标表)表添加索引将增加写入时间,因为插入需要更新该索引。想象一下,索引是您的表的副本,信息较少,它位于您的表的顶部,并且需要与您的表同步。每次写入(插入、更新、删除)都需要更新这个索引。
  3. 看看两个表的总行数,这个查询的运行速度应该比 10 秒快得多,除非你有一台柠檬电脑,那么情况就不同了。

编辑: 只想指出第二点,我没有意识到你的源表也是临时表。连接的每个会话结束后,临时表将被销毁。给临时表添加索引意味着每次创建这个临时表都会增加额外的时间来创建这个索引。

编辑: 抱歉,我现在正在使用电话。我只是要简短一点。所以本质上有两件事:

  • 在临时表创建时添加主键,以便您一次性完成。不要费心添加非聚集索引或任何覆盖索引,您最终会花费更多时间来创建这些索引。

  • 查看您的查询,所有 case when 语句,而不是在此查询中执行它,为什么不将它们添加为表中的另一列。本质上,您希望在进行分组时避免动态计算。您可以将 sum() 保留在查询中,因为它是聚合查询,但请尝试尽可能减少运行时间计算。

样本 :

case when b.AM IS null and b.ProgramName IS not null 
    then 'Unassigned' 
    else b.AM 
end as AM
Run Code Online (Sandbox Code Playgroud)

您可以在创建表b时创建一个名为AM的列。还有那些 rtrim 和 ltrim。请删除这些并将其粘贴到表创建时间。:)