Quando una tabella ha un indice cluster, l'indice è i dati della tabella (altrimenti si dispone di una tabella dei tipi di heap). Una ricostruzione dell'indice cluster (qualsiasi indice in effetti, ma lo spazio non verrebbe conteggiato come "dati" per un indice non cluster) comporterà l'unione delle pagine parzialmente utilizzate in una forma più completa.
Man mano che si inseriscono i dati in un indice (raggruppato o meno) nelle pagine foglia dell'ordine dell'indice vengono create secondo necessità e si avrà sempre e solo una pagina parziale: quella alla fine. Quando inserisci i dati fuori dall'ordine dell'indice, una pagina deve essere divisa affinché i dati si adattino al posto giusto: finisci con due pagine che sono piene per circa la metà e la nuova riga va in una di esse. Nel tempo ciò può accadere molto, consumando una buona quantità di spazio extra, sebbene in futuro gli inserti colmeranno alcune lacune. Anche le pagine non foglia vedranno un effetto simile, ma le pagine di dati reali hanno dimensioni molto più significative di quanto non siano.
Inoltre, le eliminazioni possono causare pagine parziali. Se si rimuovono tutte le righe in una pagina, vengono conteggiate come "non utilizzate", ma se sono rimaste una o più righe di dati, vengono comunque conteggiate come in uso. Anche se esiste una sola riga che utilizza 10 byte in una pagina, quella pagina conta come 8192 byte nel conteggio dello spazio utilizzato. Anche in questo caso inserti futuri potrebbero colmare parte del gap.
Per le righe di lunghezza variabile, anche gli aggiornamenti possono avere lo stesso effetto: man mano che una riga si restringe, può lasciare spazio nella sua pagina che non è successivamente facile da riutilizzare e se una riga in una pagina quasi intera si allunga, potrebbe forzare una divisione della pagina .
SQL Server non passa il tempo a cercare di normalizzare i dati riorganizzando il modo in cui le pagine vengono utilizzate, fino a quando non viene esplicitamente comunicato a come l'ordine di ricostruzione dell'indice, poiché tali esercizi di garbage collection potrebbero essere un incubo di prestazioni.
Sospetto che questo sia ciò che stai vedendo, anche se direi che avere uno spazio sufficiente allocato per ~ 2,7 volte la quantità di dati assolutamente necessaria è un caso particolarmente negativo. Potrebbe implicare che tu abbia qualcosa di casuale come una delle chiavi significative nell'indice (forse una colonna UUID), il che significa che è improbabile che nuove righe vengano mai aggiunte nell'ordine dell'indice e / o che un numero significativo di eliminazioni si sia verificato di recente.
Esempio di suddivisione della pagina
Inserimento in ordine di indice con righe a lunghezza fissa di cui quattro si adattano a una pagina:
Start with one empty page:
[__|__|__|__]
Add the first item in index order:
[00|__|__|__]
Add the next three
[00|02|04|06]
Adding the next will result in a new page:
[00|02|04|06] [08|__|__|__]
And so on...
[00|02|04|06] [08|10|12|14] [16|18|__|__]
Ora per aggiungere righe fuori dall'ordine dell'indice (ecco perché ho usato numeri pari solo sopra): aggiungere 11
significherebbe estendere quella seconda pagina (non possibile in quanto sono di dimensioni fisse), spostando tutto sopra 11 su uno (decisamente troppo costoso su un indice di grandi dimensioni) o suddividere la pagina in questo modo:
[00|02|04|06] [08|10|11|__] [12|14|__|__] [16|18|__|__]
Da qui, l'aggiunta 13
e 17
non si tradurrà in una divisione in quanto vi è attualmente spazio nelle pagine pertinenti:
[00|02|04|06] [08|10|11|__] [12|13|14|__] [16|17|18|__]
ma aggiungendo 03 sarà:
[00|02|03|__] [04|06|__|__] [08|10|11|__] [12|13|14|__] [16|17|18|__]
Come puoi vedere, dopo quelle operazioni di inserimento al momento abbiamo allocato 5 pagine di dati che potrebbero contenere un totale di 20 righe, ma abbiamo solo 14 righe lì ("sprecando" il 30% dello spazio).
Una ricostruzione con opzioni predefinite (vedi sotto "fattore di riempimento") comporterebbe:
[00|02|03|04] [06|08|10|11] [12|13|14|16] [17|18|__|__]
salvare una pagina in questo semplice esempio. È facile vedere come le eliminazioni possono avere un effetto simile agli inserti fuori dall'indice.
attenuazione
Se ci si aspetta che i dati arrivino in un ordine abbastanza casuale rispetto all'ordine dell'indice, è possibile utilizzare l' FILLFACTOR
opzione durante la creazione o la ricostruzione di un indice per dire a SQL Server di lasciare artificialmente vuoti da riempire in seguito, riducendo le divisioni di pagina a lungo termine ma prendendo più spazio inizialmente. Naturalmente sbagliare questo valore può peggiorare le cose piuttosto che migliorare la situazione, quindi gestisci con cura.
La suddivisione della pagina, in particolare sull'indice cluster, può avere un impatto sulle prestazioni per inserimenti / aggiornamenti, pertanto a FILLFACTOR
volte è ottimizzata per quel motivo invece del problema di utilizzo dello spazio nei database che vedono molte attività di scrittura (ma per la maggior parte delle app, dove le letture superano le scritture per diversi ordini di grandezza, è generalmente meglio lasciare il fattore di riempimento al 100%, tranne per casi specifici come quelli in cui si hanno indici su colonne con contenuti effettivamente casuali).
Presumo che altri DB di grandi nomi abbiano un'opzione simile, se hai bisogno anche di questo livello di controllo.
Aggiornare
Per quanto riguarda l' ALTER INDEX
affermazione aggiunta alla domanda dopo che ho iniziato a digitare quanto sopra: suppongo che le opzioni siano le stesse di quando l'indice è stato creato (o ricostruito per ultimo) ma in caso contrario l'opzione di compressione potrebbe essere molto significativa se fosse stata aggiunta questa tempo intorno. Anche in questa affermazione il fillfactor è impostato su 85% non 100%, quindi ogni pagina foglia sarà ~ 15% vuota immediatamente dopo la ricostruzione.