Sql 连接时CTE非常慢
我以前也发表过类似的文章,但我现在从不同的方向来探讨这个问题,所以我提出了一个新的问题。我希望这没问题 我一直在与一个CTE合作,该CTE根据父级费用创建一个费用总额。可在此处查看SQL和详细信息: 我不认为我在CTE上遗漏了任何东西,但是当我将它用于一个大的数据表(350万行)时,我遇到了一个问题 表Sql 连接时CTE非常慢,sql,sql-server-2008-r2,common-table-expression,Sql,Sql Server 2008 R2,Common Table Expression,我以前也发表过类似的文章,但我现在从不同的方向来探讨这个问题,所以我提出了一个新的问题。我希望这没问题 我一直在与一个CTE合作,该CTE根据父级费用创建一个费用总额。可在此处查看SQL和详细信息: 我不认为我在CTE上遗漏了任何东西,但是当我将它用于一个大的数据表(350万行)时,我遇到了一个问题 表tblChargeShare包含我需要的一些其他信息,例如InvoiceID,因此我将我的CTE放在视图vwChargeShareSubCharges中,并将其加入表中 查询: Select t
tblChargeShare
包含我需要的一些其他信息,例如InvoiceID
,因此我将我的CTE放在视图vwChargeShareSubCharges
中,并将其加入表中
查询:
Select t.* from vwChargeShareSubCharges t
inner join
tblChargeShare s
on t.CustomerID = s.CustomerID
and t.MasterChargeID = s.ChargeID
Where s.ChargeID = 1291094
Select ChargeID from tblChargeShare Where InvoiceID = 1045854
几毫秒后返回结果
查询:
Select t.* from vwChargeShareSubCharges t
inner join
tblChargeShare s
on t.CustomerID = s.CustomerID
and t.MasterChargeID = s.ChargeID
Where s.ChargeID = 1291094
Select ChargeID from tblChargeShare Where InvoiceID = 1045854
返回1行:
1291094
但问题是:
Select t.* from vwChargeShareSubCharges t
inner join
tblChargeShare s
on t.CustomerID = s.CustomerID
and t.MasterChargeID = s.ChargeID
Where InvoiceID = 1045854
运行需要2-3分钟
我保存了执行计划并将其加载到SQL Sentry中。快速查询的树如下所示:
Declare @ChargeID int = 60900
select *
from dbo.udfChargeShareSubCharges(@ChargeID)
慢速查询中的计划是:
我尝试过重新索引,通过tuning advisor和各种子查询组合运行查询。只要联接包含PK以外的任何内容,查询就会很慢
我在这里有一个类似的问题:
它使用函数对子行进行求和,而不是CTE。这是使用CTE的重写,试图避免我现在遇到的相同问题。我已经阅读了答案中的回答,但我一点也不明白——我读了一些关于提示和参数的信息,但我无法让它起作用。我原以为用CTE重写可以解决我的问题。当在具有几千行的tblCharge上运行时,查询速度很快
已在SQL 2008 R2和SQL 2012中测试
编辑:
我已将查询压缩为一条语句,但同样的问题仍然存在:
WITH RCTE AS
(
SELECT ParentChargeId, s.ChargeID, 1 AS Lvl, ISNULL(TotalAmount, 0) as TotalAmount, ISNULL(s.TaxAmount, 0) as TaxAmount,
ISNULL(s.DiscountAmount, 0) as DiscountAmount, s.CustomerID, c.ChargeID as MasterChargeID
from tblCharge c inner join tblChargeShare s
on c.ChargeID = s.ChargeID Where s.ChargeShareStatusID < 3 and ParentChargeID is NULL
UNION ALL
SELECT c.ParentChargeID, c.ChargeID, Lvl+1 AS Lvl, ISNULL(s.TotalAmount, 0), ISNULL(s.TaxAmount, 0), ISNULL(s.DiscountAmount, 0) , s.CustomerID
, rc.MasterChargeID
from tblCharge c inner join tblChargeShare s
on c.ChargeID = s.ChargeID
INNER JOIN RCTE rc ON c.PArentChargeID = rc.ChargeID and s.CustomerID = rc.CustomerID Where s.ChargeShareStatusID < 3
)
Select MasterChargeID as ChargeID, rcte.CustomerID, Sum(rcte.TotalAmount) as TotalCharged, Sum(rcte.TaxAmount) as TotalTax, Sum(rcte.DiscountAmount) as TotalDiscount
from RCTE inner join tblChargeShare s on rcte.ChargeID = s.ChargeID and RCTE.CustomerID = s.CustomerID
Where InvoiceID = 1045854
Group by MasterChargeID, rcte.CustomerID
GO
鉴于这需要3分钟:
DECLARE @ChargeID int = 1291094
Select t.* from
vwChargeShareSubCharges t
Where t.MasterChargeID = @ChargeID
即使我将成堆的数字放在“in”中,查询仍然是即时的:
Where t.MasterChargeID in (1291090, 1291091, 1291092, 1291093, 1291094, 1291095, 1291096, 1291097, 1291098, 1291099, 129109)
编辑2: 我可以使用以下示例数据从头开始复制: 我创建了一些虚拟数据来复制问题。这并不重要,因为我只添加了100000行,但错误的执行计划仍然会发生(在SQLCMD模式下运行): 然后运行以下两个查询:
--Slow Query:
Declare @ChargeID int = 60900
Select *
from [vwChargeShareSubCharges]
Where MasterChargeID = @ChargeID
--Fast Query:
Select *
from [vwChargeShareSubCharges]
Where MasterChargeID = 60900
在这里,SQL Server能为您做的最好的事情就是将ChargeID上的过滤器向下推到视图中递归CTE的锚定部分。这使得seek可以找到构建层次结构所需的唯一行。当您将参数作为常量值提供时,SQL Server可以进行优化(对于那些对此感兴趣的人,使用名为
SelOnIterator
的规则):
使用局部变量时,它无法执行此操作,因此ChargeID
上的谓词卡在视图之外(它从所有NULL
id开始构建完整的层次结构):
使用变量时获得最佳计划的一种方法是强制优化器在每次执行时编译一个新的计划。然后,在执行时根据变量中的特定值定制生成的计划。这是通过添加选项(重新编译)
查询提示来实现的:
Declare @ChargeID int = 60900;
-- Produces a fast execution plan, at the cost of a compile on every execution
Select *
from [vwChargeShareSubCharges]
Where MasterChargeID = @ChargeID
OPTION (RECOMPILE);
第二个选项是将视图更改为内联表函数。这允许您显式指定筛选谓词的位置:
CREATE FUNCTION [dbo].[udfChargeShareSubCharges]
(
@ChargeID int
)
RETURNS TABLE AS RETURN
(
WITH RCTE AS
(
SELECT ParentChargeID, ChargeID, 1 AS Lvl, ISNULL(TotalAmount, 0) as TotalAmount, ISNULL(TaxAmount, 0) as TaxAmount,
ISNULL(DiscountAmount, 0) as DiscountAmount, ChargeID as MasterChargeID
FROM tblChargeTest
Where ParentChargeID is NULL
AND ChargeID = @ChargeID -- Filter placed here explicitly
UNION ALL
SELECT rh.ParentChargeID, rh.ChargeID, Lvl+1 AS Lvl, ISNULL(rh.TotalAmount, 0), ISNULL(rh.TaxAmount, 0), ISNULL(rh.DiscountAmount, 0)
, rc.MasterChargeID
FROM tblChargeTest rh
INNER JOIN RCTE rc ON rh.ParentChargeID = rc.ChargeID --and rh.CustomerID = rc.CustomerID
)
Select MasterChargeID, ParentChargeID, ChargeID, TotalAmount, TaxAmount, DiscountAmount , Lvl
FROM RCTE r
)
像这样使用它:
Declare @ChargeID int = 60900
select *
from dbo.udfChargeShareSubCharges(@ChargeID)
查询还可以从ParentChargeID
上的索引中获益
create index ix_ParentChargeID on tblChargeTest(ParentChargeID)
下面是关于类似场景中类似优化规则的另一个答案。
接下来要找到一个解决方案,我建议选择进入CTE INTO e temp表并从那里加入。根据加入CTE的个人经验,我的查询返回了5分钟,而将CTE生成的数据简单地插入到临时表中,使其下降到4秒。我实际上是将两个CTE连接在一起,但我想当一个CTE连接到一个长表(特别是外部连接)时,这将适用于所有长时间运行的查询
快速思考一下……如果您从vwChargeShareSubCharges t中选择t.*进行查询,将视图
vwChargeShareSubCharges
替换为其实际的tsql定义,根据需要连接到其他表,然后运行查询,是否会更快?如果我粘贴完整的CTE,速度会完全相同。谢谢。我今天会玩这些,看看我能做到什么。感谢您提供详细的答案。选项(重新编译)对于@ChargeID变量非常有效,但如果我以任何方式加入CTE,速度仍然非常慢。我已经将InvoiceID键添加到了基础表中,并按照这里的建议创建了一个表函数,它非常好而且快速。非常感谢您的建议。我有一个关于两个CTE的问题,我看了一会儿,没有发现任何明显的问题。当我把其中一个做成临时表时,时间从3分钟减少到2秒。我能对MS说的就是(HSJ/MoG),也许它在klugey方面,但如果它能带来约2个数量级的性能改进,为什么不在内部以这种方式实施呢???