Por que o espaço de dados de uma tabela pode ocupar 4x o tamanho dos dados brutos?

18

Eu tenho uma tabela com 490 M linhas e 55 GB de espaço de tabela, portanto, cerca de 167 bytes por linha. A tabela possui três colunas: a VARCHAR(100), a DATETIME2(0)e a SMALLINT. O comprimento médio do texto no VARCHARcampo é de cerca de 21,5, portanto, os dados brutos devem ter cerca de 32 bytes por linha: 22 + 2 para o VARCHAR, 6 para o DATETIME2e 2 para o inteiro de 16 bits.

Observe que o espaço acima é apenas para dados, não para índices. Estou usando o valor relatado em Propriedades | Armazenamento | Geral Espaço de dados.

É claro que deve haver alguma sobrecarga, mas 135 bytes por linha parecem muito, especialmente para uma tabela grande. Por que isso pode ser? Alguém mais viu multiplicadores semelhantes? Quais fatores podem influenciar a quantidade de espaço extra necessária?

Para comparação, tentei criar uma tabela com dois INTcampos e 1 milhão de linhas. O espaço de dados necessário era 16,4 MB: 17 bytes por linha, em comparação com 8 bytes de dados brutos. Outra tabela de teste com um INTe um VARCHAR(100)preenchido com o mesmo texto que a tabela real usa 39 bytes por linha (44 K linhas), onde eu esperaria 28 mais um pouco.

Portanto, a tabela de produção possui consideravelmente mais despesas gerais. Isso é porque é maior? Eu esperava que os tamanhos dos índices fossem aproximadamente N * log (N), mas não vejo por que o espaço necessário para os dados reais serem não lineares.

Agradecemos antecipadamente por quaisquer ponteiros!

EDITAR:

Todos os campos listados são NOT NULL. A tabela real possui uma PK em cluster no VARCHARcampo e no DATETIME2campo, nessa ordem. Para os dois testes, o primeiro INTfoi o PK (agrupado).

Se isso importa: a tabela é um registro dos resultados do ping. Os campos são URL, data / hora do ping e latência em milissegundos. Os dados são constantemente anexados e nunca atualizados, mas os dados são excluídos periodicamente para reduzir para apenas alguns registros por hora por URL.

EDITAR:

Uma resposta muito interessante aqui sugere que, para um índice com muita leitura e escrita, a reconstrução pode não ser benéfica. No meu caso, o espaço consumido é uma preocupação, mas se o desempenho da gravação for mais importante, é possível melhorar os índices flácidos.

Jon de todos os comércios
fonte

Respostas:

11

Após discussões nos comentários sobre a pergunta original, parece que nesse caso o espaço perdido é causado pela escolha da chave em cluster, o que levou a uma fragmentação maciça.

Sempre vale a pena verificar o estado de fragmentação via sys.dm_db_index_physical_stats nessas situações.

Editar: Após a atualização nos comentários

A densidade média da página (antes da reconstrução do índice em cluster) era de 24%, o que se encaixa perfeitamente na pergunta original. As páginas estavam cheias apenas em 1/4, portanto o tamanho total era 4x o tamanho dos dados brutos.

Mark Storey-Smith
fonte
7

As estruturas em disco têm sobrecarga:

  • cabeçalho da linha
  • bitmap nulo + ponteiro
  • deslocamentos de coluna de comprimento variável
  • ponteiros de versão de linha (opcional)
  • ...

Tomando 2 x 4 bytes em colunas, você tem

  • Cabeçalho de linha de 4 bytes
  • Ponteiro de 2 bytes para bitmap NULL
  • 8 bytes para 2 colunas int
  • Bitmap NULL de 3 bytes

Uau 17 bytes!

Você pode fazer o mesmo na sua segunda tabela de teste, que possui mais despesas gerais como a original:

  • 2 bytes para a contagem de colunas de comprimento variável
  • 2 bytes por coluna de comprimento variável

