Filtra in modo efficiente set di grandi dimensioni con disgiunzioni


9

Diciamo che ho un solo tavolo

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

In questo esempio TicketIdè la chiave primaria.

Voglio che gli utenti siano in grado di creare query "parzialmente ad hoc" su questa tabella. Dico in parte perché alcune parti della query verranno sempre corrette:

  1. La query eseguirà sempre un filtro intervallo su un InsertDateTime
  2. La query sarà sempre ORDER BY InsertDateTime DESC
  3. La query visualizzerà i risultati

L'utente può facoltativamente filtrare su una qualsiasi delle altre colonne. Possono filtrare su nessuno, uno o molti. E per ogni colonna l'utente può selezionare da una serie di valori che verranno applicati come disgiunzione. Per esempio:

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;

Ora supponiamo che la tabella abbia 100.000.000 di righe.

Il meglio che posso trovare è un indice di copertura che include ciascuna delle colonne "opzionali":

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

Questo mi dà un piano di query come segue:

  • SELEZIONARE
    • Filtro
      • Superiore
        • Progetto sequenza (Calcola scalare)
          • Segmento
            • Ricerca indice

Sembra abbastanza buono. Circa l'80% -90% del costo deriva dall'operazione Index Seek, che è l'ideale.

Esistono strategie migliori per implementare questo tipo di ricerca?

Non voglio necessariamente scaricare il filtro opzionale sul client perché in alcuni casi il set di risultati dalla parte "fissa" potrebbe essere 100s o 1000s. Il client sarebbe quindi anche responsabile dell'ordinamento e del paging che potrebbero funzionare troppo per il client.


Sarebbe possibile posizionare la tua sottoquery in una tabella temporanea o variabile di tabella e costruire in quel modo? Con i miei tavoli più grandi, a volte vengo colpito dalle query secondarie. Gli indici di copertura ti portano solo finora.
Valkyrie,

@Valkyrie che sembra incredibilmente inefficiente. Considera anche che le varianti di questa query (parametri diversi e diverse clausole where facoltative) verranno probabilmente eseguite più volte al secondo per tutto il giorno e dovranno restituire risultati in media in meno di 100 ms. Lo facciamo già e per ora funziona bene. Sto solo cercando idee su come continuare a migliorare le prestazioni per la scalabilità.
Joseph Daigle,

Quanto ti interessa utilizzare lo spazio di archiviazione?
Jon Seigel,

@JonSeigel dipende da quanto ... ma voglio vedere qualche suggerimento
Joseph Daigle

2
E qual è il tuo approccio / query per ottenere la seconda pagina dei risultati? RowNum BETWEEN 101 AND 200?
ypercubeᵀᴹ

Risposte:


1

Se questo particolare carico di lavoro è la maggior parte delle query rispetto alla tabella, è possibile prendere in considerazione:

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

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

considerazioni:

  • puoi usare datetime2 (SQL 2008+; precisione flessibile)
  • InsertDateTime sarà unico nella tua precisione
  • se i tempi non sono limitati, sql univoco aggiungerà una colonna di unificatore univoca nascosta di tipo int. Questo viene aggiunto a tutti gli indici non classificati in modo che possano fare riferimento al record cluster corretto

vantaggi:

  • Aggiunge nuove righe alla fine della tabella
  • impedisce di scrivere due volte le colonne del filtro opzionale (una volta nel cluster e una volta sull'indice foglia per l'inclusione)
  • la maggior parte del tempo sarà ancora in una ricerca dell'indice del cluster con più o meno filer.
  • quindi aggiungi un altro indice non cluster per le coppie di colonne più popolari

1

Ho usato questa tecnica in passato. Il tavolo non era altrettanto grande ma i criteri di ricerca erano più complessi.

Questa è la versione breve.

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

Dati i tuoi primi due presupposti, guarderei un indice cluster su InsertDateTime.



-1

Se i client filtrano quasi allo stesso modo più e più volte, è possibile creare un indice per tali query.

Ad esempio, il client sta filtrando su SiteId e StatusId, è possibile creare un indice aggiuntivo:

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

In questo modo, la maggior parte delle query "più comuni" potrebbe essere eseguita rapidamente.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.