Mencari bantuan untuk meningkatkan kinerja kueri ini.
SQL Server 2008 R2 Enterprise , Max RAM 16 GB, CPU 40, Max Degree of Parallelism 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;
Pesan eksekusi,
(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.
Struktur tabel:
-- 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
)
Rencana eksekusi:
https://www.brentozar.com/pastetheplan/?id=rkUVhMlXM
Perbarui setelah dijawab
Terima kasih banyak @ Jo Obbish
Anda benar tentang masalah permintaan ini yaitu tentang antara DsJobStat dan DsAvg. Ini tidak banyak tentang cara BERGABUNG dan tidak menggunakan TIDAK DI.
Memang ada meja seperti yang Anda duga.
CREATE TABLE [dbo].[DSJobNames](
[JobName] [nvarchar](255) NOT NULL,
CONSTRAINT [DSJobNames$PrimaryKey] PRIMARY KEY CLUSTERED
( [JobName] ASC
) );
Saya mencoba saran Anda,
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;
Pesan eksekusi:
(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.
Rencana eksekusi: https://www.brentozar.com/pastetheplan/?id=rJVkLSZ7f
Jawaban:
Mari kita mulai dengan mempertimbangkan pesanan bergabung. Anda memiliki tiga referensi tabel dalam kueri. Pesanan bergabung mana yang mungkin memberi Anda kinerja terbaik? Pengoptimal kueri berpikir bahwa bergabung dari
DsJobStat
keDsAvg
akan menghilangkan hampir semua baris (perkiraan kardinalitas turun dari 212195000 ke 1 baris). Rencana aktual menunjukkan kepada kita bahwa perkiraan tersebut cukup dekat dengan kenyataan (11 baris bertahan untuk bergabung). Namun, join diimplementasikan sebagai join anti semi-gabung yang tepat, sehingga semua 212 juta baris dariDsJobStat
tabel dipindai hanya untuk menghasilkan 11 baris. Itu tentu saja dapat berkontribusi pada waktu eksekusi permintaan yang panjang, tetapi saya tidak dapat memikirkan operator fisik atau logis yang lebih baik untuk sambungan yang akan lebih baik. Saya yakin ituDJS_Dashboard_2
indeks digunakan untuk pertanyaan lain, tetapi semua kunci tambahan dan kolom yang disertakan hanya akan membutuhkan lebih banyak IO untuk permintaan ini dan memperlambat Anda. Jadi Anda berpotensi memiliki masalah akses tabel dengan pemindaian indeks di atasDsJobStat
meja.Saya akan berasumsi bahwa bergabung
AJF
tidak terlalu selektif. Saat ini tidak relevan dengan masalah kinerja yang Anda lihat dalam kueri, jadi saya akan mengabaikannya untuk sisa jawaban ini. Itu bisa berubah jika data dalam tabel berubah.Masalah lain yang jelas dari rencana tersebut adalah operator spool count row. Ini adalah operator yang sangat ringan tetapi menjalankan lebih dari 200 juta kali. Operator ada di sana karena permintaan ditulis dengan
NOT IN
. Jika ada satu baris NULL diDsAvg
semua baris harus dihilangkan. Kumparan adalah implementasi dari pemeriksaan itu. Itu mungkin bukan logika yang Anda inginkan, jadi sebaiknya Anda menulis bagian yang akan digunakanNOT EXISTS
. Manfaat sebenarnya dari penulisan ulang itu tergantung pada sistem dan data Anda.Saya membuat beberapa data berdasarkan rencana kueri untuk menguji beberapa penulisan ulang kueri. Definisi tabel saya sangat berbeda dengan definisi Anda karena terlalu banyak upaya untuk membuat data untuk setiap kolom. Bahkan dengan struktur data yang disingkat saya dapat mereproduksi masalah kinerja yang Anda alami.
Berdasarkan rencana kueri, kita dapat melihat bahwa ada sekitar 200000
JobName
nilai unik dalamDsAvg
tabel. Berdasarkan jumlah baris aktual setelah bergabung dengan tabel itu, kita dapat melihat bahwa hampir semuaJobName
nilai diDsJobStat
juga dalamDsAvg
tabel. Dengan demikian,DsJobStat
tabel memiliki 20.000 nilai unik untukJobName
kolom dan 1000 baris per nilai.Saya percaya bahwa kueri ini mewakili masalah kinerja:
Semua hal lain dalam rencana kueri Anda (
GROUP BY
,,HAVING
gabung gaya kuno, dll) terjadi setelah set hasil dikurangi menjadi 11 baris. Saat ini tidak masalah dari sudut pandang kinerja permintaan, tetapi mungkin ada masalah lain di sana yang bisa diungkapkan oleh data yang berubah di tabel Anda.Saya menguji di SQL Server 2017, tapi saya mendapatkan bentuk rencana dasar yang sama seperti Anda:
Di mesin saya, permintaan itu membutuhkan 62219 ms waktu CPU dan 65576 ms waktu berlalu untuk mengeksekusi. Jika saya menulis ulang kueri untuk digunakan
NOT EXISTS
:Spool tidak lagi dieksekusi 212 juta kali dan mungkin memiliki perilaku yang diinginkan dari vendor. Sekarang kueri dieksekusi dalam 34516 ms waktu CPU dan 41132 ms waktu berlalu. Sebagian besar waktu dihabiskan memindai 212 juta baris dari indeks.
Pemindaian indeks itu sangat disayangkan untuk permintaan itu. Rata-rata kita memiliki 1000 baris per nilai unik
JobName
, tetapi kita tahu setelah membaca baris pertama jika kita membutuhkan 1000 baris sebelumnya. Kami hampir tidak pernah membutuhkan baris-baris itu, tetapi kami masih perlu memindai mereka. Jika kita tahu bahwa baris-barisnya tidak terlalu padat dalam tabel dan bahwa hampir semuanya akan dihilangkan dengan join, kita dapat membayangkan pola IO yang mungkin lebih efisien pada indeks. Bagaimana jika SQL Server membaca baris pertama per nilai unikJobName
, memeriksa apakah nilai itu masukDsAvg
, dan hanya melompat ke depan ke nilai selanjutnyaJobName
jika itu? Alih-alih memindai 212 juta baris, rencana pencarian yang membutuhkan sekitar 200 ribu eksekusi dapat dilakukan sebagai gantinya.Ini sebagian besar dapat dicapai dengan menggunakan rekursi bersama dengan teknik yang dipelopori Paul White yang dijelaskan di sini . Kita dapat menggunakan rekursi untuk melakukan pola IO yang saya jelaskan di atas:
Permintaan itu banyak untuk dilihat, jadi saya sarankan hati-hati memeriksa rencana yang sebenarnya . Pertama-tama kita melakukan 200002 indeks berusaha melawan indeks
DsJobStat
untuk mendapatkan semua nilai unikJobName
. Kemudian kita bergabungDsAvg
dan menghilangkan semua baris kecuali satu. Untuk baris yang tersisa, gabung kembali keDsJobStat
dan dapatkan semua kolom yang diperlukan.Pola IO benar-benar berubah. Sebelum kita dapatkan ini:
Dengan permintaan rekursif kami mendapatkan ini:
Di komputer saya, kueri baru dijalankan hanya dalam 6891 ms waktu CPU dan 7107 ms berlalu waktu. Perhatikan bahwa perlu menggunakan rekursi dengan cara ini menunjukkan bahwa ada sesuatu yang hilang dari model data (atau mungkin itu hanya tidak disebutkan dalam pertanyaan yang diposting). Jika ada tabel yang relatif kecil yang berisi semua kemungkinan
JobNames
, akan jauh lebih baik untuk menggunakan tabel itu daripada rekursi di meja besar. Intinya adalah jika Anda memiliki set hasil yang berisi semuaJobNames
yang Anda butuhkan maka Anda dapat menggunakan indeks berusaha untuk mendapatkan sisa kolom yang hilang. Namun, Anda tidak dapat melakukannya dengan set hasilJobNames
yang TIDAK Anda perlukan.sumber
NOT EXISTS
. Mereka sudah menjawab dengan, "Saya sudah mencoba keduanya, bergabung dan tidak ada, sebelum saya memposting pertanyaan. Tidak banyak perbedaan."Lihat apa yang terjadi jika Anda menulis ulang kondisinya,
Untuk
Juga pertimbangkan untuk menulis ulang bergabung SQL89 Anda karena gaya itu mengerikan.
Dari pada
Mencoba
Saya juga curiga bahwa kondisi ini dapat ditulis lebih baik tetapi kita harus tahu lebih banyak tentang apa yang terjadi
Apakah Anda benar-benar harus tahu rata-rata bukan nol, atau hanya satu unsur dari kelompok itu bukan nol?
sumber