2014-04-02 18 views
9

Napisałem coś podobnego wcześniej, ale zbliżam się do tego z innego kierunku, więc otworzyłem nowe pytanie. Mam nadzieję, że to jest w porządku.CTE bardzo powolny, gdy dołączyłem

Pracowałem z CTE, który tworzy sumę opłat w oparciu o Parent Charge. SQL i szczegóły można zobaczyć tutaj:

CTE Index recommendations on multiple keyed table

Nie sądzę, brakuje mi niczego na CTE, ale jestem coraz problem, gdy używam go z dużym stołem danych (3,5 mln wydziwianie).

Tabela tblChargeShare zawiera kilka innych informacji, których potrzebuję, takich jak InvoiceID, więc umieściłem mój CTE w widoku vwChargeShareSubCharges i dołączyłem go do stołu.

Zapytanie:

Select t.* from vwChargeShareSubCharges t 
inner join 
tblChargeShare s 
on t.CustomerID = s.CustomerID 
and t.MasterChargeID = s.ChargeID 
Where s.ChargeID = 1291094 

Zwraca wynik w ciągu kilku ms.

Zapytanie:

Select ChargeID from tblChargeShare Where InvoiceID = 1045854 

zwraca 1 wiersz:

1291094 

Ale zapytania:

Select t.* from vwChargeShareSubCharges t 
inner join 
tblChargeShare s 
on t.CustomerID = s.CustomerID 
and t.MasterChargeID = s.ChargeID 
Where InvoiceID = 1045854 

trwa 2-3 minut, aby uruchomić.

Zapisałem plany wykonania i wczytałem je do SQL Sentry. Drzewo do szybkiego zapytania wygląda następująco:

Fast Query

Plan z powolnym zapytania jest:

Slow Query

Próbowałem reindeksowania, uruchamiając zapytanie poprzez doradcę strojenia i różnych kombinacjach podrzędnych zapytań. Gdy sprzężenie zawiera coś innego niż PK, zapytanie jest wolne.

miałem podobne pytanie tutaj:

SQL Server Query time out depending on Where Clause

Które używane funkcje zrobić summimg wierszy podrzędnych zamiast CTE. To jest przepisywanie za pomocą CTE, aby spróbować uniknąć tego samego problemu, którego teraz doświadczam. Przeczytałem odpowiedzi w tej odpowiedzi, ale nie jestem mądrzejszy - przeczytałem kilka informacji o podpowiedziach i parametrach, ale nie mogę tego zrobić. Myślałem, że przepisanie za pomocą CTE rozwiąże mój problem. Zapytanie jest szybkie, gdy działa na tblCharge z kilkoma tysiącami wierszy.

badana w obu SQL 2008 R2 i SQL 2012

Edit:

Mam skondensowane zapytanie do pojedynczej instrukcji, ale ten sam problem będzie się powtarzał:

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 

Edycja : Więcej zabawy, po prostu tego nie rozumiem.

Ta kwerenda jest błyskawiczne (2 ms):

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

niniejsza trwa 3 minuty:

DECLARE @ChargeID int = 1291094 

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

Nawet jeśli kładę stosy liczb w "in", zapytanie jest jeszcze chwila :

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

Edit 2:

można replikować to od podstaw przy użyciu ten przykład dane:

