Karena keterampilan penyesuaian kinerja saya sepertinya tidak pernah terasa cukup, saya selalu bertanya-tanya apakah ada lebih banyak optimasi yang dapat saya lakukan terhadap beberapa pertanyaan. Situasi yang terkait dengan pertanyaan ini adalah fungsi Windowed MAX yang bersarang di dalam subquery.
Data yang saya gali adalah serangkaian transaksi pada berbagai kelompok set yang lebih besar. Saya memiliki 4 bidang penting, ID unik transaksi, ID Grup dari sekelompok transaksi, dan tanggal yang terkait dengan transaksi unik atau grup transaksi masing-masing. Paling sering, Tanggal Grup cocok dengan Tanggal Transaksi Unik Maksimum untuk suatu Batch, tetapi ada kalanya penyesuaian manual datang melalui sistem kami dan operasi tanggal unik terjadi setelah tanggal transaksi grup diambil. Hasil edit manual ini tidak menyesuaikan tanggal grup berdasarkan desain.
Apa yang saya identifikasi dalam kueri ini adalah catatan di mana Tanggal Unik jatuh setelah Tanggal Grup. Kueri sampel berikut membangun setara kasar dari skenario saya dan pernyataan SELECT mengembalikan catatan yang saya cari, namun, apakah saya mendekati solusi ini dengan cara yang paling efisien? Ini memakan waktu cukup lama untuk dijalankan selama tabel fakta saya memuat karena catatan saya menghitung angka di 9 digit atas, tetapi kebanyakan penghinaan saya untuk subqueries membuat saya bertanya-tanya apakah ada pendekatan yang lebih baik di sini. Saya tidak khawatir tentang indeks apa pun karena saya yakin sudah ada di tempatnya; apa yang saya cari adalah pendekatan kueri alternatif yang akan mencapai hal yang sama, tetapi bahkan lebih efisien. Setiap umpan balik diterima.
CREATE TABLE #Example
(
UniqueID INT IDENTITY(1,1)
, GroupID INT
, GroupDate DATETIME
, UniqueDate DATETIME
)
CREATE CLUSTERED INDEX [CX_1] ON [#Example]
(
[UniqueID] ASC
)
SET NOCOUNT ON
--Populate some test data
DECLARE @i INT = 0, @j INT = 5, @UniqueDate DATETIME, @GroupDate DATETIME
WHILE @i < 10000
BEGIN
IF((@i + @j)%173 = 0)
BEGIN
SET @UniqueDate = GETDATE()+@i+5
END
ELSE
BEGIN
SET @UniqueDate = GETDATE()+@i
END
SET @GroupDate = GETDATE()+(@j-1)
INSERT INTO #Example (GroupID, GroupDate, UniqueDate)
VALUES (@j, @GroupDate, @UniqueDate)
SET @i = @i + 1
IF (@i % 5 = 0)
BEGIN
SET @j = @j+5
END
END
SET NOCOUNT OFF
CREATE NONCLUSTERED INDEX [IX_2_4_3] ON [#Example]
(
[GroupID] ASC,
[UniqueDate] ASC,
[GroupDate] ASC
)
INCLUDE ([UniqueID])
-- Identify any UniqueDates that are greater than the GroupDate within their GroupID
SELECT UniqueID
, GroupID
, GroupDate
, UniqueDate
FROM (
SELECT UniqueID
, GroupID
, GroupDate
, UniqueDate
, MAX(UniqueDate) OVER (PARTITION BY GroupID) AS maxUniqueDate
FROM #Example
) calc_maxUD
WHERE maxUniqueDate > GroupDate
AND maxUniqueDate = UniqueDate
DROP TABLE #Example
Aku di sini
sumber
Jawaban:
Saya berasumsi tidak ada indeks, karena Anda belum memberikannya.
Langsung saja, indeks berikut ini akan menghilangkan operator Sortir dalam paket Anda, yang jika tidak akan berpotensi menghabiskan banyak memori:
Subquery bukan masalah kinerja dalam hal ini. Jika ada, saya akan mencari cara untuk menghilangkan fungsi jendela (MAX ... LEBIH) untuk menghindari konstruksi Nested Loop dan Table Spool.
Dengan indeks yang sama, permintaan berikut pada pandangan pertama mungkin terlihat kurang efisien, dan itu pergi dari dua hingga tiga pemindaian pada tabel dasar, tetapi menghilangkan sejumlah besar pembacaan secara internal karena tidak memiliki operator Spool. Saya menduga itu masih akan berkinerja lebih baik, terutama jika Anda memiliki core CPU dan kinerja IO yang cukup di server Anda:
(Catatan: Saya menambahkan
MERGE JOIN
petunjuk permintaan, tetapi ini mungkin akan terjadi secara otomatis jika statistik Anda sesuai. Praktik terbaik adalah meninggalkan petunjuk seperti ini jika Anda bisa.)sumber
Kapan dan jika Anda dapat memutakhirkan dari SQL Server 2012 ke SQL Server 2016, Anda mungkin dapat memanfaatkan kinerja yang jauh lebih baik (terutama untuk agregat jendela tanpa bingkai) yang disediakan oleh mode batch baru Window Aggregate operator.
Hampir semua skenario pemrosesan data besar bekerja lebih baik dengan penyimpanan columnstore daripada rowstore. Bahkan tanpa mengubah ke columnstore untuk tabel dasar Anda, Anda masih dapat memperoleh manfaat dari operator baru 2016 dan eksekusi mode batch dengan membuat indeks yang disaring kolomstore kosong yang tidak tercakup di salah satu tabel dasar, atau dengan bergabung secara berlebihan di luar ke suatu columnstore yang diorganisir meja.
Menggunakan opsi kedua, kueri menjadi:
db <> biola
Perhatikan satu-satunya perubahan ke kueri asli adalah membuat tabel sementara yang kosong dan menambahkan gabungan kiri. Rencana pelaksanaannya adalah:
Untuk informasi dan opsi lebih lanjut, lihat seri Itzik Ben-Gan yang hebat, Apa yang Perlu Anda Ketahui tentang Agregat Jendela Batch Mode di SQL Server 2016 (dalam tiga bagian).
sumber
Aku hanya akan membuang ol 'Cross. Melamar di luar sana:
Dengan beberapa indeks apa pun, itu cukup baik.
Waktu statistik dan io terlihat seperti ini (permintaan Anda adalah hasil pertama)
Paket kueri ada di sini (lagi, milikmu adalah yang pertama):
https://www.brentozar.com/pastetheplan/?id=BJYJvqAal
Kenapa saya lebih suka versi ini? Saya menghindari gulungan. Jika mereka mulai tumpah ke disk, itu akan menjadi jelek.
Tetapi Anda mungkin ingin mencobanya juga.
Jika ini adalah DW besar, Anda mungkin lebih suka Hash Join, dan pemfilteran baris dalam join, daripada di akhir dalam
TOP 1
kueri sebagai operator Filter.Paket ada di sini: https://www.brentozar.com/pastetheplan/?id=BkUF55ATx
Waktu statistik dan io di sini:
Semoga ini membantu!
Satu edit, berdasarkan ide @ ypercube, dan indeks baru.
Inilah statistik waktu dan io:
Inilah rencananya:
https://www.brentozar.com/pastetheplan/?id=SJv8foR6g
sumber
Saya akan melihat
top with ties
Jika
GroupDate
sama perGroupId
maka:Lain: gunakan
top with ties
dalam ekspresi tabel umumdbfiddle: http://dbfiddle.uk/?rdbms=sqlserver_2016&fiddle=c058994c2f5f3d99b212f06e1dae9fd3
Permintaan Asli
vs
top with ties
dalam ekspresi tabel umumsumber
Jadi saya melakukan beberapa analisis pada berbagai pendekatan yang diposting sejauh ini, dan di lingkungan saya, sepertinya pendekatan Daniel menang secara konsisten pada waktu eksekusi. Anehnya (bagi saya) pendekatan CROSS APPLY ketiga sp_BlitzErik tidak jauh di belakang. Di sini ada keluaran jika ada yang tertarik, tapi terima kasih banyak untuk semua pendekatan alternatif. Saya belajar lebih banyak dari menggali jawaban atas pertanyaan ini daripada yang saya miliki cukup lama!
sumber
top with ties
gesper saya dengan banyak baris. dbfiddle.uk/…