2014-04-02 100 views
9

我之前发布过类似的内容,但现在我正在从一个不同的方向来处理这个问题,所以我提出了一个新问题。我希望这是好的。CTE加入时速度很慢

我一直在使用CTE创建基于父费用的费用总和。在SQL和细节可以看这里:

CTE Index recommendations on multiple keyed table

我不认为我缺少的CTE什么,但是当我用数据的一大桌(350万使用它我得到一个问题行)。

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 

返回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中。为快速查询树看起来是这样的:

Fast Query

从慢查询的计划是:

Slow Query

我试图重建索引,贯穿优化顾问以及各种组合查询的子查询。只要联接包含PK以外的任何内容,查询就会很慢。

我也有类似的问题在这里:

SQL Server Query time out depending on Where Clause

其中用函数来做子行,而不是一个CTE的summimg。这是使用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 

编辑: 更多玩耍,我只是不明白这一点。

该查询即时(2MS):

Select t.* from 
vwChargeShareSubCharges t 
Where t.MasterChargeID = 1291094 

而这需要3分:

DECLARE @ChargeID int = 1291094 

Select t.* from 
vwChargeShareSubCharges t 
Where t.MasterChargeID = @ChargeID 

即使我把号堆在 “在”,查询还是瞬间:

Where t.MasterChargeID in (1291090, 1291091, 1291092, 1291093, 1291094, 1291095, 1291096, 1291097, 1291098, 1291099, 129109) 

编辑2:

我可以从头开始使用这个例子中的数据复制这样的:

我创建了一些虚拟数据复制的问题。它不是那么显著,因为我只加了100,000行,但坏的执行计划仍然发生(在SQLCMD模式下运行):

CREATE TABLE [tblChargeTest](
[ChargeID] [int] IDENTITY(1,1) NOT NULL, 
[ParentChargeID] [int] NULL, 
[TotalAmount] [money] NULL, 
[TaxAmount] [money] NULL, 
[DiscountAmount] [money] NULL, 
[InvoiceID] [int] NULL, 
CONSTRAINT [PK_tblChargeTest] PRIMARY KEY CLUSTERED 
(
[ChargeID] ASC 
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF,  ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] 
) ON [PRIMARY] 
END 
GO 

Insert into tblChargeTest 
(discountAmount, TotalAmount, TaxAmount) 
Select ABS(CHECKSUM(NewId())) % 10, ABS(CHECKSUM(NewId())) % 100, ABS(CHECKSUM(NewId())) % 10 
GO 100000 

Update tblChargeTest 
Set ParentChargeID = (ABS(CHECKSUM(NewId())) % 60000) + 20000 
Where ChargeID = (ABS(CHECKSUM(NewId())) % 20000) 
GO 5000 

CREATE VIEW [vwChargeShareSubCharges] AS 
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 

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 
GO 

然后运行这两个查询:

--Slow Query: 
Declare @ChargeID int = 60900 

Select * 
from [vwChargeShareSubCharges] 
Where MasterChargeID = @ChargeID 

--Fast Query: 
Select * 
from [vwChargeShareSubCharges] 
Where MasterChargeID = 60900 
+0

只是快速的想法...如果你把你的查询'从vwChargeShareSubCharges t'选择吨。*,其实际TSQL定义替换视图'vwChargeShareSubCharges',加入到其他表酌情和运行查询,它有什么更快? – DMason

+0

完全相同,如果我粘贴在完整的CTE。 – Molloch

回答

11

最好SQL Server可以为你做的是将ChargeID上的过滤器向下推入视图内部的递归CTE的锚部分。这样可以查找唯一需要从中构建层次结构的行。当您提供的参数作为一个恒定值SQL Server可以做出优化(使用一种称为SelOnIterator规则,对于那些有兴趣谁在诸如此类的事情):

Pushed predicate with a constant value

当你使用一个局部变量,使用variabl时

Stuck Predicate

的一种方式,以获得最佳的计划:能不能做到这一点,所以在ChargeID谓词卡视图(它建立完整的层次从所有NULL IDS开始)外e是强制优化器在每次执行时编译新的计划。然后根据执行时变量中的特定值量身定制计划。这是通过添加一个OPTION (RECOMPILE)查询提示实现:

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) 

这是关于类似场景中类似优化规则的另一个答案。 Optimizing Execution Plans for Parameterized T-SQL Queries Containing Window Functions

+0

谢谢。今天我会玩这些,看看我能做些什么。感谢您的详细解答。 – Molloch

+1

OPTION(RECOMPILE)完美适用于@ChargeID变量,但如果我以任何方式加入CTE,仍会非常缓慢。我已将InvoiceID键添加到接地表中,并按照此处所建议的那样创建了TABLE函数,并且它非常快速。 – Molloch

3

首先感谢您对帖子的布局非常好。从你对问题的解释中学到了很多东西。

接下来得到一个解决方案,我会建议选择进入临时表中的CTE并从那里加入。从加入CTE的个人经验来看,我的查询返回了5分钟,而只是将由CTE生成的数据插入临时表中将其降至仅4秒。实际上,我将两个CTE连接在一起,但我想这将适用于所有长时间运行的查询,当CTE连接到LONG表(特别是外连接)时。


    --temp tables if needed to work with intermediate values 
    If object_id('tempdb..#p') is not null 
    drop table #p 

    ;WITH cte as ( 
    select * from t1 
    ) 

    select * 
    into #p 
    from cte 

    --then use the temp table as you would normally use the CTE 
    select * from #p 
+0

这不是一个“分享你的经验”的网站。如果你想'@'某人,请在他们的帖子下留言。这是提出问题的实际解决方案还是开个新问题? – brasofilo

+2

上面的帖子是一个实际的解决方案。像通常使用CTE一样,将CTE插入临时表并在查询中使用该临时表。我的经验是提到的加速,但经验各异,加速可能不会太激烈(因此分享经验)。在我的情况下,我使用临时表来加入两个CTE,并且从5分钟减少到4秒。 – kuklei