2014-04-02 137 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