Büyük Seti Disjeksiyonlarla Etkin Bir Şekilde Filtreleyin


9

Diyelim ki tek bir masam var

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
);

Bu örnekte TicketIdBirincil Anahtar yer almaktadır.

Kullanıcıların bu tabloya karşı "kısmen geçici" sorgular oluşturabilmesini istiyorum. Kısmen diyorum çünkü sorgunun birkaç kısmı her zaman düzeltilecektir:

  1. Sorgu her zaman bir InsertDateTime
  2. Sorgu her zaman ORDER BY InsertDateTime DESC
  3. Sorgu, sonuçları gösterir

Kullanıcı isteğe bağlı olarak diğer sütunlardan herhangi birine filtre uygulayabilir. Hiçbirine, bir veya daha fazlasına filtre uygulayabilirler. Ve her sütun için kullanıcı, bir ayrılma olarak uygulanacak bir değer kümesi arasından seçim yapabilir. Örneğin:

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;

Şimdi tablonun 100.000.000 satırı olduğunu varsayın.

Ben gelebilir en iyi "isteğe bağlı" sütunların her birini içeren bir kaplama dizinidir:

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

Bu bana aşağıdaki gibi bir sorgu planı verir:

  • SEÇ
    • filtre
      • Üst
        • Dizi Projesi (Hesaplamalı Skaler)
          • bölüm
            • Dizin Ara

Oldukça iyi görünüyor. Maliyetin yaklaşık% 80-% 90'ı ideal olan Endeks Arama işleminden gelir.

Bu tür bir araştırmayı uygulamak için daha iyi stratejiler var mı?

Isteğe bağlı filtreleme istemciye boşaltmak istemiyorum çünkü bazı durumlarda "sabit" bölümünden sonuç kümesi 100s veya 1000s olabilir. Bu durumda müşteri, müşteri için çok fazla işe yarayabilecek sıralama ve sayfalamadan da sorumlu olacaktır.


Alt sorgunuzu geçici tabloya veya tablo değişkenine yerleştirip bu şekilde oluşturmak mümkün mü? Daha büyük tablolarımla, bazen alt sorgular tarafından sokulur. Kaplama endeksleri sizi şimdiye kadar götürür.
Valkyrie

@Valkyrie inanılmaz derecede verimsiz görünüyor. Ayrıca, bu sorgunun varyantlarının (farklı parametreler ve farklı isteğe bağlı yan tümcecikler) gün boyunca saniyede birkaç kez yürütülebileceğini ve sonuçları ortalama olarak 100 ms'den daha kısa bir sürede döndürmesi gerektiğini düşünün. Bunu zaten yapıyoruz ve şimdilik iyi bir performans sergiliyor. Sadece ölçeklenebilirlik için performansı artırmaya nasıl devam edeceğime dair fikirler arıyorum.
Joseph Daigle

Depolama alanını kullanmaya ne kadar önem veriyorsunuz?
Jon Seigel

@JonSeigel ne kadar olduğuna bağlı ... ama herhangi bir öneri görmek istiyorum
Joseph Daigle

2
Sonuçların 2. sayfasını almak için yaklaşımınız / sorgunuz nedir? RowNum BETWEEN 101 AND 200?
ypercubeᵀᴹ

Yanıtlar:


1

Bu özel iş yükü tablodaki sorguların çoğunluğuysa, aşağıdakileri göz önünde bulundurabilirsiniz:

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

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

hususlar:

  • datetime2 kullanabilir misiniz (SQL 2008+; esnek hassasiyet)
  • InsertDateTime sizin hassasiyetinizde benzersiz olacak
  • zaman kısıtlı değilse benzersiz sql int türünde gizli bir benzersiz sütun ekler. Bu, kümelenmemiş tüm dizine eklenir, böylece doğru kümelenmiş kayda başvurabilirler

Avantajları:

  • Tablonun sonuna yeni satırlar ekler
  • isteğe bağlı filtre sütunlarının iki kez yazılmasını önleyin (bir kez kümelenmiş ve bir kez içerme için dizin yaprağına)
  • zamanınızın büyük bir kısmı hala daha fazla veya daha az dosya içeren bir küme dizin arayışında olacaktır.
  • en popüler sütun çiftleri için başka kümelenmemiş dizin ekleyin

1

Bu tekniği geçmişte kullandım. Tablo neredeyse büyük değildi, ancak arama kriterleri daha karmaşıktı.

Bu kısa versiyon.

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;

1

İlk iki ön koşulunuz göz önüne alındığında, üzerinde kümelenmiş bir dizine bakıyordum InsertDateTime.



-1

İstemciler tekrar tekrar aynı şekilde filtreliyorsa, bu sorgular için bir dizin oluşturabilirsiniz.

İstemci SiteId ve StatusId üzerinde filtreleme yapıyorsa, ek bir dizin oluşturabilirsiniz:

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

Bu şekilde, 'daha yaygın' sorguların çoğu hızlı çalışabilir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.