sql - 我怎样才能提高这个存储过程的性能

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

我知道它看起来很大,也许我以这种方式展示它是愚蠢的,但经过 2 周的尝试并没有取得太大的成功,人们开始逼迫我并提示我在这上面花费了太多时间...... .老实说,我真的不知道还能做什么。

到目前为止,我一直使用 SQL Profiler 工具来获取包含工作负载的文件,并在 Tunning Advisor 工具中使用它来查看它提出的建议。 我得到了一些建议,说要建立一些统计数据和一些索引,我也这样做了,但差异几乎不明显。 另一件我认为值得一提的事情是,在计算时(在使用 Profiler 和 Tunning Advisor 之前)这部分存储过程应该返回的估计行数是 1600,而实际行数是 536。据我所知,这不是一件好事。 现在奇怪的部分来了,在使用从 Tunning Advisor 获得的建议后,它没有减少估计的行数,而是增加到 2800,但速度几乎相同,为 2-3 秒。 我知道还有很多事情可以做,但我现在既没有知识也没有时间深入研究它们,所以如果有人能指出我正确的方向,那就太好了。 如果我还可以提供任何其他信息来弄清这个问题,我很乐意这样做,所以请尽管问。 而且我差点忘了,预期的结果可能是 1 秒或更短......因为它只有 536 行,而且我的客户已经看到查询在超过 20.000 行上执行得更快

最佳答案

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

乍一看,您似乎正在执行很多 ORDER BY,这可能会“对机器造成沉重负担”,但我主要担心 dbo.abx_sysSplitTwo() 函数。对于初学者来说,函数对性能来说确实很糟糕,而且它们肯定会打乱查询优化器的猜测工作。此外,如果函数是以“非最佳方式”编写的,这可能会进一步降低速度。

=> 我是否可以假设它将“ABC|XYZ”之类的内容拆分为 vfr=“ABC”和 vto=“XYZ”?或者它应该返回多条记录(例如,当它找到“ABC|XYZ|PQR|STU”时它返回 2 条记录?)或类似的东西?

最后,我认为您遗漏了部分查询。

无论如何,公用表表达式的问题在于,人们很容易在查询时构建查询而忘乎所以,从而无法弄清楚到底哪里出了问题。

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

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 填充它,但是对于这个测试我很懒...

关于sql - 我怎样才能提高这个存储过程的性能,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33395441/

相关文章:

sql - 如何加速 SQL 中的 LIKE 操作(最好是 Postgres)

SQL - 选择仅满足特定条件的记录组

sql-server - 为什么 CROSS APPLY *不*在此查询中出现无效列错误?

sql-server - 将表复制到不同 SQL Server 上的不同数据库

database - Mule ESB数据流设计

sql - 简单的 SQL - 计数函数

MySQL-为每个设备选择最后一行的查询

sql - 如何根据 SQL 查询的结果运行子查询

postgresql - 提高重复查询的查询效率

sql - 计算两个字符串中顺序匹配的单词 oracle