Por que a diferença? Além disso (não vou vincular a estes)

  • você já reconstruiu índices para desfragmentá-los?
  • exclusões não recuperam espaço
  • as páginas de dados serão divididas se você inserir no meio
  • atualizações podem causar indicadores diretos (deixa uma lacuna)
  • estouro de linha
  • coluna varchar removida sem reconstrução de índice ou DBCC CLEANTABLE
  • pilha ou tabela (a pilha não possui índice clusterizado = registros espalhados por todo o lado)
  • Nível de isolamento RCSI (14 bytes extras por linha)
  • espaços à direita (SET ANSI_PADDING está ativado por padrão) no varchar. Use DATALENGTH para checl, não LEN
  • Execute sp_spaceused com @updateusage = 'true'
  • ...

Veja isto: SQL Server: Como criar uma tabela que preenche uma página de 8 KB?

De SO:

gbn
fonte
A amostra da coluna int de 2x4 bytes não está 100% correta. Você terá um cabeçalho de linha de 4 bytes (2 bytes de status e 2 bytes para o tamanho de dados de tamanho fixo). Então você terá 2x4 bytes para os dados. Dois bytes para a contagem de coluna e um único byte para o mapa de bits nulo, dando um registo de tamanho total de 15 bytes, não 17.
Mark S. Rasmussen
@ Mark S. Rasmussen: Onde você obtém "2 bytes para o tamanho fixo dos dados"? MSDN? E o bitmap nulo é sempre de 3 bytes: sqlskills.com/blogs/paul/post/… + msdn.microsoft.com/en-us/library/ms178085%28v=sql.90%29.aspx
gbn
Uau, ótimos detalhes! Eu respondi pelo campo de comprimento dos VARCHARs na minha estimativa acima, mas não pela contagem de colunas. Esta tabela não possui campos NULLable (deveria ter mencionado isso), ainda aloca bytes para eles?
Jon of All Trades
Os índices de reconstrução afetariam a parte dos dados do espaço necessário? Talvez a reconstrução do índice em cluster o faria. Inserções acontecem no meio, muito, se eu trocasse a ordem dos campos de cluster que parariam. A maior parte do restante não deve ser aplicada nesse caso, mas é uma ótima referência para o caso geral. Vou verificar seus links. Coisa boa!
Jon of All Trades
11
@gbn Os 2 bytes para o tamanho fixo dos dados fazem parte do cabeçalho da linha de 4 bytes que você mencionou. Este é o ponteiro que aponta para o final da parte do comprimento fixo dos dados / início da contagem de colunas / bitmap nulo. O bitmap NULL nem sempre é de três bytes. Se você incluir a contagem de colunas, será no mínimo três bytes, mas pode ser mais - eu divido o bitmap e a contagem de colunas na minha descrição. Além disso, o bitmap NULL nem sempre está presente, embora esteja neste caso.
Mark S. Rasmussen
5

Os tipos de dados foram alterados ao longo do tempo? As colunas de comprimento variável foram removidas? Os índices foram desfragmentados com frequência, mas nunca foram reconstruídos? Muitas linhas foram excluídas ou muitas colunas de comprimento variável foram atualizadas significativamente? Alguma boa discussão aqui .

Aaron Bertrand
fonte
Estou 97% confiante de que não alterei um tipo de dados ou removi um campo. Se o fizesse, teria sido muito cedo quando a tabela tivesse muito menos linhas. Não há exclusões ou atualizações, os dados são apenas anexados.
Jon of All Trades
Correção: não são exclusões, e um pouco. A tabela tem um crescimento líquido considerável, então eu imagino que esse espaço seja rapidamente reutilizado.
Jon of All Trades
Com muitas exclusões, os dados podem ou não ser reutilizados. Qual é a chave de cluster da tabela? As inserções estão no meio da tabela ou no final?
precisa saber é o seguinte
A chave em cluster é composta, nos campos VARCHARe DATETIME2, nessa ordem. As inserções serão distribuídas igualmente para o primeiro campo. Para o segundo campo, novos valores e sempre serão maiores que os existentes.
Jon of All Trades