Eu criei a seguinte tabela:
CREATE TABLE dbo.TestStructure
(
id INT NOT NULL,
filler1 CHAR(36) NOT NULL,
filler2 CHAR(216) NOT NULL
);
e, em seguida, criou um índice em cluster:
CREATE CLUSTERED INDEX idx_cl_id
ON dbo.TestStructure(id);
Em seguida, preenchi-o com 30 linhas, cada tamanho com 256 bytes (com base na declaração da tabela):
DECLARE @i AS int = 0;
WHILE @i < 30
BEGIN
SET @i = @i + 1;
INSERT INTO dbo.TestStructure (id, filler1, filler2)
VALUES (@i, 'a', 'b');
END;
Agora, com base nas informações que li no "Kit de treinamento (Exame 70-461): Consultando o Microsoft SQL Server 2012 (Itzik Ben-Gan)" no livro:
O SQL Server organiza internamente os dados em um arquivo de dados em páginas. Uma página é uma unidade de 8 KB e pertence a um único objeto; por exemplo, para uma tabela ou um índice. Uma página é a menor unidade de leitura e escrita. As páginas são organizadas em extensão. Uma extensão consiste em oito páginas consecutivas. As páginas de uma extensão podem pertencer a um único objeto ou a vários objetos. Se as páginas pertencerem a vários objetos, a extensão será chamada de extensão mista; se as páginas pertencerem a um único objeto, a extensão será chamada de extensão uniforme. O SQL Server armazena as oito primeiras páginas de um objeto em extensões mistas. Quando um objeto excede oito páginas, o SQL Server aloca extensões uniformes adicionais para esse objeto. Com essa organização, objetos pequenos desperdiçam menos espaço e objetos grandes são menos fragmentados.
Portanto, aqui tenho a primeira página de 8 KB de extensão mista, preenchida com 7680 bytes (inseri uma linha de tamanho de 30 vezes 256 bytes, portanto, 30 * 256 = 7680), para verificar o tamanho em que corri proc de verificação de tamanho - ele retorna o seguinte resultado
index_type_desc: CLUSTERED INDEX
index_depth: 1
index_level: 0
page_count: 1
record_count: 30
avg_page_space_used_in_percent: 98.1961947121324
name : TestStructure
rows : 30
reserved : 16 KB
data : 8 KB
index_size : 8 KB
unused : 0 KB
Portanto, 16 KB são reservados para a tabela, a primeira página de 8 KB é para a página Root IAM, a segunda é para a página de armazenamento de dados em folha, com 8 KB com ocupação de ~ 7,5 KB, agora quando insiro uma nova linha com 256 bytes:
INSERT INTO dbo.TestStructure (id, filler1, filler2)
VALUES (1, 'a', 'b');
ele não é armazenado na mesma página, embora tenha um espaço de 256 bytes (7680 b + 256 = 7936, que ainda é menor que 8 KB), uma nova página de dados é criada, mas essa nova linha pode caber na mesma página antiga , por que o SQL Server cria uma nova página quando pode economizar espaço e tempo de pesquisa, inserindo-a na página existente?
Nota: o mesmo está acontecendo no índice de heap.
fonte
Embora seja verdade que o SQL Server usa páginas de dados de 8k (8192 bytes) para armazenar 1 ou mais linhas, cada página de dados possui alguma sobrecarga (96 bytes) e cada linha tem alguma sobrecarga (pelo menos 9 bytes). Os 8192 bytes não são puramente dados.
Para um exame mais detalhado de como isso funciona, consulte minha resposta para a seguinte pergunta do DBA.SE:
SOMA de DATALENGTHs que não corresponde ao tamanho da tabela de sys.allocation_units
Usando as informações nessa resposta vinculada, podemos obter uma imagem mais clara do tamanho real da linha:
O uso
DBCC PAGE
confirma meu cálculo mostrando:Record Size 263
(paratempdb
) eRecord Size 277
(para um banco de dados definido comoALLOW_SNAPSHOT_ISOLATION ON
).Agora, com 30 linhas, ou seja:
SEM informações da versão
30 * 263 nos daria 7890 bytes. Em seguida, adicione os 96 bytes do cabeçalho da página aos 7986 bytes usados. Por fim, adicione os 60 bytes (2 por linha) da matriz de slots para um total de 8046 bytes usados na página e 146 restantes. O uso
DBCC PAGE
confirma meu cálculo mostrando:m_slotCnt 30
(ou seja, número de linhas)m_freeCnt 146
(ou seja, número de bytes restantes na página)m_freeData 7986
(isto é, dados + cabeçalho da página - 7890 + 96 - a matriz de slots não é fatorada no cálculo de bytes "usados")COM informações da versão
30 * 277 bytes para um total de 8310 bytes. Mas o 8310 está acima de 8192, e isso nem foi responsável pelo cabeçalho da página de 96 bytes nem pela matriz de slots de 2 bytes por linha (30 * 2 = 60 bytes), o que deve fornecer apenas 8036 bytes utilizáveis para as linhas.
MAS, que tal 29 linhas? Isso nos daria 8033 bytes de dados (29 * 277) + 96 bytes para o cabeçalho da página + 58 bytes para a matriz de slots (29 * 2) igual a 8187 bytes. E isso deixaria a página com 5 bytes restantes (8192 - 8187; inutilizável, é claro). O uso
DBCC PAGE
confirma meu cálculo mostrando:m_slotCnt 29
(ou seja, número de linhas)m_freeCnt 5
(ou seja, número de bytes restantes na página)m_freeData 8129
(isto é, dados + cabeçalho da página - 8033 + 96 - a matriz de slots não é fatorada no cálculo de bytes "usados")Em relação aos montões
As pilhas preenchem as páginas de dados de maneira um pouco diferente. Eles mantêm uma estimativa muito aproximada da quantidade de espaço restante na página. Ao olhar para a saída DBCC, olhar para a linha para:
PAGE HEADER: Allocation Status PFS (1:1)
. Você veráVALUE
algo mostrando ao longo das linhas de0x60 MIXED_EXT ALLOCATED 0_PCT_FULL
(quando eu olhei para a tabela em cluster) ou0x64 MIXED_EXT ALLOCATED 100_PCT_FULL
quando Heap. Isso é avaliado por transação, portanto, fazer inserções individuais, como o teste que está sendo realizado aqui, pode mostrar resultados diferentes entre as tabelas Cluster e Heap. Executar uma única operação DML para todas as 30 linhas, no entanto, preencherá a pilha conforme o esperado.No entanto, nenhum desses detalhes sobre os Heaps afeta diretamente esse teste específico, pois as duas versões da tabela cabem em 30 linhas, com apenas 146 bytes restantes. Isso não é espaço suficiente para outra linha, independentemente de Clustered ou Heap.
Lembre-se de que este teste é bastante simples. O cálculo do tamanho real de uma linha pode ficar muito complicado dependendo de vários fatores, como:
SPARSE
Compactação de dados, dados LOB, etc.Para ver os detalhes da página de dados, use a seguinte consulta:
** O valor "versão info" de 14 bytes estará presente se o seu banco de dados estiver definido como
ALLOW_SNAPSHOT_ISOLATION ON
ouREAD_COMMITTED_SNAPSHOT ON
.fonte
tempdb
? Consegui reproduzir os números exatos fornecidos pelo OP.ALLOW_SNAPSHOT_ISOLATION
. Eu também tenteitempdb
e vi que as "informações da versão" não estão lá, portanto, 30 linhas se encaixam. Vou atualizar para adicionar as novas informações.A estrutura real da página de dados é bastante complexa. Embora seja geralmente declarado que 8060 bytes por página estão disponíveis para dados do usuário, há alguma sobrecarga adicional não contada em nenhum lugar que resulte nesse comportamento.
No entanto, você deve ter notado que o SQL Server na verdade dá uma dica de que a 31ª linha não cabe na página. Para que a próxima linha caiba na mesma página, o
avg_page_space_used_in_percent
valor deve estar abaixo de 100% - (100/31) = 96.774194 e, no seu caso, está muito acima disso.PS: Creio ter visto uma explicação detalhada e detalhada da estrutura da página de dados em um dos livros "SQL Server Internals" de Kalen Delaney, mas foi há quase 10 anos, então, desculpe-me por não me lembrar de mais detalhes. Além disso, a estrutura da página tende a mudar de versão para versão.
fonte