簡體   English   中英

如何提高此存儲過程的性能

[英]How can I improve the performance of this stored procedure

所以我剛開始使用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行上執行得更快

首先,我邀請您閱讀http://importblogkit.com/2015/05/how-do-you-eat-an-elephant/

您需要將查詢拆分為較小的部分以確定問題的位置。 您已經發現查詢的哪一部分是較慢的部分,現在您應該繼續挖掘。

你有4 querys myAccountmyR1C1myAccount2和最終選擇。 只需逐個運行和ANALYZE / EXPLAIN每一個,看看有什么用,多長時間和索引使用什么。

檢查行數,嘗試添加新索引或復合索引以提高速度。

現在,如果你發現了什么東西,那么你看起來很奇怪了如何創建一個最小的,完整的,可驗證的例子。 並使用該部分創建一個新問題。

既然您已經在SQL Sentry Plan Explorer中運行了這個,那么您是否可以嘗試僅運行此查詢然后復制Plan XML數據? (您也可以保護.queryanalysis文件,但它可能包含有關您的服務器名稱等的信息,您可能不會將其放在網上)。 只需將其復制粘貼到pastebin或將其壓縮到例如Dropbox上,然后在此處共享URL。 這樣我們就能更好地了解真正發生的事情......

乍一看,看起來你正在做很多ORDER BY,這可能是“在機器上很重”,但我主要擔心的是dbo.abx_sysSplitTwo()函數。 對於初學者來說,功能對於性能來說真的很糟糕,他們肯定會搞砸查詢優化器的猜測工作。 此外,如果函數以“非最佳方式”寫入,則可能會進一步降低速度。

=>我是否正確地假設它將'ABC | XYZ'分成vfr ='ABC'和vto ='XYZ'? 或者它應該返回多個記錄(例如,當它發現'ABC | XYZ | PQR | STU'它返回2條記錄?)或類似的東西?

最后,我認為你錯過了部分查詢。

無論如何,Common Table Expressions的問題在於,在查詢時可以很容易地進行構建查詢,從而無法確定究竟可能出錯的地方。

我建議將第一部分拆分為單獨的臨時表,然后使用例如查詢計划資源管理器來查看花費的時間。 (查看持續時間,“成本”字段可能會指示哪些部分可能無法很好地擴展,但最終持續時間是您現在關注的內容!)。 添加索引可能看起來過多,但它們具有隱式向表中添加(非常好)統計信息的好處,在這種情況下,它們通常會幫助執行后面的查詢。 此外,索引“合理大小的表”在現代硬件上花費的時間非常少。

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

PS:是的我知道,更好的方法是做SELECT .. INTO #table FROM... WHERE 1 = 2首先然后使用INSERT INTO填充它,但是對於這個測試我很懶...

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM