Bir sorguda performans ayarlama


9

Bu sorgu performansını artırmak için yardım istemek.

SQL Server 2008 R2 Enterprise , Maksimum RAM 16 GB, CPU 40, Maksimum Paralellik Derecesi 4.

SELECT DsJobStat.JobName AS JobName
    , AJF.ApplGroup AS GroupName
    , DsJobStat.JobStatus AS JobStatus
    , AVG(CAST(DsJobStat.ElapsedSec AS FLOAT)) AS ElapsedSecAVG
    , AVG(CAST(DsJobStat.CpuMSec AS FLOAT)) AS CpuMSecAVG 
FROM DsJobStat, AJF 
WHERE DsJobStat.NumericOrderNo=AJF.OrderNo 
AND DsJobStat.Odate=AJF.Odate 
AND DsJobStat.JobName NOT IN( SELECT [DsAvg].JobName FROM [DsAvg] )         
GROUP BY DsJobStat.JobName
, AJF.ApplGroup
, DsJobStat.JobStatus
HAVING AVG(CAST(DsJobStat.ElapsedSec AS FLOAT)) <> 0;

Yürütme mesajı,

(0 row(s) affected)
Table 'AJF'. Scan count 11, logical reads 45, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'DsAvg'. Scan count 2, logical reads 1926, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'DsJobStat'. Scan count 1, logical reads 3831235, physical reads 85, read-ahead reads 3724396, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)

SQL Server Execution Times:
      CPU time = 67268 ms,  elapsed time = 90206 ms.

Tabloların yapısı:

-- 212271023 rows
CREATE TABLE [dbo].[DsJobStat](
    [OrderID] [nvarchar](8) NOT NULL,
    [JobNo] [int] NOT NULL,
    [Odate] [datetime] NOT NULL,
    [TaskType] [nvarchar](255) NULL,
    [JobName] [nvarchar](255) NOT NULL,
    [StartTime] [datetime] NULL,
    [EndTime] [datetime] NULL,
    [NodeID] [nvarchar](255) NULL,
    [GroupName] [nvarchar](255) NULL,
    [CompStat] [int] NULL,
    [RerunCounter] [int] NOT NULL,
    [JobStatus] [nvarchar](255) NULL,
    [CpuMSec] [int] NULL,
    [ElapsedSec] [int] NULL,
    [StatusReason] [nvarchar](255) NULL,
    [NumericOrderNo] [int] NULL,
CONSTRAINT [PK_DsJobStat] PRIMARY KEY CLUSTERED 
(   [OrderID] ASC,
    [JobNo] ASC,
    [Odate] ASC,
    [JobName] ASC,
    [RerunCounter] ASC
));

-- 48992126 rows
CREATE TABLE [dbo].[AJF](  
    [JobName] [nvarchar](255) NOT NULL,
    [JobNo] [int] NOT NULL,
    [OrderNo] [int] NOT NULL,
    [Odate] [datetime] NOT NULL,
    [SchedTab] [nvarchar](255) NULL,
    [Application] [nvarchar](255) NULL,
    [ApplGroup] [nvarchar](255) NULL,
    [GroupName] [nvarchar](255) NULL,
    [NodeID] [nvarchar](255) NULL,
    [Memlib] [nvarchar](255) NULL,
    [Memname] [nvarchar](255) NULL,
    [CreationTime] [datetime] NULL,
CONSTRAINT [AJF$PrimaryKey] PRIMARY KEY CLUSTERED 
(   [JobName] ASC,
    [JobNo] ASC,
    [OrderNo] ASC,
    [Odate] ASC
));

-- 413176 rows
CREATE TABLE [dbo].[DsAvg](
    [JobName] [nvarchar](255) NULL,
    [GroupName] [nvarchar](255) NULL,
    [JobStatus] [nvarchar](255) NULL,
    [ElapsedSecAVG] [float] NULL,
    [CpuMSecAVG] [float] NULL
);

