Saya membuat data uji yang sebagian besar mereproduksi masalah Anda:
INSERT INTO [dbo].[TestTable] WITH (TABLOCK)
SELECT TOP (7000000) N'*NOT GDPR*', N'*NOT GDPR*', N'*NOT GDPR*', 0, DATEADD(DAY, q.RN / 16965, '20160801')
FROM
(
SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) RN
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
) q
ORDER BY q.RN
OPTION (MAXDOP 1);
DROP INDEX IF EXISTS [dbo].[TestTable].IX_TestTable_Date;
CREATE NONCLUSTERED INDEX IX_TestTable_Date ON [dbo].[TestTable] ([Date]);
Statistik untuk kueri yang menggunakan indeks nonclustered:
Tabel 'TestTable'. Pindai hitungan 1, bacaan logis 1299838, bacaan fisik 0, bacaan baca depan 0, bacaan logis lob 0, bacaan fisik lob 0, bacaan baca lob depan 0.
Waktu Eksekusi SQL Server: Waktu CPU = 984 ms, waktu yang berlalu = 988 ms.
Statistik untuk kueri yang menggunakan indeks berkerumun:
Tabel 'TestTable'. Pindai hitungan 1, bacaan logis 72609, bacaan fisik 0, bacaan baca-depan 0, bacaan logis lob 0, bacaan fisik lob 0, bacaan lob baca-depan 0.
Waktu Eksekusi SQL Server: Waktu CPU = 781 ms, waktu yang berlalu = 772 ms.
Mendapatkan pertanyaan Anda:
Mungkinkah memanfaatkan fakta ini untuk meningkatkan kinerja kueri saya?
Iya. Anda dapat menggunakan indeks nonclustered yang sudah Anda miliki untuk secara efisien menemukan nilai maksimum id
yang perlu diperbarui. Jika Anda menyimpannya ke variabel dan menyaringnya, Anda akan mendapatkan paket permintaan pembaruan yang melakukan pemindaian indeks berkerumun (tanpa pengurutan) yang berhenti lebih awal dan karenanya mengurangi IO. Inilah satu implementasi:
DECLARE @Id INT;
SELECT TOP (1) @Id = Id
FROM dbo.TestTable
WHERE [Date] <= '25 August 2016'
ORDER BY [Date] DESC, Id DESC;
UPDATE TestTable
SET TestCol='*GDPR*', TestCol2='*GDPR*', TestCol3='*GDPR*', Anonymised=1
WHERE [Id] < @Id AND [Date] <= '25 August 2016'
AND [Anonymised] <> 1 -- optional
OPTION (MAXDOP 1);
Jalankan statistik untuk kueri baru:
Tabel 'TestTable'. Pindai hitungan 1, bacaan logis 3, bacaan fisik 0, bacaan baca depan 0, bacaan logis lob 0, bacaan fisik lob 0, bacaan baca lob depan 0.
Tabel 'TestTable'. Pindai hitungan 1, bacaan logis 4776, bacaan fisik 0, bacaan baca depan 0, bacaan logis lob 0, bacaan fisik lob 0, bacaan baca lob depan 0.
Waktu Eksekusi SQL Server: Waktu CPU = 515 ms, waktu yang berlalu = 510 ms.
Serta rencana kueri:
Dengan semua itu, keinginan Anda untuk membuat kueri lebih cepat menunjukkan kepada saya bahwa Anda berencana untuk menjalankan kueri lebih dari satu kali. Saat ini permintaan Anda memiliki filter ujung terbuka pada date
kolom. Apakah benar-benar perlu menganonimkan baris lebih dari satu kali? Bisakah Anda menghindari memperbarui atau memindai baris yang sudah dianonimkan? Tentunya akan lebih cepat untuk memperbarui berbagai tanggal dengan tanggal di kedua sisi itu. Anda juga bisa menambahkan Anonymised
kolom ke indeks Anda, tetapi indeks itu perlu diperbarui selama UPDATE
permintaan Anda . Singkatnya, hindari memproses data yang sama berulang kali jika Anda bisa.
Kueri asli yang Anda miliki dengan pengurutan lebih lambat karena pekerjaan yang dilakukan di Clustered Index Update
operator. Jumlah waktu yang dihabiskan untuk pencarian indeks dan jenisnya hanya 407 ms. Anda dapat melihat ini dalam rencana aktual. Paket dijalankan dalam mode baris sehingga waktu yang dihabiskan untuk pengurutan adalah waktu dari operator itu bersama dengan setiap operator anak:
Itu membuat operator sortir sekitar 1600 ms waktu. SQL Server perlu membaca halaman dari indeks berkerumun untuk melakukan pembaruan. Anda dapat melihat bahwa Clustered Index Update
operator melakukan 1205921 pembacaan logis. Anda dapat membaca lebih lanjut tentang pengurutan optimasi untuk DML dan dioptimalkan pengambilan di posting blog ini oleh Paul White .
Paket kueri lain yang Anda miliki (tanpa pengurutan) membutuhkan 683 ms untuk pemindaian indeks berkelompok dan sekitar 550 ms untuk Clustered Index Update
operator. Operator pembaruan tidak melakukan IO apa pun untuk kueri ini.
Jawaban sederhana mengapa rencana dengan pengurutan lebih lambat adalah bahwa SQL Server membaca lebih logis pada indeks berkerumun untuk rencana itu dibandingkan dengan rencana pemindaian indeks berkerumun. Bahkan jika semua data yang dibutuhkan ada di memori, masih ada overhead dan biaya untuk melakukan pembacaan logis. Jawaban yang lebih baik jauh lebih sulit didapat, sejauh yang saya tahu rencana tidak akan memberi Anda rincian lebih lanjut. Dimungkinkan untuk menggunakan PerfView atau alat lain berdasarkan pelacakan ETW untuk membandingkan tumpukan panggilan antara permintaan:
Di sebelah kiri adalah permintaan yang melakukan pemindaian indeks berkerumun dan di sebelah kanan adalah permintaan yang melakukan pengurutan. Saya menandai tumpukan panggilan dengan warna biru atau merah yang hanya muncul dalam satu permintaan. Tidak mengherankan, tumpukan panggilan yang berbeda dengan jumlah besar siklus CPU sampel untuk permintaan pengurutan tampaknya berkaitan dengan pembacaan logis yang diperlukan untuk melakukan pembaruan pada indeks berkerumun. Selain itu, ada perbedaan dalam jumlah siklus sampel antara permintaan untuk operasi yang sama. Untuk sampel, kueri dengan pengurutan menghabiskan 31 siklus memperoleh kait sedangkan permintaan dengan pemindaian hanya menghabiskan 9 siklus memperoleh kait.
Saya menduga bahwa SQL Server memilih rencana yang lebih lambat karena keterbatasan biaya rencana operator paket. Mungkin bagian dari perbedaan waktu berjalan adalah karena perangkat keras atau edisi SQL Server Anda. Dalam kasus apa pun, SQL Server tidak dapat mengetahui bahwa kolom tanggal secara implisit dipesan persis sama dengan indeks berkerumun. Data dikembalikan dari pemindaian indeks berkerumun dalam urutan kunci berkerumun, sehingga tidak perlu melakukan pengurutan dalam upaya untuk mengoptimalkan IO ketika melakukan pembaruan indeks berkerumun.