Stworzyłem niektóre dane manekina do replikowania problem. To nie jest tak istotna, jak tylko dodaje 100.000 wierszy, ale zły plan wykonania nadal dzieje (prowadzony w trybie 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 

Następnie uruchomić te dwa zapytania:

--Slow Query: 
Declare @ChargeID int = 60900 

Select * 
from [vwChargeShareSubCharges] 
Where MasterChargeID = @ChargeID 

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

Tylko szybka myśl ... Jeśli wziąć zapytanie 'Wybierz t. * Od vwChargeShareSubCharges t' wymienić pogląd' vwChargeShareSubCharges' z jego rzeczywistej definicji tsql, przyłączyć do innych tabel w stosownych przypadkach, a run zapytanie, czy to szybciej? – DMason

+0

Dokładnie to samo, jeśli wkleję w pełnym CTE. – Molloch

Odpowiedz

11

Najlepszym SQL Server może zrobić dla ciebie tutaj, aby przesunąć filtr na ChargeID w dół do części zakotwiczonej rekurencyjnego CTE wewnątrz widoku. Pozwala to na znalezienie jedynego wiersza, z którego trzeba zbudować hierarchię. Gdy podasz parametr jako stałą wartość SQL Server może sprawić, że optymalizacja (stosując regułę zwaną SelOnIterator, dla tych, którzy są zainteresowani tego rodzaju rzeczy):

Pushed predicate with a constant value

podczas korzystania z lokalną zmienną it nie mogę tego zrobić, więc orzecznikiem na ChargeID utknie poza widzenia (który buduje całą hierarchię, począwszy od wszystkich NULL iDS):

Stuck Predicate

jeden sposób, aby uzyskać optymalny plan przy użyciu variabl e to zmuszanie optymalizatora do kompilowania świeżego planu przy każdym wykonaniu. Wynikowy plan jest następnie dostosowywany do konkretnej wartości zmiennej w czasie wykonywania. Osiąga się to przez dodanie podpowiedź OPTION (RECOMPILE) zapytania:

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); 

Drugą opcją jest, aby zmienić widok na funkcję stołu inline.To pozwala określić położenie orzecznika filtrującego extenso:

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 
) 

używać go tak:

Declare @ChargeID int = 60900 

select * 
from dbo.udfChargeShareSubCharges(@ChargeID) 

Zapytanie mogą również korzystać z indeksu na ParentChargeID.

create index ix_ParentChargeID on tblChargeTest(ParentChargeID) 

Oto kolejna odpowiedź na temat podobnej reguły optymalizacji w podobnym scenariuszu. Optimizing Execution Plans for Parameterized T-SQL Queries Containing Window Functions

+0

Dzięki. Będę się z nimi dzisiaj bawił i zobaczę, co mogę osiągnąć. Dzięki za szczegółową odpowiedź. – Molloch

+1

OPCJA (RECOMPILE) działa idealnie dla zmiennej @ChargeID, ale wciąż boleśnie powolna, jeśli dołączę do CTE w jakikolwiek sposób. Dodałem klawisz FakturaID do tabeli uziemienia i utworzyłem funkcję TABELA, zgodnie z sugestią, która jest przyjemna i szybka. – Molloch

3

Po pierwsze dziękuję za tak dobre ułożenie poczty. Wiele się nauczyłem od samego wyjaśnienia problemu.

Następnie, aby uzyskać rozwiązanie, polecam WYBRAĆ DO CTE w tabelę e temp i dołączyć stamtąd. Z osobistego doświadczenia dołączania do CTE moje zapytanie powracało przez 5 minut, a zwykłe wstawienie danych wygenerowanych przez CTE do tabeli tymczasowej spowodowało, że spadły do ​​zaledwie 4 sekundy. W rzeczywistości dołączałem do dwóch CTE razem, ale myślę, że to by miało zastosowanie do wszystkich długich uruchomionych zapytań, gdy CTE jest połączone z DŁUGĄ tabelą (szczególnie zewnętrzne sprzężenia).


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

To nie jest strona "podziel się swoimi doświadczeniami". Jeśli chcesz kogoś "@", skomentuj pod swoim wpisem. Czy jest to faktyczne rozwiązanie przedstawionego problemu, czy też jest to kwestia otwarcia nowego pytania? – brasofilo

+2

Powyższy post jest rzeczywistym rozwiązaniem. Włóż CTE do tabeli tymczasowej i użyj tej tabeli tymczasowej w zapytaniu, tak jak zwykle używasz CTE. Moje doświadczenie było wspomnianym przyspieszeniem, ale doświadczenia są różne i przyspieszenie może nie być tak drastyczne (stąd dzielenie się doświadczeniami). W moim przypadku byłem zewnętrzny łącząc dwa CTE i od 5 minut został zredukowany do 4sek przy użyciu tabel tymczasowych. – kuklei

Powiązane problemy