Fiquei com a impressão de que, se somasse DATALENGTH()
todos os campos de todos os registros em uma tabela, obteria o tamanho total da tabela. Estou enganado?
SELECT
SUM(DATALENGTH(Field1)) +
SUM(DATALENGTH(Field2)) +
SUM(DATALENGTH(Field3)) TotalSizeInBytes
FROM SomeTable
WHERE X, Y, and Z are true
Usei essa consulta abaixo (que obtive on-line para obter tamanhos de tabela, apenas índices agrupados para que não inclua índices NC) para obter o tamanho de uma tabela específica no meu banco de dados. Para fins de cobrança (cobramos nossos departamentos pela quantidade de espaço que eles usam), preciso descobrir quanto espaço cada departamento usou nesta tabela. Eu tenho uma consulta que identifica cada grupo dentro da tabela. Eu só preciso descobrir quanto espaço cada grupo está ocupando.
O espaço por linha pode variar bastante devido aos VARCHAR(MAX)
campos na tabela, portanto, não posso apenas ter um tamanho médio * a proporção de linhas para um departamento. Quando uso a DATALENGTH()
abordagem descrita acima, recebo apenas 85% do espaço total usado na consulta abaixo. Pensamentos?
SELECT
s.Name AS SchemaName,
t.NAME AS TableName,
p.rows AS RowCounts,
(SUM(a.total_pages) * 8)/1024 AS TotalSpaceMB,
(SUM(a.used_pages) * 8)/1024 AS UsedSpaceMB,
((SUM(a.total_pages) - SUM(a.used_pages)) * 8)/1024 AS UnusedSpaceMB
FROM
sys.tables t with (nolock)
INNER JOIN
sys.schemas s with (nolock) ON s.schema_id = t.schema_id
INNER JOIN
sys.indexes i with (nolock) ON t.OBJECT_ID = i.object_id
INNER JOIN
sys.partitions p with (nolock) ON i.object_id = p.OBJECT_ID AND i.index_id = p.index_id
INNER JOIN
sys.allocation_units a with (nolock) ON p.partition_id = a.container_id
WHERE
t.is_ms_shipped = 0
AND i.OBJECT_ID > 255
AND i.type_desc = 'Clustered'
GROUP BY
t.Name, s.Name, p.Rows
ORDER BY
TotalSpaceMB desc
Foi sugerido que eu crie um índice filtrado para cada departamento ou particione a tabela, para poder consultar diretamente o espaço usado por índice. Os índices filtrados podem ser criados programaticamente (e descartados novamente durante uma janela de manutenção ou quando eu precisar executar o faturamento periódico), em vez de usar o espaço o tempo todo (as partições seriam melhores nesse aspecto).
Eu gosto dessa sugestão e normalmente faria isso. Mas, para ser sincero, uso o "cada departamento" como exemplo para explicar por que preciso disso, mas para ser sincero, não é exatamente por isso. Devido a razões de confidencialidade, não consigo explicar exatamente o motivo pelo qual preciso desses dados, mas é análogo a diferentes departamentos.
Em relação aos índices não clusterizados nesta tabela: Se eu puder obter os tamanhos dos índices NC, isso seria ótimo. No entanto, os índices NC representam <1% do tamanho do índice clusterizado, portanto não podemos incluí-los. No entanto, como incluiríamos os índices NC de qualquer maneira? Não consigo nem obter um tamanho exato para o índice Clustered :)
fonte
Respostas:
Please note that the following info is not intended to be a comprehensive
description of how data pages are laid out, such that one can calculate
the number of bytes used per any set of rows, as that is very complicated.
Dados não são a única coisa que ocupa espaço em uma página de dados de 8k:
Há espaço reservado. Você só pode usar 8060 dos 8192 bytes (ou seja, 132 bytes que nunca foram seus):
DBCC PAGE
, e é por isso que é mantido aqui separado, em vez de ser incluído nas informações por linha abaixo.NULL
. 1 byte por cada conjunto de 8 colunas. E para todas as colunas, mesmoNOT NULL
as. Portanto, no mínimo 1 byte.ALLOW_SNAPSHOT_ISOLATION ON
ouREAD_COMMITTED_SNAPSHOT ON
).Ponteiros LOB para dados que não são armazenados em linha. Portanto, isso representaria
DATALENGTH
+ pointer_size. Mas estes não são de tamanho padrão. Consulte a seguinte publicação no blog para obter detalhes sobre este tópico complexo: Qual é o tamanho do ponteiro LOB para tipos (MAX) como Varchar, Varbinary, Etc? . Entre essa postagem vinculada e alguns testes adicionais que eu fiz , as regras (padrão) devem ser as seguintes:TEXT
,NTEXT
eIMAGE
):text in row
opção, então:VARCHAR(MAX)
,NVARCHAR(MAX)
eVARBINARY(MAX)
):large value types out of row
opção, use sempre um ponteiro de 16 bytes para armazenamento LOB.Páginas de estouro de LOB: se um valor for 10k, isso exigirá 1 página de 8k de estouro e parte da segunda página. Se nenhum outro dado puder ocupar o espaço restante (ou é permitido, não tenho certeza dessa regra), você terá aproximadamente 6kb de espaço "desperdiçado" nessa segunda página de dados de estouro de LOB.
Espaço não utilizado: uma página de dados de 8k é exatamente isso: 8192 bytes. Não varia em tamanho. Os dados e metadados colocados nele, no entanto, nem sempre se encaixam perfeitamente em todos os 8192 bytes. E as linhas não podem ser divididas em várias páginas de dados. Portanto, se você tiver 100 bytes restantes, mas nenhuma linha (ou nenhuma linha que se encaixaria nesse local, dependendo de vários fatores) pode caber lá, a página de dados continuará ocupando 8192 bytes e sua 2ª consulta contará apenas o número de páginas de dados. Você pode encontrar esse valor em dois lugares (lembre-se de que parte desse valor é uma parte desse espaço reservado):
DBCC PAGE( db_name, file_id, page_id ) WITH TABLERESULTS;
ProcureParentObject
= "PAGE HEADER:" eField
= "m_freeCnt". OValue
campo é o número de bytes não utilizados.SELECT buff.free_space_in_bytes FROM sys.dm_os_buffer_descriptors buff WHERE buff.[database_id] = DB_ID(N'db_name') AND buff.[page_id] = page_id;
Este é o mesmo valor relatado por "m_freeCnt". Isso é mais fácil que o DBCC, pois pode obter muitas páginas, mas também exige que as páginas tenham sido lidas no buffer pool em primeiro lugar.Espaço reservado por
FILLFACTOR
<100. As páginas criadas recentemente não respeitam aFILLFACTOR
configuração, mas executar um REBUILD reservará esse espaço em cada página de dados. A idéia por trás do espaço reservado é que ele será usado por inserções não sequenciais e / ou atualizações que já expandem o tamanho das linhas da página, devido à atualização de colunas de comprimento variável com um pouco mais de dados (mas não o suficiente para causar uma divisão de página). Mas você pode facilmente reservar espaço em páginas de dados que naturalmente nunca receberão novas linhas e nunca terão as linhas existentes atualizadas ou, pelo menos, não atualizadas de maneira a aumentar o tamanho da linha.Divisões de página (fragmentação): a necessidade de adicionar uma linha a um local que não tenha espaço para a linha causará uma divisão de página. Nesse caso, aproximadamente 50% dos dados existentes são movidos para uma nova página e a nova linha é adicionada a uma das 2 páginas. Mas agora você tem um pouco mais de espaço livre que não é contabilizado pelos
DATALENGTH
cálculos.Linhas marcadas para exclusão. Quando você exclui linhas, elas nem sempre são removidas imediatamente da página de dados. Se não puderem ser removidos imediatamente, serão "marcados para morte" (referência de Steven Segal) e serão fisicamente removidos posteriormente pelo processo de limpeza de fantasmas (acredito que esse seja o nome). No entanto, estes podem não ser relevantes para esta questão em particular.
Páginas fantasmas? Não tenho certeza se esse é o termo adequado, mas às vezes as páginas de dados não são removidas até que uma REBUILD do Clustered Index seja concluída. Isso também seria responsável por mais páginas do
DATALENGTH
que as somadas. Isso geralmente não deveria acontecer, mas já o encontrei uma vez, há vários anos.Colunas esparsas: as colunas esparsas economizam espaço (principalmente para tipos de dados de comprimento fixo) em tabelas em que uma grande% das linhas é
NULL
para uma ou mais colunas. ASPARSE
opçãoNULL
aumenta o valor do tipo 0 bytes (em vez da quantidade normal de comprimento fixo, como 4 bytes para umINT
), mas os valores diferentes de NULL ocupam 4 bytes adicionais para os tipos de comprimento fixo e uma quantidade variável para tipos de comprimento variável. O problema aqui é queDATALENGTH
não inclui os 4 bytes extras para valores diferentes de NULL em uma coluna SPARSE; portanto, esses 4 bytes precisam ser adicionados novamente. Você pode verificar se háSPARSE
colunas através de:E, em seguida, para cada
SPARSE
coluna, atualize a consulta original para usar:Observe que o cálculo acima para adicionar um padrão de 4 bytes é um pouco simplista, pois funciona apenas para tipos de comprimento fixo. E, há metadados adicionais por linha (pelo que posso dizer até agora) que reduzem o espaço disponível para os dados, simplesmente tendo pelo menos uma coluna SPARSE. Para mais detalhes, consulte a página do MSDN para Usar colunas esparsas .
Índice e outras páginas (por exemplo, IAM, PFS, GAM, SGAM, etc): essas não são páginas de "dados" em termos de dados do usuário. Isso aumentará o tamanho total da tabela. Se você estiver usando o SQL Server 2012 ou mais recente, poderá usar a
sys.dm_db_database_page_allocations
DMF (Dynamic Management Function) para ver os tipos de página (as versões anteriores do SQL Server podem usarDBCC IND(0, N'dbo.table_name', 0);
):Nem o
DBCC IND
norsys.dm_db_database_page_allocations
(com essa cláusula WHERE) reportará nenhuma página de índice e apenas oDBCC IND
reportará pelo menos uma página do IAM.DATA_COMPRESSION: se você tiver
ROW
ou aPAGE
Compactação ativada no Clustered Index ou Heap, poderá esquecer a maior parte do que foi mencionado até agora. O cabeçalho da página de 96 bytes, a matriz de slot de 2 bytes por linha e as informações de versão de 14 bytes por linha ainda estão lá, mas a representação física dos dados se torna altamente complexa (muito mais do que o que já foi mencionado quando o Compactação não está sendo usado). Por exemplo, com compactação de linha, o SQL Server tenta usar o menor contêiner possível para caber em cada coluna, por cada linha. Portanto, se você tiver umaBIGINT
coluna que, caso contrário (supondo queSPARSE
também não esteja ativada), sempre ocupará 8 bytes, se o valor estiver entre -128 e 127 (ou seja, número inteiro de 8 bits assinado), ele usará apenas 1 byte e se o valor valor poderia caber em umSMALLINT
, ocupará apenas 2 bytes. Os tipos inteiros que ou sãoNULL
ou0
não ocupam espaço e são simplesmente indicados como sendoNULL
ou "vazias" (isto é,0
) em uma matriz de mapeamento para as colunas. E há muitas, muitas outras regras. Dados têm Unicode (NCHAR
,NVARCHAR(1 - 4000)
mas nãoNVARCHAR(MAX)
, mesmo se armazenado em-linha)? A compactação Unicode foi adicionada no SQL Server 2008 R2, mas não há como prever o resultado do valor "compactado" em todas as situações sem fazer a compactação real, dada a complexidade das regras .Realmente, sua segunda consulta, embora mais precisa em termos de espaço físico total ocupado em disco, só é realmente precisa ao executar um
REBUILD
dos índices de cluster. E depois disso, você ainda precisa contabilizar qualquerFILLFACTOR
configuração abaixo de 100. E mesmo assim, sempre há cabeçalhos de página e, com frequência, uma quantidade suficiente de espaço "desperdiçado" que simplesmente não é preenchível por ser muito pequena para caber em qualquer linha dessa linha. tabela, ou pelo menos a linha que logicamente deve ir nesse slot.Com relação à precisão da 2ª consulta na determinação do "uso de dados", parece mais justo recuperar os bytes do cabeçalho da página, pois eles não são uso de dados: são custos indiretos de custo dos negócios. Se houver 1 linha em uma página de dados e essa linha for apenas a
TINYINT
, esse byte ainda exigirá que a página de dados existisse e, portanto, os 96 bytes do cabeçalho. Esse departamento deve ser cobrado por toda a página de dados? Se essa página de dados for preenchida pelo Departamento 2, eles dividirão uniformemente esse custo "adicional" ou pagarão proporcionalmente? Parece mais fácil apenas fazer o backup. Nesse caso, usar um valor de8
para multiplicarnumber of pages
é muito alto. E se:Portanto, use algo como:
para todos os cálculos nas colunas "número_de_páginas".
E , considerando que o uso
DATALENGTH
por cada campo não pode retornar os metadados por linha, que devem ser adicionados à sua consulta por tabela onde você obtém oDATALENGTH
por cada campo, filtrando cada "departamento":ALLOW_SNAPSHOT_ISOLATION
ouREAD_COMMITTED_SNAPSHOT
definir aON
)NULL
e, se o valor se ajustar à linha, ele poderá ser muito menor ou muito maior que o ponteiro e se o valor for armazenado. linha, o tamanho do ponteiro pode depender da quantidade de dados que há. No entanto, como queremos apenas uma estimativa (ou seja, "swag"), parece que 24 bytes é um bom valor para usar (bem, tão bom quanto qualquer outro ;-). Este é oMAX
campo por cada .Portanto, use algo como:
Em geral (cabeçalho da linha + número de colunas + matriz do slot + bitmap NULL):
Em geral (detecção automática se "informações da versão" estiverem presentes):
SE houver colunas de tamanho variável, adicione:
Se houver alguma
MAX
coluna / LOB, adicione:Em geral:
Isso não é exato e, novamente, não funcionará se você tiver a compactação de linha ou de página ativada no heap ou no índice clusterizado, mas definitivamente deve aproximá-lo.
ATUALIZAÇÃO sobre o mistério da diferença de 15%
Nós (inclusive eu) estávamos tão focados em pensar em como as páginas de dados são dispostas e em como isso
DATALENGTH
pode explicar coisas que não passamos muito tempo revisando a segunda consulta. Executei essa consulta em uma única tabela e comparei esses valores com o que estava sendo relatadosys.dm_db_database_page_allocations
e eles não eram os mesmos valores para o número de páginas. Em um palpite, removi as funções agregadasGROUP BY
e substituí aSELECT
lista pora.*, '---' AS [---], p.*
. E então ficou claro: as pessoas devem ter cuidado de onde nessas interwebs obscuras elas obtêm suas informações e scripts de ;-). A segunda consulta postada na pergunta não está exatamente correta, especialmente para essa pergunta em particular.Problema menor: fora dele não faz muito sentido
GROUP BY rows
(e não tem essa coluna em uma função agregada), a junção entresys.allocation_units
esys.partitions
não é tecnicamente correta. Existem 3 tipos de unidades de alocação, e uma delas deve se unir a um campo diferente. Muitas vezespartition_id
ehobt_id
são os mesmos, portanto, talvez nunca haja um problema, mas às vezes esses dois campos têm valores diferentes.Grande problema: a consulta usa o
used_pages
campo Esse campo abrange todos os tipos de páginas: Dados, Índice, IAM, etc, tc. Há um outro campo, mais apropriado para uso quando em causa apenas com os dados reais:data_pages
.Adaptei a 2ª consulta da pergunta com os itens acima em mente e usando o tamanho da página de dados que faz o retorno do cabeçalho da página. Também removi dois JOINs desnecessários:
sys.schemas
(substituído por call toSCHEMA_NAME()
) esys.indexes
(o Clustered Index é sempreindex_id = 1
e temosindex_id
dentrosys.partitions
).fonte
Talvez seja uma resposta grunge, mas é isso que eu faria.
Portanto, DATALENGTH representa apenas 86% do total. Ainda é uma divisão muito representativa. A sobrecarga na excelente resposta de srutzky deve ter uma divisão bastante uniforme.
Eu usaria sua segunda consulta (páginas) para o total. E use o primeiro (comprimento de dados) para alocar a divisão. Muitos custos são alocados usando uma normalização.
E você deve considerar que uma resposta mais próxima aumentará os custos, de modo que mesmo o departamento que perdeu uma divisão ainda poderá pagar mais.
fonte