Menyaring Set Besar Dengan Efisien secara efisien

9

Katakanlah saya punya satu meja

CREATE TABLE Ticket (
    TicketId int NOT NULL,
    InsertDateTime datetime NOT NULL,
    SiteId int NOT NULL,
    StatusId tinyint NOT NULL,
    AssignedId int NULL,
    ReportedById int NOT NULL,
    CategoryId int NULL
);

Dalam contoh ini TicketIdadalah Kunci Utama.

Saya ingin pengguna dapat membuat kueri "sebagian ad-hoc" terhadap tabel ini. Saya katakan sebagian karena beberapa bagian dari query akan selalu diperbaiki:

  1. Kueri akan selalu melakukan filter rentang pada InsertDateTime
  2. Permintaan akan selalu ORDER BY InsertDateTime DESC
  3. Permintaan akan hasil halaman

Pengguna dapat memfilter secara opsional pada salah satu kolom lainnya. Mereka dapat memfilter tanpa, satu, atau banyak. Dan untuk setiap kolom pengguna dapat memilih dari serangkaian nilai yang akan diterapkan sebagai disjungsi. Sebagai contoh:

SELECT
    TicketId
FROM (
    SELECT
        TicketId,
        ROW_NUMBER() OVER(ORDER BY InsertDateTime DESC) as RowNum
    FROM Ticket
    WHERE InsertDateTime >= '2013-01-01' AND InsertDateTime < '2013-02-01'
      AND StatusId IN (1,2,3)
      AND (CategoryId IN (10,11) OR CategoryId IS NULL)
    ) _
WHERE RowNum BETWEEN 1 AND 100;

Sekarang asumsikan tabel memiliki 100.000.000 baris.

Yang terbaik yang bisa saya dapatkan adalah indeks penutup yang mencakup masing-masing kolom "opsional":

CREATE NONCLUSTERED INDEX IX_Ticket_Covering ON Ticket (
    InsertDateTime DESC
) INCLUDE (
    SiteId, StatusId, AssignedId, ReportedById, CategoryId
);

Ini memberi saya rencana permintaan sebagai berikut:

  • PILIH
    • Saring
      • Teratas
        • Proyek Urutan (Hitung Skalar)
          • Segmen
            • Indeks mencari

Sepertinya cukup bagus. Sekitar 80% -90% dari biaya berasal dari operasi Index Seek, yang ideal.

Adakah strategi yang lebih baik untuk menerapkan pencarian semacam ini?

Saya tidak perlu ingin melepas pemfilteran opsional ke klien karena dalam beberapa kasus hasil yang ditetapkan dari bagian "tetap" bisa 100s atau 1000s. Klien kemudian juga akan bertanggung jawab untuk menyortir dan paging yang mungkin terlalu banyak bekerja untuk klien.

Joseph Daigle
sumber
Apakah mungkin untuk menempatkan subquery Anda ke tabel temp atau variabel tabel dan membangun seperti itu? Dengan meja saya yang lebih besar, terkadang saya disengat oleh subqueries. Menutup indeks hanya membawa Anda sejauh ini.
Valkyrie
@ Valkyrie yang tampaknya sangat tidak efisien. Juga pertimbangkan bahwa varian kueri ini (parameter berbeda dan opsi opsional berbeda di mana klausa) kemungkinan akan mengeksekusi beberapa kali per detik sepanjang hari dan perlu mengembalikan hasil rata-rata dalam waktu kurang dari 100 ms. Kami sudah melakukan ini, dan kinerjanya baik-baik saja untuk saat ini. Saya hanya mencari ide tentang bagaimana terus meningkatkan kinerja untuk skalabilitas.
Joseph Daigle
Seberapa peduli Anda menggunakan ruang penyimpanan?
Jon Seigel
@ JonSeigel itu tergantung pada seberapa banyak ... tapi saya ingin melihat saran
Joseph Daigle
2
Dan apa pendekatan / permintaan Anda untuk mendapatkan halaman ke-2 dari hasil? RowNum BETWEEN 101 AND 200?
ypercubeᵀᴹ

Jawaban:

1

Jika beban kerja khusus ini adalah mayoritas kueri terhadap tabel, Anda dapat mempertimbangkan:

ALTER TABLE Ticket ADD CONSTRAINT PK_Ticket PRIMARY KEY NONCLUSTERED (TicketId);

CREATE UNIQUE CLUSTERED INDEX IX_Ticket_Covering ON Ticket (
    InsertDateTime ASC
);

Pertimbangan:

  • Anda bisa menggunakan datetime2 (SQL 2008+; presisi fleksibel)
  • akan InsertDateTime menjadi unik dalam ketepatan Anda
  • jika waktu tidak dibatasi, sql unik akan menambahkan kolom pemersatu yang tersembunyi dari tipe int. Ini ditambahkan ke semua indeks nonclutred sehingga mereka dapat referensi catatan berkerumun yang benar

Keuntungan:

  • Menambahkan baris baru ke ujung tabel
  • cegah penulisan kolom filter opsional dua kali (satu kali di clustered, dan satu kali di daun indeks untuk menyertakan)
  • sebagian besar waktu Anda masih akan mencari indeks cluster dengan lebih atau kurang filer.
  • kemudian tambahkan indeks nonclustered lainnya untuk pasangan kolom paling populer
Mat
sumber
1

Saya telah menggunakan teknik ini di masa lalu. Tabelnya tidak terlalu besar tetapi kriteria pencariannya lebih kompleks.

Ini adalah versi singkatnya.

CREATE PROC usp_Search
    (
    @StartDate  Date,
    @EndDate    Date,
    @Sites      Varchar(30) = NULL,
    @Assigned   Int = NULL, --Assuming only value possible
    @StartRow   Int,
    @EndRow     Int
    )
AS
DECLARE @TblSites   TABLE (ID Int)
IF @Sites IS NOT NULL
BEGIN
    -- Split @Sites into table @TblSites
END
SELECT  TicketId
FROM    (
        SELECT  TicketId,
                ROW_NUMBER() OVER(ORDER BY InsertDateTime DESC) as RowNum
        FROM    Ticket
                LEFT JOIN @TblSites
                    Ticket.SiteID = @TblSites.ID
        WHERE   InsertDateTime >= @StartDate 
                AND InsertDateTime < @EndDate
                AND (
                    @Assigned IS NULL 
                    OR AssignedId = @Assigned 
                    )
        ) _
WHERE   RowNum BETWEEN @StartRow AND @EndRow;
Dennis Post
sumber
1

Mengingat dua prasyarat pertama Anda, saya akan melihat indeks berkerumun di InsertDateTime.

Michael Green
sumber
-1

Jika klien memfilter dengan cara yang hampir sama berulang-ulang, Anda dapat membuat indeks untuk permintaan tersebut.

Misalnya klien memfilter pada SiteId dan StatusId Anda dapat membuat indeks tambahan:

CREATE NONCLUSTERED INDEX IX_Ticket_InsertDateTime_SiteId_StatusId ON Ticket     
(InsertDateTime DESC,
 SiteId [ASC/DESC],
 StatusId [ASC/DESC] ) 
 INCLUDE ( ... );

Dengan cara ini, sebagian besar pertanyaan 'lebih umum' dapat berjalan cepat.

Ruud van de Beeten
sumber