如何提高此存储过程的性能

时间:2015-10-28 15:42:03

标签: sql sql-server stored-procedures query-optimization

所以我刚刚开始使用SQL Server大约两个月了(我还是一个新手),我必须提高存储过程的性能。它在3秒内运行,但由于某种原因,客户对结果不满意。 我试图训练自己阅读执行计划并找出问题所在。

获得SQL Sentry Plan Explorer之后,我发现该过程中只有一部分导致了问题,那部分是这样的:

With myAccount as
(
    select 
        ROW_NUMBER() over(order by Account) as Row_ID,
        ID, Account, 
        replace(Name, '*', '#_') Name, 
        Totaling 
    from 
        Account
    where 
        Company_ID = @company_id and Balance = 0)
,myR1C1 (ID, R1C1) as
(
    select 
        t1.ID, 
        case when t4.Account = t6.Account 
             then 'R[' + convert(nvarchar(10), t4.row_id - t1.Row_ID) + ']C'
             else 'R[' + convert(nvarchar(10), t4.Row_ID - t1.Row_ID) + ']C:R[' + convert(nvarchar(10), t6.Row_ID - t1.Row_ID) + ']C'  
        end R1C1 
        --t1.*,t2.*,t4.*,t6.*, t4.id-t1.id,t6.id-t1.id 
    from 
        myAccount t1
    cross apply 
        dbo.abx_sysSplitTwo(Totaling,'|') t2
    cross apply 
        (select top 1 
             Row_ID, ID, account 
         from myAccount t3 
         where t3.account >= t2.VFr 
           and t3.account <= t2.vto 
           and t1.account <> t3.account 
         order by t3.account) t4
    cross apply 
        (select top 1 
             Row_ID, ID, account  
         from myAccount t5 
         where t5.account >= t2.VFr 
           and t5.account <= t2.vto 
           and t1.account <> t5.account order by t5.account) t6
)
, myAccount2 as
(
    Select 
        t1.*, t2.R1C1 
    from myAccount t1 
    left join 
       (select 
            ID, STUFF((select',' + R1C1 
                       from myR1C1
                       where ID = a.id 
                       for xml path ('')), 1, 1, '') as R1C1
        from 
            myR1C1 as a
        group by 
            id) t2 on t1.ID = t2.id
--  order by row_ID

-- Data1
select tv.id [<dang n="BudData" u="0" o="1" fmt="1" fn="ID"/>],tv.account, tv.Name
 ,case when len(tv.R1C1)>0 then '=subtotal(9,' + tv.r1c1 + ')' else convert(nvarchar,sum(case When tp.[Date] between dateadd(yy,-1,@FromDate) and dateadd(d,-1,@FromDate) then isnull(Bud,0) else 0 end)) end A
 ,case when len(tv.R1C1)>0 then '=subtotal(9,' + tv.r1c1 + ')' else convert(nvarchar,Sum(case When tp.[Date] between dateadd(yy,-1,@FromDate) and dateadd(d,-1,@FromDate) then isnull(tp.Actual,0) else 0 end)) end B
 ,case when len(tv.R1C1)>0 then '=subtotal(9,' + tv.r1c1 + ')' else convert(nvarchar,sum(case When tp.[Date] between dateadd(yy,0,@FromDate) and dateadd(d,-1,dateadd(yy,1,@FromDate)) then isnull(Bud,0) else 0 end)) end C
 ,case when len(tv.R1C1)>0 then '=subtotal(9,' + tv.r1c1 + ')' else convert(nvarchar,sum(case When tp.[Date] between dateadd(yy,0,@FromDate) and dateadd(yy,0,@YTD) then isnull(Bud,0) else 0 end)) end D
 ,case when len(tv.R1C1)>0 then '=subtotal(9,' + tv.r1c1 + ')' else convert(nvarchar,Sum(case When tp.[Date] between dateadd(yy,0,@FromDate) and dateadd(yy,0,@YTD) then isnull(tp.Actual,0) else 0 end)) end E 
 ,case when len(tv.R1C1)>0 then '=subtotal(9,' + tv.r1c1 + ')' else convert(nvarchar,Sum(case When tp.[Date] between dateadd(yy,0,@FromDate) and dateadd(yy,0,@YTD) then isnull(tp.Actual,0) else 0 end)-sum(case When tp.[Date] between dateadd(yy,0,@FromDate) and dateadd(yy,0,@YTD) then isnull(tp.Bud,0) else 0 end)) end F
 ,case when len(tv.R1C1)>0 then '' else convert(nvarchar,case when sum(case When tp.[Date] between dateadd(yy,0,@FromDate) and dateadd(yy,0,@YTD) then isnull(tp.Bud,0) else 0 end) between -1 and 1 then 0 else (Sum(case When tp [Date] between dateadd(yy,0,@FromDate) and dateadd(yy,0,@YTD) then isnull(tp.Actual,0) else 0 end)/sum(case When tp.[Date] between dateadd(yy,0,@FromDate) and dateadd(yy,0,@YTD) then isnull(tp.Bud,0) else 0 end)*100) end) end G
 ,case when len(tv.R1C1)>0 then '=subtotal(9,' + tv.r1c1 + ')' else convert(nvarchar,Sum(case When tp.[Date] between dateadd(yy,0,@FromDate) and dateadd(yy,0,@YTD) then isnull(Rev,0)  -- Rev er her rettet fra REv til Faktisk else 0 end +case When tp.[Date] between dateadd(d,1,@YTD) and dateadd(d,-1,dateadd(yy,1,@FromDate)) then isnull(tp.Rev,0) else 0 end)) end H

我知道它看起来很大,也许以这种方式展示它是愚蠢的,但经过两周的尝试而没有那么多的成功,人们开始推动我并抱怨我在这方面花了很多时间..老实说,我真的不知道该怎么做。

到目前为止,我已经使用SQL Profiler工具来获取带有工作负载的文件,并在Tunning Advisor工具中使用它来查看它所做的建议。 我得到了一些重新编写,据说建立了一些统计数据和一些索引,我做了,但差别几乎不明显。 我认为另一件事是一个好主意,在计算时(在使用Profiler和Tunning Advisor之前),存储过程的这部分应该返回的估计行数是1600,而实际行数是536。据我所知,这不是一件好事。 现在,奇怪的部分来了,在使用来自Tunning Advisor的建议后,而不是降低估计的nr行,它增加到2800,但速度几乎相同2-3秒。 我知道还有很多事情可以做,但我现在不具备了解它们的知识或时间,所以如果有人能指出我正确的方向,那将是伟大的。 如果我还能提供其他任何东西以便深入了解这一点,我会这样做,所以请你问一下。 我差点忘了,预期的结果可能是1秒或更短......因为它只有536行,而且我的客户看到查询在超过20,000行上执行得更快

2 个答案:

答案 0 :(得分:0)

首先,我邀请您阅读http://importblogkit.com/2015/05/how-do-you-eat-an-elephant/

您需要将查询拆分为较小的部分以确定问题的位置。你已经发现你的查询的哪一部分是较慢的部分,现在你应该继续挖掘。

您有4个查询myAccountmyR1C1myAccount2和最终选择。只需逐个运行,每个ANALYZE/EXPLAIN运行,看看有多长,多长时间以及使用什么索引。

检查行数,尝试添加新索引或复合索引以提高速度。

现在,如果你发现了什么东西,你看起来很奇怪,请阅读How to create a Minimal, Complete, and Verifiable example.,然后用该部分创建一个新问题。

答案 1 :(得分:0)

由于您已在SQL Sentry Plan Explorer中运行,您是否可以尝试仅运行此查询,然后复制Plan XML数据? (您也可以保护.queryanalysis文件,但它可能包含有关您的服务器名称等的信息,您可能不会将其放在网上)。只需将其复制粘贴到pastebin或将其压缩到例如Dropbox并在此处共享URL。这样我们就能更好地了解真实情况......

乍一看,你似乎已经做了许多可能在机器上很重的ORDER BY,但我主要担心的是SELECT ROW_NUMBER() OVER (order by Account) as Row_ID, ID, Account, Name Totaling INTO #myAccount FROM Account WHERE Company_ID = @company_id AND Balance = 0 CREATE UNIQUE CLUSTERED INDEX uq0 ON #myAccount (Row_ID) WITH (FILLFACTOR = 100) CREATE INDEX idx1 ON #myAccount (Account) WITH (FILLFACTOR = 100) -- used later on for t4 and t6 -- split the Totaling, I'm assuming multiple records can be returned by abx_sysSplitTwo SELECT DISTINCT Totaling INTO #pre_split FROM #myAccount SELECT Totaling, vfr, vto INTO #split FROM #pre_split CROSS APPLY dbo.abx_sysSplitTwo(Totaling,'|') t2 CREATE CLUSTERED INDEX idx0 ON #split (Totaling) WITH (FILLFACTOR = 100) -- apply splitted values select t1.ID, case when t4.Account = t6.Account then 'R[' + convert(nvarchar(10), t4.Row_ID - t1.Row_ID) + ']C' else 'R[' + convert(nvarchar(10), t4.Row_ID - t1.Row_ID) + ']C:R[' + convert(nvarchar(10), t6.Row_ID - t1.Row_ID) + ']C' end R1C1 INTO #myR1C1 FROM #myAccount t1 JOIN #split t2 ON t2.Totaling = t1.Totaling CROSS APPLY (SELECT TOP 1 Row_ID, ID, Account FROM #myAccount t3 WHERE t3.Account >= t2.VFr AND t3.Account <= t2.vto AND t3.Account <> t1.Account ORDER BY t3.Account) t4 CROSS APPLY (SELECT TOP 1 Row_ID, ID, Account FROM #myAccount t5 WHERE t5.Account >= t2.VFr AND t5.Account <= t2.vto AND t5.Account <> t1.Account ORDER BY t5.Account) t6 CREATE CLUSTERED INDEX idx0 ON #myR1C1 (ID) WITH (FILLFACTOR = 100) -- final result (concatenate R1C1 fields) SELECT Row_ID, ID, Account, REPLACE(Name, '*', '#_') Name, R1C1 FROM #myAccount LEFT OUTER JOIN (SELECT ID, STUFF((SELECT ',' + t.R1C1 FROM #myR1C1 t WHERE t.ID = a.ID FOR XML PATH ('')), 1, 1, '') as R1C1 FROM #myR1C1 as a GROUP BY ID) t2 ON t1.ID = t2.ID 功能。对于初学者来说,功能对性能非常不利,他们肯定会搞砸查询优化器的猜测工作。此外,如果该函数以非最佳方式写入,则可能会使其速度更慢。

=&GT;我是否正确地认为它分裂了类似于&#39; ABC | XYZ&#39;进入vfr =&#39; ABC&#39;和vto =&#39; XYZ&#39 ;?或者它应该返回多个记录(例如,当它找到&#39; ABC | XYZ | PQR | STU&#39;它返回2条记录?)或类似的东西?

最后,我认为你错过了部分查询。

无论如何,Common Table Expressions的问题在于,在查询时可以很容易地进行构建查询,从而无法确定究竟可能出现的错误。

我建议将第一部分拆分为单独的临时表,然后使用例如查询计划资源管理器以查看花了这么长时间。 (查看持续时间,&#39;费用&#39;字段可能会指示哪些部分可能无法很好地扩展,但最终持续时间是您现在关注的内容!)。添加索引可能看起来过多,但它们具有隐式向表中添加(非常好)统计信息的好处,在这种情况下,它们通常会帮助执行后面的查询。此外,索引&#39;合理大小的表格&#39;在现代硬件上花费很少的时间。

SELECT .. INTO #table FROM... WHERE 1 = 2
PS:是的,我知道,更好的方法是首先INSERT INTO然后使用parse new填写,但是对于这个测试,我是懒惰的...... / p>