Non fraintendermi; Amo gli indici filtrati. Creano opportunità per un uso molto più efficiente dell'I/O e, infine, ci consentono di implementare vincoli univoci conformi ad ANSI (dove è consentito più di un NULL). Tuttavia, sono tutt'altro che perfetti. Volevo evidenziare alcune aree in cui gli indici filtrati potrebbero essere migliorati e renderli molto più utili e pratici per un'ampia porzione di carichi di lavoro.
In primo luogo, le buone notizie
Gli indici filtrati possono eseguire rapidamente query costose in precedenza, utilizzando meno spazio (e quindi un I/O ridotto, anche durante la scansione).
Un rapido esempio utilizzando Sales.SalesOrderDetailEnlarged
(creato utilizzando questo script da Jonathan Kehayias (@SQLPoolBoy)). Questa tabella ha 4,8 MM di righe, con 587 MB di dati e 363 MB di indici. C'è solo una colonna nullable, CarrierTrackingNumber
, quindi giochiamo con quello. Così com'è, la tabella ha attualmente circa la metà di questi valori (2,4 MM) come NULL. Lo ridurrò a circa 240 K per simulare uno scenario in cui una piccola percentuale delle righe nella tabella è effettivamente idonea per un indice, al fine di evidenziare al meglio i vantaggi di un indice filtrato. La query seguente interessa 2,17 MM di righe, lasciando 241.507 righe con un valore NULL per CarrierTrackingNumber
:
UPDATE Sales.SalesOrderDetailEnlarged SET CarrierTrackingNumber = 'x' WHERE CarrierTrackingNumber IS NULL AND SalesOrderID % 10 <> 3;
Ora, supponiamo che vi sia un requisito aziendale in base al quale desideriamo costantemente rivedere gli ordini a cui devono ancora essere assegnato un numero di tracciabilità (si pensi agli ordini che vengono suddivisi e spediti separatamente). Nella tabella corrente eseguiremmo queste query (e ho aggiunto i comandi DBCC per garantire la cache a freddo in ogni caso):
DBCC DROPCLEANBUFFERS; DBCC FREEPROCCACHE; SELECT COUNT(*) FROM Sales.SalesOrderDetailEnlarged WHERE CarrierTrackingNumber IS NULL; SELECT ProductID, SalesOrderID FROM Sales.SalesOrderDetailEnlarged WHERE CarrierTrackingNumber IS NULL;
Che richiedono scansioni dell'indice in cluster e producono le seguenti metriche di runtime (acquisite con SQL Sentry Plan Explorer):
Ai "vecchi" giorni (ovvero da SQL Server 2005), avremmo creato questo indice (e infatti, anche in SQL Server 2012, questo è l'indice consigliato da SQL Server):
CREATE INDEX IX_NotVeryHelpful ON [Sales].[SalesOrderDetailEnlarged] ([CarrierTrackingNumber]) INCLUDE ([SalesOrderID],[ProductID]);
Con quell'indice attivo ed eseguendo nuovamente le query precedenti, ecco le metriche, con entrambe le query che utilizzano una ricerca dell'indice come ci si potrebbe aspettare:
E poi rilasciando quell'indice e creandone uno leggermente diverso, semplicemente aggiungendo un WHERE
clausola:
CREATE INDEX IX_Filtered_CTNisNULL ON [Sales].[SalesOrderDetailEnlarged] ([CarrierTrackingNumber]) INCLUDE ([SalesOrderID],[ProductID]) WHERE CarrierTrackingNumber IS NULL;
Otteniamo questi risultati ed entrambe le query utilizzano l'indice filtrato per le loro ricerche:
Ecco lo spazio aggiuntivo richiesto da ciascun indice, rispetto alla riduzione di runtime e I/O delle query precedenti:
Indice | Spazio indice | Spazio aggiunto | Durata | Legge |
---|---|---|---|---|
Nessun indice dedicato | 363 MB | 15.700 ms | ~164.000 | |
Indice non filtrato | 530 MB | 167 MB (+46%) | 169 ms | 1.084 |
Indice filtrato | 367 MB | 4 MB (+1%) | 170 ms | 1.084 |
Quindi, come puoi vedere, l'indice filtrato offre miglioramenti delle prestazioni quasi identici all'indice non filtrato (poiché entrambi sono in grado di ottenere i propri dati utilizzando lo stesso numero di letture), ma con uno spazio di archiviazione molto inferiore costo, poiché l'indice filtrato deve solo archiviare e mantenere le righe che corrispondono al predicato del filtro.
Ora riportiamo la tabella al suo stato originale:
UPDATE Sales.SalesOrderDetailEnlarged SET CarrierTrackingNumber = NULL WHERE CarrierTrackingNumber = 'x'; DROP INDEX IX_NotVeryHelpful ON Sales.SalesOrderDetailEnlarged; DROP INDEX IX_Filtered_CTNisNULL ON Sales.SalesOrderDetailEnlarged;
Tim Chapman (@chapmandew) e Michelle Ufford (@sqlfool) hanno svolto un lavoro fantastico nel delineare i vantaggi in termini di prestazioni degli indici filtrati a modo loro e dovresti dare un'occhiata anche ai loro post:
- Michelle Ufford:indici filtrati:cosa devi sapere
- Tim Chapman:le gioie degli indici filtrati
Inoltre, vincoli univoci conformi ad ANSI (una specie di)
Ho pensato di menzionare brevemente anche i vincoli univoci conformi all'ANSI. In SQL Server 2005 creeremmo un vincolo univoco come questo:
CREATE TABLE dbo.Personnel ( EmployeeID INT PRIMARY KEY, SSN CHAR(9) NULL, -- ... other columns ... CONSTRAINT UQ_SSN UNIQUE(SSN) );
(Potremmo anche creare un indice univoco non cluster invece di un vincolo; l'implementazione sottostante è essenzialmente la stessa.)
Ora, questo non è un problema se gli SSN sono noti al momento dell'inserimento:
INSERT dbo.Personnel(EmployeeID, SSN) VALUES(1,'111111111'),(2,'111111112');
Va bene anche se abbiamo il SSN occasionale che non è noto al momento dell'ingresso (pensa a un richiedente visto o forse anche a un lavoratore straniero che non ha un SSN e non lo farà mai):
INSERT dbo.Personnel(EmployeeID, SSN) VALUES(3,NULL);
Fin qui tutto bene. Ma cosa succede quando abbiamo un secondo dipendente con SSN sconosciuto?
INSERT dbo.Personnel(EmployeeID, SSN) VALUES(4,NULL);
Risultato:
Msg 2627, livello 14, stato 1, riga 1Violazione del vincolo UNIQUE KEY 'UQ_SSN'. Impossibile inserire la chiave duplicata nell'oggetto 'dbo.Personnel'. Il valore della chiave duplicata è (
L'istruzione è stata terminata.
Pertanto, in qualsiasi momento, in questa colonna può esistere un solo valore NULL. A differenza della maggior parte degli scenari, questo è un caso in cui SQL Server considera due valori NULL come uguali (anziché determinare che l'uguaglianza è semplicemente sconosciuta e, a sua volta, falsa). La gente si lamenta da anni di questa incoerenza.
Se questo è un requisito, ora possiamo ovviare a questo problema utilizzando indici filtrati:
ALTER TABLE dbo.Personnel DROP CONSTRAINT UQ_SSN; GO CREATE UNIQUE INDEX UQ_SSN ON dbo.Personnel(SSN) WHERE SSN IS NOT NULL;
Ora il nostro quarto inserto funziona perfettamente, poiché l'unicità viene applicata solo ai valori non NULL. Questo è un tipo di imbroglio, ma soddisfa i requisiti di base previsti dallo standard ANSI (anche se SQL Server non ci consente di utilizzare ALTER TABLE ... ADD CONSTRAINT
sintassi per creare un vincolo univoco filtrato).
Ma tieni premuto il telefono
Questi sono ottimi esempi di cosa possiamo fare con gli indici filtrati, ma ci sono molte cose che non possiamo ancora fare e diversi limiti e problemi che ne derivano.
Aggiornamenti delle statistiche
Questa è una delle limitazioni più importanti IMHO. Gli indici filtrati non traggono vantaggio dall'aggiornamento automatico delle statistiche in base a una variazione percentuale del sottoinsieme della tabella identificato dal predicato del filtro; si basa (come tutti gli indici non filtrati) sull'abbandono rispetto all'intera tabella. Ciò significa che, a seconda della percentuale della tabella nell'indice filtrato, il numero di righe nell'indice potrebbe quadruplicare o dimezzarsi e le statistiche non si aggiorneranno se non lo fai manualmente. Kimberly Tripp ha fornito ottime informazioni al riguardo (e Gail Shaw cita un esempio in cui sono stati necessari 257.000 aggiornamenti prima che le statistiche venissero aggiornate per un indice filtrato che conteneva solo 10.000 righe):
http://www.sqlskills.com/blogs/kimberly/filtered-indexes-and-filtered-stats-might-become-seriously-out-of-date/
http://www.sqlskills.com/ blogs/kimberly/category/filtered-indexes/
Inoltre, il collega di Kimberly, Joe Sack (@JosephSack), ha presentato un articolo Connect che suggerisce di correggere questo comportamento sia per gli indici filtrati che per le statistiche filtrate.
Limitazioni dell'espressione del filtro
Ci sono diversi costrutti che non puoi usare in un predicato di filtro, come NOT IN
, OR
e predicati dinamici/non deterministici come WHERE col >= DATEADD(DAY, -1, GETDATE())
. Inoltre, l'ottimizzatore potrebbe non riconoscere un indice filtrato se il predicato non corrisponde esattamente a WHERE
clausola nella definizione dell'indice. Ecco alcuni elementi di Connect che cercano di convincere un po' di supporto per una migliore copertura qui:
L'indice filtrato non consente filtri sulle disgiunzioni | (chiuso:in base alla progettazione) |
Creazione dell'indice filtrato non riuscita con clausola NOT IN | (chiuso:in base alla progettazione) |
Supporto per clausole WHERE più complesse negli indici filtrati | (attivo) |
Altri potenziali usi attualmente non possibili
Al momento non è possibile creare un indice filtrato su una colonna calcolata persistente, anche se deterministica. Non possiamo puntare una chiave esterna a un indice filtrato univoco; se vogliamo che un indice supporti la chiave esterna oltre alle query supportate dall'indice filtrato, dobbiamo creare un secondo indice, ridondante, non filtrato. Ed ecco alcune altre limitazioni simili che sono state trascurate o non ancora considerate:
Dovrebbe essere possibile creare un indice filtrato su una colonna calcolata persistente deterministica | (attivo) |
Consenti all'indice univoco filtrato di essere una chiave candidata per una chiave esterna | (attivo) |
possibilità di creare indici di filtro sulle viste indicizzate | (chiuso:non risolverà) |
Errore di partizionamento 1908 – Migliora il partizionamento | (chiuso:non risolverà) |
CREA INDICE COLONNSTORE "FILTRATO" | (attivo) |
Problemi con MERGE
E MERGE
fa ancora un'altra apparizione nella mia lista "attenzione" :
MERGE valuta l'indice filtrato per riga, non l'operazione post, che causa la violazione dell'indice filtrato | (chiuso:non risolverà) |
MERGE non si aggiorna con l'indice filtrato in atto | (chiuso:fisso) |
Bug dell'istruzione MERGE quando INSERT/DELETE viene utilizzato e filtrato l'indice | (attivo) |
MERGE segnala erroneamente violazioni di chiavi univoche | (attivo) |
Sebbene uno di questi bug (apparentemente correlati) indichi che è stato corretto in SQL Server 2012, potrebbe essere necessario contattare PSS se si riscontrano variazioni di questo problema, in particolare nelle versioni precedenti (o interrompere l'utilizzo di MERGE , come ho suggerito prima).
Strumento/DMV/limitazioni integrate
Esistono molti DMV, comandi DBCC, procedure di sistema e strumenti client su cui iniziamo a fare affidamento nel tempo. Tuttavia, non tutte queste cose vengono aggiornate per sfruttare le nuove funzionalità; gli indici filtrati non fanno eccezione. I seguenti elementi Connect evidenziano alcuni problemi che potrebbero inciampare se prevedi che funzionino con indici filtrati:
Non c'è modo di creare un indice filtrato da SSMS durante la progettazione di una nuova tabella | (chiuso:non risolverà) |
L'espressione del filtro di un indice filtrato viene persa quando una tabella viene modificata da Designer tabelle | (chiuso:non risolverà) |
Il designer di tabelle non esegue lo script della clausola WHERE negli indici filtrati | (attivo) |
Il designer di tabelle SSMS non conserva l'espressione del filtro dell'indice durante la ricostruzione della tabella | (chiuso:non risolverà) |
Output errato della PAGINA DBCC con indici filtrati | (attivo) |
Suggerimenti per indici filtrati SQL 2008 da viste DM e DTA | (chiuso:non risolverà) |
Miglioramenti agli indici mancanti DMV per gli indici filtrati | (chiuso:non risolverà) |
Errore di sintassi durante la replica di indici filtrati compressi | (chiuso:non risolverà) |
Agente:i lavori utilizzano opzioni non predefinite durante l'esecuzione di uno script T-SQL | (chiuso:non risolverà) |
La visualizzazione delle dipendenze non riesce con l'errore Transact-SQL 515 | (attivo) |
La visualizzazione delle dipendenze non riesce su determinati oggetti | (chiuso:non risolverà) |
Le differenze tra le opzioni dell'indice non vengono rilevate nel confronto degli schemi per due database | (chiuso:esterno) |
Suggerisci di esporre la condizione del filtro dell'indice in tutte le visualizzazioni delle informazioni sull'indice | (chiuso:non risolverà) |
I risultati di sp_helpIndex dovrebbero includere l'espressione di filtro degli indici di filtro | (attivo) |
Sovraccarico sp_help, sp_columns, sp_helpindex per le funzionalità 2008 | (chiuso:non risolverà) |
Per gli ultimi tre, non trattenere il respiro:è piuttosto improbabile che Microsoft investirà del tempo nelle procedure sp_, nelle DMV, nelle visualizzazioni INFORMATION_SCHEMA e così via. Guarda invece le riscritture sp_helpindex di Kimberly Tripp, che includono informazioni sugli indici filtrati insieme con altre nuove funzionalità che Microsoft ha lasciato alle spalle.
Limiti di Optimizer
Esistono diversi elementi Connect che descrivono casi in cui gli indici filtrati *potrebbero* essere utilizzati dall'ottimizzatore, ma vengono invece ignorati. In alcuni casi questi non sono considerati "bug" ma piuttosto "lacune di funzionalità"...
SQL non usa l'indice filtrato su una query semplice | (chiuso:in base alla progettazione) |
Il piano di esecuzione dell'indice filtrato non è ottimizzato | (chiuso:non risolverà) |
Indice filtrato non utilizzato e ricerca della chiave senza output | (chiuso:non risolverà) |
L'utilizzo dell'indice filtrato sulla colonna BIT dipende dall'esatta espressione SQL utilizzata nella clausola WHERE | (attivo) |
La query del server collegato non viene ottimizzata correttamente quando esiste un indice univoco filtrato | (chiuso:non risolverà) |
Row_Number() fornisce risultati imprevedibili sui server collegati in cui sono stati utilizzati gli indici filtrati | (chiuso:nessuna riproduzione) |
Ovvio indice filtrato non utilizzato da QP | (chiuso:in base alla progettazione) |
Riconosci gli indici filtrati univoci come unici | (attivo) |
Paul White (@SQL_Kiwi) ha recentemente pubblicato qui su SQLPerformance.com un post molto dettagliato su un paio delle limitazioni dell'ottimizzatore di cui sopra.
E Tim Chapman ha scritto un ottimo post delineando alcune altre limitazioni degli indici filtrati, come l'impossibilità di abbinare il predicato a una variabile locale (risolta nel 2008 R2 SP1) e l'impossibilità di specificare un indice filtrato in un suggerimento di indice.
Conclusione
Gli indici filtrati hanno un grande potenziale e avevo grandi speranze per loro quando sono stati introdotti per la prima volta in SQL Server 2008. Tuttavia, la maggior parte delle limitazioni fornite con la loro prima versione esistono ancora oggi, uno e mezzo (o due, a seconda del tuo prospettiva) major release successive. Quanto sopra sembra un elenco piuttosto ampio di elementi che devono essere affrontati, ma non intendevo che si imbattesse in quel modo. Voglio solo che le persone siano consapevoli del vasto numero di potenziali problemi che potrebbero dover considerare quando si sfruttano gli indici filtrati.