Urutan bidang dalam urutan indeks komposit dengan bidang selektivitas tinggi dan selektivitas rendah

11

Saya memiliki tabel SQL Server dengan lebih dari 3 miliar baris. Salah satu permintaan saya membutuhkan waktu yang sangat lama sehingga saya mempertimbangkan untuk mengoptimalkannya. Kueri terlihat seperti ini:

SELECT [Enroll_Date]
      ,Count(*) AS [Record #]
      ,Count(Distinct UserID) AS [User #]
  FROM UserTable
  GROUP BY [Enroll_Date]

[Enroll_Date] adalah kolom selektivitas rendah dengan kurang dari 50 nilai yang mungkin, sedangkan kolom UserID adalah kolom selektivitas tinggi dengan lebih dari 200 juta nilai berbeda. Berdasarkan penelitian saya, saya percaya saya harus membuat indeks komposit non-cluster pada dua kolom ini, dan secara teori kolom selektivitas tinggi harus menjadi kolom pertama. Tetapi saya tidak yakin dalam kasus saya, apakah itu akan berhasil karena saya menggunakan kolom selektivitas rendah dalam grup dengan klausa.

Tabel ini tidak memiliki indeks berkerumun.

Thinkinger
sumber
Bisakah Anda memposting rencana eksekusi yang sebenarnya xml (gunakan pastebin dan tautkan di sini)? Versi server sql apa yang Anda gunakan?
Kin Shah
3
Indeks dengan kolom yang sangat selektif pertama tidak akan berguna untuk permintaan tertentu.
ypercubeᵀᴹ
Ini adalah praktik terbaik untuk menggunakan kolom selektivitas yang lebih tinggi sebagai kolom kunci pertama dalam indeks (biasanya). Dalam skenario ini, seperti yang Anda duga, itu sama sekali tidak membantu Anda. Anda mungkin membutuhkan dua indeks! Apa yang terjadi ketika Anda menggunakan register_date pertama dan user_id kedua?
paulbarbin

Jawaban:

12

Sebagai alternatif untuk solusi @ AaronBertrand (jika Anda tidak dapat atau tidak ingin membuat tampilan yang diindeks), saya akan merekomendasikan Anda untuk membuat indeks (Enroll_Date, UserID). Jika jenis pertanyaan ini sangat umum di meja Anda, ini mungkin seharusnya menjadi indeks cluster Anda.

Saya biasanya tidak akan merekomendasikan indeks selektivitas tinggi sebagai "praktik terbaik" umum, tetapi melihat indeks apa yang akan memberikan kinerja terbaik pada permintaan Anda.

Indeks aktif (Enroll_Date, UserID)akan memberikan kueri Anda rencana kueri yang sangat dioptimalkan, tanpa pemblokiran dengan Agregat Stream.

Streaming rencana permintaan agregat

"Non-blocking" dalam konteks ini berarti bahwa kueri tidak perlu buffer sejumlah data yang signifikan (seperti, misalnya, semacam atau agregat hash akan), yang berarti itu (a) mulai mengembalikan baris segera, dan ( b) hampir tidak menggunakan memori yang bekerja.

Daniel Hutmacher
sumber
Lucu, terpisah 4 detik dan jawaban yang sama.
usr
11

Jawaban Aarons adalah solusi yang bagus. Saya akan menjawab pertanyaan dengan asumsi Anda tidak ingin mengambil pendekatan itu.

Kueri yang Anda poskan biasanya akan dieksekusi dengan pengelompokan pertama aktif (Enroll_Date, UserID), lalu lagi pada (Enroll_Date). Optimasi ini baru untuk SQL Server 2012. Ini berlaku jika ada satu COUNT DISTINCT.

Indeks pada dua kolom dalam urutan tertentu (Enroll_Date, UserID)akan cukup untuk mendapatkan rencana efisien yang menggerakkan pemindaian indeks ke dua Agregat Stream berturut-turut. Urutan sebaliknya tidak akan memungkinkan rencana itu.

Karena itu, gunakan perintah (Enroll_Date, UserID). Anda tidak punya pilihan di sini.

usr
sumber
5 detik terpisah dan solusi yang sama. Bermain bagus, tuan. :)
Daniel Hutmacher
@DanielHutmacher OMG, akankah kita mengatur hampir mencocokkan posting kami untuk ketiga kalinya ?! Memberi +1 kepada Anda! Bagaimana mungkin saya tidak menjawab dengan jawaban yang identik?
usr
Kesalahan dalam Matriks. :)
Daniel Hutmacher
Terima kasih banyak. Saya membuat indeks dan akan mengirim peningkatan setelah selesai. Versi servernya adalah Microsoft SQL Server 2008 R2 pada AWS, tapi saya kira itu tetap satu-satunya pilihan.
Thinkinger
@ Berpikir jika Anda tidak menerima pendekatan Aarons Anda punya pilihan yang sulit :)
usr
11

