Quando uma tabela possui um índice em cluster, o índice são os dados da tabela (caso contrário, você possui uma tabela do tipo heap). Uma reconstrução do índice clusterizado (qualquer índice de fato, mas o espaço não seria contado como "dados" para um índice não clusterizado) resultará na mesclagem de páginas parcialmente usadas em uma forma mais completa.
À medida que você insere dados em um índice (agrupado ou não) na ordem do índice, as páginas folha são criadas conforme necessário e você terá apenas uma página parcial: a que está no final. Quando você insere os dados fora da ordem do índice, uma página precisa ser dividida para que os dados se encaixem no lugar certo: você termina com duas páginas que estão aproximadamente pela metade e a nova linha entra em uma delas. Com o tempo, isso pode acontecer bastante, consumindo bastante espaço extra, embora, em certa medida, as inserções futuras preencham algumas das lacunas. As páginas que não são folhas também terão um efeito semelhante, mas as páginas de dados reais são muito mais significativas em tamanho do que são.
Também as exclusões podem resultar em páginas parciais. Se você remover todas as linhas de uma página, elas serão contadas como "não utilizadas", mas, se houver uma ou mais linhas de dados, ainda serão contadas como em uso. Mesmo se houver apenas uma linha usando 10 bytes em uma página, essa página conta como 8192 bytes na contagem de espaço usado. Novamente, inserções futuras podem preencher parte dessa lacuna.
Para linhas de comprimento variável, as atualizações também podem ter o mesmo efeito: à medida que uma linha fica menor, pode deixar espaço em sua página que não é fácil de reutilizar posteriormente, e se uma linha em uma página quase inteira crescer mais, poderá forçar a divisão da página .
O SQL Server não gasta tempo tentando normalizar os dados reorganizando a maneira como as páginas são usadas, até que seja explicitamente informado a sua ordem de reconstrução do índice, pois esses exercícios de coleta de lixo podem ser um pesadelo no desempenho.
Eu suspeito que isso é o que você está vendo, embora eu diria que ter espaço suficiente alocado para ~ 2,7 vezes a quantidade absolutamente necessária para os dados é um caso particularmente ruim. Pode implicar que você tenha algo aleatório como uma das chaves significativas no índice (talvez uma coluna UUID), o que significa que é improvável que novas linhas sejam adicionadas na ordem do índice e / ou que um número significativo de exclusões tenha acontecido recentemente.
Exemplo de divisão de página
Inserindo na ordem do índice com linhas de comprimento fixo, das quais quatro cabem em uma página:
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|__|__]
Agora, para adicionar linhas fora da ordem do índice (é por isso que usei números pares apenas acima): Adicionar 11
significaria estender a segunda página (não é possível porque são de tamanho fixo), movendo tudo acima de 11 para cima uma (muito caro em um índice grande) ou dividir a página da seguinte forma:
[00|02|04|06] [08|10|11|__] [12|14|__|__] [16|18|__|__]
A partir daqui, adicionar 13
e 17
não resultará em uma divisão, pois há espaço nas páginas relevantes:
[00|02|04|06] [08|10|11|__] [12|13|14|__] [16|17|18|__]
mas adicionar 03 irá:
[00|02|03|__] [04|06|__|__] [08|10|11|__] [12|13|14|__] [16|17|18|__]
Como você pode ver, após essas operações de inserção, atualmente temos 5 páginas de dados alocadas que podem caber um total de 20 linhas, mas só temos 14 linhas lá ("desperdiçando" 30% do espaço).
Uma reconstrução com opções padrão (veja abaixo sobre "fator de preenchimento") resultaria em:
[00|02|03|04] [06|08|10|11] [12|13|14|16] [17|18|__|__]
salvando uma página neste exemplo simples. É fácil ver como as exclusões podem ter um efeito semelhante às inserções de pedidos fora do índice.
Mitigação
Se você espera que os dados cheguem em uma ordem bastante aleatória em relação à ordem do índice, você pode usar a FILLFACTOR
opção ao criar ou recriar um índice para instruir o SQL Server a deixar lacunas artificialmente para preenchimento posterior - reduzindo as divisões da página a longo prazo, mas ocupando mais espaço inicialmente. É claro que errar esse valor pode piorar as coisas, em vez de melhorar a situação; portanto, lide com cuidado.
A divisão de páginas, principalmente no índice clusterizado, pode ter uma implicação no desempenho de inserções / atualizações, por isso FILLFACTOR
às vezes é aprimorada por esse motivo, em vez do problema de uso de espaço em bancos de dados que exibem muita atividade de gravação (mas para a maioria dos aplicativos, onde as leituras superam as gravações por várias ordens de grandeza, geralmente é melhor deixar o fator de preenchimento em 100%, exceto em casos específicos, como onde você tem índices sobre colunas com conteúdo efetivamente aleatório).
Suponho que outros DBs de grande nome tenham uma opção semelhante, se você precisar desse nível de controle também.
Atualizar
Em relação à ALTER INDEX
declaração adicionada à pergunta depois que comecei a digitar o acima: Presumo que as opções sejam as mesmas de quando o índice foi criado (ou reconstruído pela última vez), mas, se não, a opção de compactação pode ser muito significativa se for adicionada esta tempo ao redor. Também nessa declaração, o fator de preenchimento é definido como 85% e não 100%, para que cada página de folha fique ~ 15% vazia imediatamente após a reconstrução.