CREATE NONCLUSTERED INDEX [DJS_Dashboard_2] ON [dbo].[DsJobStat] 
(   [JobName] ASC,
    [Odate] ASC,
    [StartTime] ASC,
    [EndTime] ASC
)
INCLUDE ( [OrderID],
[JobNo],
[NodeID],
[GroupName],
[JobStatus],
[CpuMSec],
[ElapsedSec],
[NumericOrderNo]) ;

CREATE NONCLUSTERED INDEX [Idx_Dashboard_AJF] ON [dbo].[AJF] 
(   [OrderNo] ASC,
[Odate] ASC
)
INCLUDE ( [SchedTab],
[Application],
[ApplGroup]) ;

CREATE NONCLUSTERED INDEX [DsAvg$JobName] ON [dbo].[DsAvg] 
(   [JobName] ASC
)

Yürütme planı:

https://www.brentozar.com/pastetheplan/?id=rkUVhMlXM


Yanıtlandıktan sonra güncelleme

Çok teşekkür ederim @Joe Obbish

DsJobStat ve DsAvg arasındaki bu sorgunun sorunu hakkında haklısınız. Bu nasıl katılacak ve değil kullanmayla ilgili bir şey değil.

Tahmin ettiğiniz gibi gerçekten bir masa var.

CREATE TABLE [dbo].[DSJobNames](
    [JobName] [nvarchar](255) NOT NULL,
 CONSTRAINT [DSJobNames$PrimaryKey] PRIMARY KEY CLUSTERED 
(   [JobName] ASC
) ); 

Önerinizi denedim,

SELECT DsJobStat.JobName AS JobName
, AJF.ApplGroup AS GroupName
, DsJobStat.JobStatus AS JobStatus
, AVG(CAST(DsJobStat.ElapsedSec AS FLOAT)) AS ElapsedSecAVG
, Avg(CAST(DsJobStat.CpuMSec AS FLOAT)) AS CpuMSecAVG 
FROM DsJobStat
INNER JOIN DSJobNames jn
    ON jn.[JobName]= DsJobStat.[JobName]
INNER JOIN AJF 
    ON DsJobStat.Odate=AJF.Odate 
    AND DsJobStat.NumericOrderNo=AJF.OrderNo 
WHERE NOT EXISTS ( SELECT 1 FROM [DsAvg] WHERE jn.JobName =  [DsAvg].JobName )      
GROUP BY DsJobStat.JobName, AJF.ApplGroup, DsJobStat.JobStatus
HAVING AVG(CAST(DsJobStat.ElapsedSec AS FLOAT)) <> 0;   

Yürütme mesajı:

(0 row(s) affected)
Table 'DSJobNames'. Scan count 5, logical reads 1244, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'DsAvg'. Scan count 5, logical reads 2129, physical reads 0, read-ahead reads 24, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'DsJobStat'. Scan count 8, logical reads 84, physical reads 0, read-ahead reads 83, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'AJF'. Scan count 5, logical reads 757999, physical reads 944, read-ahead reads 757311, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)

 SQL Server Execution Times:
   CPU time = 21776 ms,  elapsed time = 33984 ms.

Uygulama planı: https://www.brentozar.com/pastetheplan/?id=rJVkLSZ7f


Değiştiremediğiniz satıcı kodu ise, yapılacak en iyi şey, satıcıyla olabildiğince acı verici bir destek olayı açmak ve birçok okumanın yerine getirilmesini gerektiren bir sorgu için onları dövmektir. 413 bin satır içeren bir tablodaki değerleri ifade eden NOT IN yan tümcesi, uh, alt optimaldir. DSJobStat üzerindeki endeks taraması 212 milyon satır döndürüyor ve bu da 212 milyon iç içe döngüye ulaşıyor ve 212 milyon satır sayısının maliyetin% 83'ü olduğunu görebilirsiniz. Sorguyu yeniden yazmadan veya verileri temizlemeden yardımcı olabileceğinizi sanmıyorum ...
Tony Hinkle

Anlamadım, Evan önerisi size ilk etapta nasıl yardımcı olmadı, açıklama dışında her ikisi de aynı.
KumarHarsh

Yanıtlar:


11

