Ordine dei campi in un ordine indice composito con campi di selettività alta e bassa selettività


11

Ho una tabella di SQL Server con oltre 3 miliardi di righe. Una delle mie query richiede molto tempo, quindi sto pensando di ottimizzarla. La query è simile alla seguente:

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

[Enroll_Date] è una colonna a bassa selettività con meno di 50 possibili valori, mentre la colonna UserID è una colonna ad alta selettività con oltre 200 milioni di valori distinti. Sulla base della mia ricerca, credo che dovrei creare un indice composito non cluster su queste due colonne, e in teoria la colonna ad alta selettività dovrebbe essere la prima colonna. Ma non sono sicuro nel mio caso, funzionerebbe perché sto usando la colonna a bassa selettività nel gruppo per clausola.

Questa tabella non ha un indice cluster.


Puoi pubblicare l'xml del piano di esecuzione effettivo (usa pastebin e collegalo qui)? Quale versione di SQL Server stai usando?
Kin Shah,

3
L'indice con prima la colonna altamente selettiva sarà inutile per la query specifica.
ypercubeᵀᴹ

È consigliabile utilizzare la colonna di selettività superiore come prima colonna chiave di un indice (normalmente). In questo scenario, come hai indovinato, non ti aiuta affatto. Potrebbero essere necessari due indici! Cosa succede quando si utilizza iscr_date prima e user_id in secondo luogo?
paulbarbin,

Risposte:


12

In alternativa alla soluzione di @ AaronBertrand (se non puoi o non vuoi creare una vista indicizzata), ti consiglio di creare un indice su (Enroll_Date, UserID). Se questo tipo di domanda è molto comune sulla tua tabella, questo dovrebbe probabilmente essere anche il tuo indice cluster.

Non consiglierei generalmente gli indici ad alta selettività come "best practice" generale, ma piuttosto guardo a quale indice darà alla tua query le migliori prestazioni.

Un indice (Enroll_Date, UserID)attivo fornirà alla tua query un piano di query altamente ottimizzato e non bloccante con Stream Aggregates.

Streaming piano di query aggregato

"Non bloccando" in questo contesto significa che la query non deve bufferizzare quantità significative di dati (come, ad esempio, un ordinamento o un hash aggregato), il che significa che (a) inizia a restituire immediatamente le righe e ( b) non consuma praticamente memoria di lavoro.


Divertente, a 4 secondi di distanza e la stessa risposta.
usr

11

La risposta di Aarons è un'ottima soluzione. Risponderò alla domanda assumendo che tu non voglia seguire questo approccio.

La query che hai pubblicato di solito verrà eseguita prima raggruppando (Enroll_Date, UserID), poi di nuovo su (Enroll_Date). Questa ottimizzazione è una novità di SQL Server 2012. Ha effetto nel caso di un singolo COUNT DISTINCT.

Un indice su queste due colonne nell'ordine specifico (Enroll_Date, UserID)sarà sufficiente per ottenere un piano efficiente che incanni una scansione dell'indice in due aggregati di flusso consecutivi. L'ordine opposto non consentirebbe quel piano.

Pertanto, utilizzare l'ordine (Enroll_Date, UserID). Non hai scelta qui.


A 5 secondi di distanza e la stessa soluzione. Ben fatto, signore. :)
Daniel Hutmacher,

@DanielHutmacher OMG, riusciremo quasi ad abbinare i nostri post per la terza volta ?! +1 a te! Come potrei non votare una risposta identica?
usr

Glitch in the Matrix. :)
Daniel Hutmacher,

Grazie mille. Sto creando l'indice e pubblicherò il miglioramento dopo averlo fatto. La versione del server è Microsoft SQL Server 2008 R2 su AWS, ma suppongo che sia comunque l'unica scelta a prescindere.
Thinkinger,

@Thinkinger nel caso in cui non accetti l'approccio di Aarons hai una scelta difficile :)
usr

11

Sembra uno scenario ideale per una vista indicizzata, che consente di pagare per calcoli e aggregati al momento della scrittura anziché al momento della query.

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

Ci vorrà del tempo per la creazione e, naturalmente, richiederà la manutenzione in tutte le operazioni DML, proprio come un indice sulla tabella di base.

Ora la query su questa vista sarebbe abbastanza simile: ogni riga nella vista ora rappresenta un combo utente / data distinto, quindi quella cifra può essere calcolata da un singolo COUNT (*), mentre il numero totale di righe nella tabella di base è già parzialmente aggregato per te, ora devi solo aggiungerli usando SUM per data:

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

Aggiunto suggerimento NOEXPAND, dopo aver ricordato questo e questo .

Posso dirti senza dubbio che questa query sarà più veloce della tua query corrente (ma non di quanto), tranne nel raro caso in cui hai esattamente un utente per ogni data (nel qual caso la stessa quantità di dati avrà da leggere) e le colonne che conosciamo sono le uniche colonne nell'indice della tabella di base. Se quel miglioramento delle prestazioni in fase di lettura valga il lavoro extra che influirà sulla porzione di scrittura del tuo carico di lavoro è qualcosa che non possiamo dirti: dovrai testarlo per misurare il compromesso (nessun indice è gratuito).

E se usi frequentemente le stesse clausole WHERE comuni contro Enroll_Date per intervalli specifici e ben definiti (ad esempio, il trimestre o l'anno corrente fino ad oggi), potresti aggiungere indici filtrati corrispondenti che riducono ulteriormente l'I / O (ma c'è sempre un scambio).

Potresti anche considerare di inserire un indice cluster nella tabella di base. Questo non sembra essere uno di quei casi d'uso molto rari che beneficiano di un mucchio.


Ho appena confermato con il nostro IT e sembra che non riesca a creare questo tipo di vista. Ma apprezzo ancora il tuo consiglio e aiuterà gli altri che possono usarlo.
Thinkinger,

1
Il tuo IT ritiene che ci sia una differenza significativa tra una vista indicizzata e indici aggiuntivi o diversi nella tabella di base? Non essere combattivo, solo curioso, perché molte persone hanno idee sbagliate sulle viste indicizzate. Mi piace pensarli come un indice cluster più magro sulla tabella, ma con meno righe.
Aaron Bertrand

@Thinkinger, inoltre, le viste indicizzate non sono solo EE. La corrispondenza della vista indicizzata è solo EE. Puoi indirizzarli direttamente usando NOEXPAND.
usr
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.