Data secara inheren dipesan seolah-olah itu adalah indeks berkerumun

8

Saya memiliki tabel berikut dengan 7,5 juta catatan:

CREATE TABLE [dbo].[TestTable](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [TestCol] [nvarchar](50) NOT NULL,
    [TestCol2] [nvarchar](50) NOT NULL,
    [TestCol3] [nvarchar](50) NOT NULL,
    [Anonymised] [tinyint] NOT NULL,
    [Date] [datetime] NOT NULL,
CONSTRAINT [PK_TestTable] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, 
ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]

Saya perhatikan bahwa ketika ada indeks non-cluster di bidang tanggal:

CREATE NONCLUSTERED INDEX IX_TestTable_Date ON [dbo].[TestTable] ([Date])

-dan saya menjalankan kueri berikut:

UPDATE TestTable 
SET TestCol='*GDPR*', TestCol2='*GDPR*', TestCol3='*GDPR*', Anonymised=1
WHERE [Date] <= '25 August 2016'

- data yang dikembalikan oleh operasi akses indeks diurutkan agar sesuai dengan urutan utama PK / CX, mengurangi kinerja.

Rencana kueri

Saya terkejut menemukan bahwa menghapus indeks dari bidang tanggal sebenarnya meningkatkan kinerja kueri sekitar 30% karena tidak lagi melakukan pengurutan:

Rencana kueri

Teori saya, dan ini mungkin jelas bagi yang lebih berpengalaman di antara Anda, adalah telah menemukan bahwa kolom tanggal secara tersirat dipesan persis sama dengan kunci utama / indeks berkerumun.

Jadi pertanyaan saya adalah: Apakah mungkin untuk mengambil keuntungan dari fakta ini untuk meningkatkan kinerja permintaan saya?

AproposArmadillo
sumber
1
Saya belum melihat rencana tetapi saya akan curiga kinerja (baik, durasi, tidak ada angka% biaya taksiran yang berguna) membaik karena tidak lagi harus memperbarui indeks yang Anda hapus, bukan karena operasi semacam.
Aaron Bertrand
@ AaronBertrand Saya mungkin salah membaca ini, jadi tolong perbaiki saya jika saya salah, tetapi tampaknya ada operasi pembaruan indeks di kedua paket kueri. Apakah Anda mengacu pada sesuatu yang lain?
AproposArmadillo
1
Sekali lagi, saya katakan, saya tidak melihat rencananya. Anda mengatakan "menghapus indeks dari bidang tanggal meningkatkan kinerja kueri" ... jika Anda menghapus indeks, itu tidak akan muncul dalam rencana, jadi mungkin Anda telah mengumpulkan rencana yang salah atau tidak benar-benar menghapus indeks yang Anda pikir Anda lakukan. Dan sekali lagi, beberapa% yang diperkirakan untuk suatu rencana adalah indikator tetapi sebenarnya tidak mencerminkan pengukuran kinerja sebenarnya dengan cara apa pun. Ini adalah perkiraan yang dihitung sebelum kueri berjalan.
Aaron Bertrand
@ Harun Bertrand, toh itu tidak harus memperbarui indeks, karena [Tanggal] tidak ada di antara bidang yang diperbarui.
Denis Rubashkin
1
@ Shaffanhoon Sudahkah Anda mencoba membuat ulang indeks [Date]tetapi dalam DESCurutan? Penasaran saja karena predikatnya adalah <=. Juga, jika indeks aktif Date(dalam ACSurutan default ) membantu pertanyaan lain, maka mungkin Anda dapat mencoba menambahkan petunjuk tabel ke UPDATE untuk memaksanya menggunakan PK? Atau, mungkin pilah ini menjadi dua bagian: membuat tabel temp, mengisi dengan [Id]berdasarkan [Date] <= '25 August 2016', dan kemudian menghapus WHEREdari UPDATE dan menambahkan FROM dbo.TestTable tt INNER JOIN #tmp ids ON ids.[Id] = tt.[Id]. Bagaimanapun, ini adalah PEMBARUAN, dan perlu menemukan baris aktual, indeks atau tidak.
Solomon Rutzky

Jawaban:

7

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

rencana permintaan ok

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 datekolom. 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 Anonymisedkolom ke indeks Anda, tetapi indeks itu perlu diperbarui selama UPDATEpermintaan 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 Updateoperator. 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:

masukkan deskripsi gambar di sini

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 Updateoperator 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 Updateoperator. 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:

masukkan deskripsi gambar di sini

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.

Joe Obbish
sumber