Birleşme sırasını düşünerek başlayalım. Sorguda üç tablo başvurunuz var. Hangi birleştirme siparişi size en iyi performansı verebilir? Sorgu iyileştirici gelen katılmak düşünüyor DsJobStatiçin DsAvg(kardinalite tahminleri 1 satıra 212195000 düşmek) hemen hemen tüm satırları ortadan kaldıracaktır. Gerçek plan bize tahminin gerçeğe oldukça yakın olduğunu gösteriyor (11 satır birleşimden sağ çıktı). Bununla birlikte, birleşim bir sağ anti-birleştirme birleşimi olarak uygulanır, böylece DsJobStattablodaki 212 milyon satırın tamamı sadece 11 sıra üretmek üzere taranır. Bu kesinlikle uzun sorgu yürütme süresine katkıda bulunabilir, ancak daha iyi olurdu o birleştirme için daha iyi bir fiziksel veya mantıksal operatör düşünemiyorum. Eminim kiDJS_Dashboard_2dizini diğer sorgular için kullanılır, ancak fazladan anahtar ve dahil edilen sütunların tümü bu sorgu için daha fazla ES gerektirir ve sizi yavaşlatır. Böylece, DsJobStattablodaki dizin taramasıyla ilgili bir tablo erişim sorununuz olabilir .

Birleşmenin AJFçok seçici olmadığını varsayacağım . Şu anda sorguda gördüğünüz performans sorunlarıyla ilgili değil, bu yüzden bu cevabın geri kalanında görmezden geleceğim. Tablodaki veriler değişirse bu değişebilir.

Plandan anlaşılan diğer sorun, satır sayısı biriktirme işleci. Bu çok hafif bir operatör ama 200 milyondan fazla kez çalışıyor. Sorgu ile yazıldığından işleç oradadır NOT IN. İçinde tek bir NULL satır varsa, DsAvgtüm satırlar elenmelidir. Makara, bu çekin uygulanmasıdır. Muhtemelen istediğiniz mantık bu değildir, bu yüzden kullanmak için o kısmı yazmakta daha iyi olursunuz NOT EXISTS. Bu yeniden yazmanın gerçek yararı sisteminize ve verilerinize bağlı olacaktır.

Birkaç sorgu yeniden yazma test etmek için sorgu planına dayalı bazı verileri alay ettim. Tablo tanımlarım sizinkinden önemli ölçüde farklı çünkü her bir sütun için veri toplamak çok fazla çaba isterdi. Kısaltılmış veri yapılarında bile, yaşadığınız performans sorununu yeniden üretebildim.

CREATE TABLE [dbo].[DsAvg](
    [JobName] [nvarchar](255) NULL
);

CREATE CLUSTERED INDEX CI_DsAvg ON [DsAvg] (JobName);