Kedengarannya seperti skenario ideal untuk tampilan yang diindeks, yang memungkinkan Anda membayar untuk perhitungan dan agregat pada waktu penulisan, bukan waktu permintaan.

CREATE VIEW dbo.MyIndexedView
WITH SCHEMABINDING
AS 
  SELECT Enroll_Date, UserID, RawCount = COUNT_BIG(*)
  FROM dbo.UserTable
  GROUP BY Enroll_Date, UserID;
GO

CREATE UNIQUE CLUSTERED INDEX CIX_miv ON dbo.MyIndexedView(Enroll_Date, UserID);

Itu akan membutuhkan waktu untuk dibuat, dan tentu saja akan membutuhkan pemeliharaan di seluruh operasi DML, seperti halnya indeks pada tabel dasar.

Sekarang kueri terhadap tampilan ini akan sangat mirip - setiap baris dalam tampilan sekarang mewakili kombo pengguna / tanggal yang berbeda, sehingga angka tersebut dapat dihitung dengan satu COUNT (*), sedangkan jumlah total baris dalam tabel dasar adalah sudah sebagian dikumpulkan untuk Anda, sekarang Anda hanya perlu menambahkannya menggunakan SUM per tanggal:

SELECT Enroll_Date, 
  [Record #] = SUM(RawCount),
  [User #] = COUNT(*)
FROM dbo.MyIndexedView WITH (NOEXPAND)
GROUP BY Enroll_Date; 

Menambahkan petunjuk NOEXPAND, setelah mengingat ini dan ini .

Saya dapat memberi tahu Anda tanpa keraguan bahwa kueri ini akan lebih cepat daripada kueri Anda saat ini (tetapi tidak seberapa banyak), kecuali dalam kasus langka di mana Anda memiliki tepat satu pengguna untuk setiap tanggal (dalam hal ini jumlah data yang sama akan memiliki untuk dibaca) dan kolom yang kita ketahui adalah satu-satunya kolom dalam indeks tabel dasar. Apakah peningkatan kinerja pada waktu baca sepadan dengan kerja ekstra yang akan memengaruhi porsi penulisan dari beban kerja Anda adalah sesuatu yang tidak dapat kami beritahukan kepada Anda - Anda harus mengujinya untuk mengukur trade-off (tidak ada indeks gratis).

Dan jika Anda sering menggunakan klausa WHERE umum yang sama terhadap Enroll_Date untuk rentang yang spesifik dan terdefinisi dengan baik (katakanlah, kuartal saat ini atau tahun ini), Anda bisa menambahkan indeks yang cocok yang disaring yang mengurangi I / O lebih jauh (tapi selalu ada trade-off).

Anda mungkin juga mempertimbangkan untuk meletakkan indeks berkerumun di tabel dasar. Ini sepertinya bukan salah satu dari kasus penggunaan yang sangat jarang yang diuntungkan oleh tumpukan.

Aaron Bertrand
sumber
Saya baru saja mengkonfirmasi dengan IT kami dan sepertinya saya tidak bisa membuat pandangan seperti ini. Tetapi tetap berikan saran Anda, dan itu akan membantu orang lain yang dapat menggunakannya.
Thinkinger
1
Apakah IT Anda berpikir ada perbedaan yang signifikan antara tampilan diindeks dan indeks tambahan atau berbeda di tabel dasar? Tidak bersikap agresif, hanya ingin tahu, karena banyak orang memiliki kesalahpahaman tentang pandangan yang diindeks. Saya suka menganggap mereka sebagai tambahan, indeks berkerut lebih kurus di atas meja, tetapi dengan lebih sedikit baris.
Aaron Bertrand
@Pikirkan juga, tampilan yang diindeks tidak hanya untuk EE. Pencocokan tampilan yang diindeks hanya untuk EE. Anda dapat langsung menargetkan mereka menggunakan NOEXPAND.
usr