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

时间:2016-08-02 07:35:12

标签: 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

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

enter image description here

此外,就行估计而言,执行计划还没有对此进行估计,因为在本地临时表中只插入了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)

我不确定这是否真的有用,但由于@kcung要求,我添加了信息。

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

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

更新(2016年8月3日):

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

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

enter image description here

2 个答案:

答案 0 :(得分:0)

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

回答你的问题:

  1. 除了添加索引之外,你大部分都是对的。添加索引将有助于查询进行查找。它还将使查询计划程序有机会考虑嵌套循环连接计划而不是散列连接计划。不幸的是,在我的问题得到解答之前,我无法回答。
  2. 您不需要为临时表添加索引。向此临时(或任何插入目标表)表添加索引将增加写入时间,因为插入将需要更新该索引。想象一下索引作为表的副本,信息较少,它位于表的顶部,需要与您的表同步。每次写入(插入,更新,删除)都需要更新此索引。
  3. 查看两个表的总行数,这个查询的运行速度应该比10s快,除非你有一个柠檬PC,那么这是一个不同的故事。
  4. 修改 只是想指出第2点,我没有意识到你的源表也是临时表。连接的每个会话结束后,临时表将被销毁。向临时表添加索引意味着每次创建此临时表时都会增加额外的时间来创建此索引。

    修改 对不起,我现在正在使用手机。我只是要做空。 所以基本上有两件事:

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

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

    示例:

    case when b.AM IS null and b.ProgramName IS not null 
        then 'Unassigned' 
        else b.AM 
    end as AM
    

    创建表b时,可以创建名为AM的列。 那些rtrim和ltrim。请删除它们并将其粘贴在表创建时间。 :)

答案 1 :(得分:0)

  1. 向临时表添加索引肯定会改善读取调用,但会减慢对临时表的写入调用。
  2. 在这里,正如您所提到的,在过程中执行了19个查询,因此只分析一个带有执行计划的查询将不会更有帮助。
  3. 如果可能,添加更多内容,仅执行此查询&amp;检查需要多长时间(受影响的行)。
  4. 您可以尝试其他方法,不确定在您的情况下是否可行,尝试使用表变量而不是临时表。这是因为,在临时表上使用表变量具有额外的优点,例如,过程是预编译的,不维护事务日志。 &安培;更多,你不需要写drop table。