INSERT INTO [DsAvg] WITH (TABLOCK)
SELECT TOP (200000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
OPTION (MAXDOP 1);

CREATE TABLE [dbo].[DsJobStat](
    [JobName] [nvarchar](255) NOT NULL,
    [JobStatus] [nvarchar](255) NULL,
);

CREATE CLUSTERED INDEX CI_JobStat ON DsJobStat (JobName)

INSERT INTO [DsJobStat] WITH (TABLOCK)
SELECT [JobName], 'ACTIVE'
FROM [DsAvg] ds
CROSS JOIN (
SELECT TOP (1000) 1
FROM master..spt_values t1
) c (t);

INSERT INTO [DsJobStat] WITH (TABLOCK)
SELECT TOP (1000) '200001', 'ACTIVE'
FROM master..spt_values t1;

Sorgu planına dayanarak JobName, DsAvgtabloda yaklaşık 200000 benzersiz değer olduğunu görebiliriz . Bu tabloya birleştirildikten sonraki gerçek satır sayısına bağlı olarak, içindeki JobNamedeğerlerin neredeyse tümünün DsJobStatde DsAvgtabloda olduğunu görebiliriz. Böylece, DsJobStattablo JobNamesütun için 200001 benzersiz değere ve değer başına 1000 satıra sahiptir.

Bu sorgunun performans sorununu temsil ettiğine inanıyorum:

SELECT DsJobStat.JobName AS JobName, DsJobStat.JobStatus AS JobStatus
FROM DsJobStat
WHERE DsJobStat.JobName NOT IN( SELECT [DsAvg].JobName FROM [DsAvg] );

Sorgu planınızdaki diğer tüm öğeler ( GROUP BY, HAVINGeski stil birleşimi vb.) Sonuç kümesi 11 satıra indirildikten sonra gerçekleşir. Şu anda bir sorgu performans açısından önemli değildir, ancak tablolarınızdaki değişen verilerle ortaya çıkabilecek başka endişeler olabilir.

SQL Server 2017'de test ediyorum, ancak sizinle aynı temel plan şeklini alıyorum:

plandan önce

Makinemde, bu sorgu yürütmek için 62219 ms CPU süresi ve 65576 ms geçen süreyi alır. Kullanılacak sorguyu yeniden yazarsam NOT EXISTS:

SELECT DsJobStat.JobName AS JobName, DsJobStat.JobStatus AS JobStatus
FROM DsJobStat
WHERE NOT EXISTS (SELECT 1 FROM [DsAvg] WHERE DsJobStat.JobName = [DsAvg].JobName);

biriktirme yok

Makara artık 212 milyon kez yürütülmüyor ve muhtemelen satıcıdan beklenen davranışa sahip. Şimdi sorgu 34516 ms CPU zamanı ve 41132 ms geçen zaman içinde yürütülmektedir. Zamanın büyük bir kısmı endeksten 212 milyon satır tarayarak geçiyor.

Bu indeks taraması bu sorgu için çok talihsiz. Ortalama olarak benzersiz değeri başına 1000 satırımız var JobName, ancak ilk satırı okuduktan sonra önceki 1000 satıra ihtiyacımız olup olmadığını biliyoruz. Neredeyse asla bu satırlara ihtiyacımız yok, ama yine de onları taramamız gerekiyor. Tablodaki satırların çok yoğun olmadığını ve neredeyse hepsinin birleştirme tarafından ortadan kaldırılacağını bilersek, dizinde daha etkili bir IO deseni hayal edebiliriz. SQL Server benzersiz değeri başına ilk satırı okuduysa, JobNamebu değerin bulunup bulunmadığını kontrol ettiyse DsAvgve JobNameeğer varsa bir sonraki değerine atlarsa ne olur? 212 milyon satırı taramak yerine 200 bin civarında yürütme gerektiren bir arama planı yapılabilir.

Bu çoğunlukla, Paul White'ın burada açıklanan öncülük ettiği bir teknikle birlikte özyineleme kullanılarak gerçekleştirilebilir . Yukarıda tarif ettiğim IO modelini yapmak için özyineleme kullanabiliriz:

WITH RecursiveCTE
AS
(
    -- Anchor
    SELECT TOP (1)
        [JobName]
    FROM dbo.DsJobStat AS T
    ORDER BY
        T.[JobName]

    UNION ALL

    -- Recursive
    SELECT R.[JobName]
    FROM
    (
        -- Number the rows
        SELECT 
            T.[JobName],
            rn = ROW_NUMBER() OVER (
                ORDER BY T.[JobName])
        FROM dbo.DsJobStat AS T
        JOIN RecursiveCTE AS R
            ON R.[JobName] < T.[JobName]
    ) AS R
    WHERE
        -- Only the row that sorts lowest
        R.rn = 1
)
SELECT js.*
FROM RecursiveCTE
INNER JOIN dbo.DsJobStat js ON RecursiveCTE.[JobName]= js.[JobName]
WHERE NOT EXISTS (SELECT 1 FROM [DsAvg] WHERE RecursiveCTE.JobName = [DsAvg].JobName)
OPTION (MAXRECURSION 0);

Bu sorgu bakmak çok şey bu yüzden gerçek planı dikkatle incelemenizi öneririz . Öncelikle DsJobStattüm benzersiz JobNamedeğerleri elde etmek için endekse karşı 200002 endeksi ararız . Sonra bir DsAvgsatır dışındaki tüm satırlara katılır ve bunları kaldırırız. Kalan satır için, DsJobStatgerekli sütunların tamamına tekrar katılın ve alın.

ES kalıbı tamamen değişir. Bunu almadan önce:

Tablo 'DsJobStat'. Tarama sayısı 1, mantıksal okumalar 1091651, fiziksel okumalar 13836, okumaya devam okumalar 181966

Özyinelemeli sorgu ile bunu elde ederiz:

Tablo 'DsJobStat'. Tarama sayısı 200003, mantıksal okumalar 1398000, fiziksel okumalar 1, ileri okumalar 7345

Makinemde, yeni sorgu yalnızca CPU zamanının 6891 ms ve geçen zamanın 7107 msn içinde yürütülür. Özyinelemeyi bu şekilde kullanmanın veri modelinde bir şey eksik olduğunu (veya belki de gönderilen soruda belirtilmemiş olduğunu) gösterdiğini unutmayın. Mümkün olan her şeyi içeren nispeten küçük bir tablo varsa JobNames, büyük tablodaki özyinelemenin aksine bu tabloyu kullanmak çok daha iyi olacaktır. JobNamesİhtiyaç duyduğunuz şeyleri içeren bir sonuç kümeniz varsa , eksik sütunların geri kalanını almak için dizin aramalarını kullanabilirsiniz. Ancak, ihtiyacınız olmayan bir sonuç kümesiyle JobNamesbunu yapamazsınız.


Ben önerdim NOT EXISTS. "Soru göndermeden önce ikisini de denedim, katıl ve var değilim." İle cevap verdiler. Çok fazla fark yok.
Evan Carroll

1
Özyineleyen fikrin işe yarayıp yaramadığını bilmek çok ilginç olurdu, ama bu çok korkutucu.
Evan Carroll

bence yan tümcenin gerekli olmadığını düşünüyorum. "ElapsedSec null değil" nerede yan tümce yapacağım.Ayrıca ben özyinelemeli CTE gerektirmez düşünüyorum. you (kullanmayın ismine göre göre bölüm) rn (seç Fikrim hakkında ne söylemek zorundasın?
KumarHarsh

@Joe Obbish, yazımı güncelledim. Çok teşekkürler.
Wendy

Evet, Özyinelemeli CTE çıkışı, row_number () üzerinden (isme göre iş adına göre bölüm) 1 dakika rn gerçekleştirir.Ancak aynı zamanda örnek verilerinizi kullanarak Yinelemeli CTE'de fazladan bir kazanç görmedim.
KumarHarsh

0

Durumu yeniden yazarsanız ne olacağını görün,

AND DsJobStat.JobName NOT IN( SELECT [DsAvg].JobName FROM [DsAvg] )         

için

AND NOT EXISTS ( SELECT 1 FROM [DsAvg] AS d WHERE d.JobName = DsJobStat.JobName )

Ayrıca, bu stil korkunç olduğu için SQL89 birleştirmenizi yeniden yazmayı düşünün.

Onun yerine

FROM DsJobStat, AJF 
WHERE DsJobStat.NumericOrderNo=AJF.OrderNo 
AND DsJobStat.Odate=AJF.Odate 

Deneyin

FROM DsJobStat
INNER JOIN AJF ON (
  DsJobStat.NumericOrderNo=AJF.OrderNo 
  AND DsJobStat.Odate=AJF.Odate
)

Ayrıca bu durumun daha iyi yazılabileceğinden şüpheleniyorum ancak neler olduğu hakkında daha fazla bilgi sahibi olmamız gerekiyor

HAVING AVG(CAST(DsJobStat.ElapsedSec AS FLOAT)) <> 0;

Ortalamanın sıfır olmadığını veya grubun bir öğesinin sıfır olmadığını gerçekten bilmek zorunda mısınız?


@EvanCarroll. Soru göndermeden önce ikisini de denedim, katıl ve mevcut değil. Çok fark yok.
Wendy
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.