所以我现在才开始使用 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/