Sqlserver
 sql >> Base de Dados >  >> RDS >> Sqlserver

Entendendo o tamanho do armazenamento 'datetime2' no SQL Server


Neste artigo, compartilho algumas observações que tive sobre o datetime2 tamanho de armazenamento do tipo de dados no SQL Server. Talvez eu esclareça alguns pontos sobre o tamanho real de armazenamento usado por esse tipo de dado quando armazenado em um banco de dados.

Em particular, observo o seguinte:
  • Documentação da Microsoft
  • Dados armazenados em uma variável
    • Comprimento em bytes usando DATALENGTH()
    • Comprimento em bytes usando DATALENGTH() depois de converter para varbinary
  • Dados armazenados em um banco de dados
    • Comprimento em bytes usando COL_LENGTH()
    • Comprimento em bytes usando DBCC PAGE()

Alguns deles parecem se contradizer, e você verá duas quantidades de tamanho de armazenamento diferentes para o mesmo valor, dependendo de onde você olhar.



Um datetime2 valor pode mostrar um tamanho de armazenamento diferente, dependendo se está armazenado em um banco de dados, como um datetime2 variável ou convertida em varbinary .

Mas há uma explicação plausível para isso – depende de onde a precisão está sendo armazenado.

Durante minha pesquisa sobre esse problema, encontrei o artigo detalhado de Ronen Ariely sobre como datetime2 está armazenado no arquivo de dados muito informativo, e isso me levou a executar alguns testes semelhantes em meu próprio ambiente de desenvolvimento e apresentá-los aqui.

Documentação da Microsoft


Primeiro, vamos ver o que diz a documentação oficial.

Documentação da Microsoft sobre o datetime2 tipo de dados informa que seu tamanho de armazenamento é o seguinte:

6 bytes para precisão menor que 3.
7 bytes para precisão 3 ou 4.
Todas as outras precisão requerem 8 bytes.

Mas qualifica a tabela acima com a seguinte declaração:

O primeiro byte de um datetime2 value armazena a precisão do valor, o que significa o armazenamento real necessário para um datetime2 value é o tamanho de armazenamento indicado na tabela acima mais 1 byte adicional para armazenar a precisão. Isso torna o tamanho máximo de um datetime2 valor 9 bytes – 1 byte armazena precisão mais 8 bytes para armazenamento de dados com precisão máxima.

Assim, dadas as informações acima, a conclusão óbvia a ser tirada seria que a tabela poderia/(deveria?) ser escrita da seguinte forma:

7 bytes para precisão menor que 3.
8 bytes para precisão 3 ou 4.
Todas as outras precisão requerem 9 bytes.

Dessa forma, eles não precisariam qualificá-lo com informações extras sobre precisão.

Mas não é tão simples assim.

Dados armazenados em uma variável


Primeiro, vamos armazenar um datetime2 valor em uma variável e verifique seu tamanho de armazenamento. Em seguida, converterei esse valor em varbinary e verifique novamente.

Comprimento em bytes usando DATALENGTH


Aqui está o que acontece se usarmos o DATALENGTH() função para retornar o número de bytes usados ​​para um datetime2(7) valor:
DECLARE @d datetime2(7);SET @d ='2025-05-21 10:15:30.1234567';SELECT @d AS 'Value', DATALENGTH(@d) AS 'Length in Bytes'; 
Resultado
+-----------------------------+---------------- ---+| Valor | Comprimento em Bytes ||-----------------------------+--------------- ----|| 21-05-2025 10:15:30.1234567 | 8 |+-----------------------------+----------------- --+

O valor neste exemplo tem a escala máxima de 7 (porque eu declaro a variável como datetime2(7) ), e retorna um comprimento de 8 bytes.

Isso parece contradizer o que a Microsoft afirma sobre a necessidade de um byte extra para armazenar a precisão. Para citar a Microsoft, Isso torna o tamanho máximo de um datetime2 valor 9 bytes – 1 byte armazena precisão mais 8 bytes para armazenamento de dados com precisão máxima. .

Embora seja verdade que parecemos obter 8 bytes para armazenamento de dados , parece que está faltando o 1 byte usado para armazenar a precisão.

No entanto, se convertermos o valor para varbinary temos uma história diferente.

Comprimento em bytes após a conversão para 'varbinary'


Veja o que acontece se convertermos nosso datetime2 valor para varbinary :
DECLARE @d datetime2(7);SET @d ='2025-05-21 10:15:30.1234567';SELECT CONVERT(VARBINARY(10), @d) AS 'Value', DATALENGTH(CONVERT(VARBINARY( 10), @d)) AS 'Comprimento em Bytes';

Resultado
+----------------------+-------------------+| Valor | Comprimento em Bytes ||----------------------+-------------------|| 0x0787A311FC553F480B | 9 |+----------------------+-------------------+

Neste caso, obtemos 9 bytes.

Esta é uma representação hexadecimal do datetime2 valor. O valor real da data e hora (e sua precisão) é tudo após o 0x . Cada par de caracteres hexadecimais é um byte. Existem 9 pares e, portanto, 9 bytes. Isso é confirmado quando usamos DATALENGTH() para retornar o comprimento em bytes.

Neste exemplo podemos ver que o primeiro byte é 07 . Isso representa a precisão (usei uma escala de 7 e é isso que é exibido aqui).

Se eu alterar a escala, podemos ver que o primeiro byte muda para corresponder à escala:
DECLARE @d datetime2(3);SET @d ='2025-05-21 10:15:30.1234567';SELECT CONVERT(VARBINARY(10), @d) AS 'Value', DATALENGTH(CONVERT(VARBINARY( 10), @d)) AS 'Comprimento em Bytes';

Resultado
+--------------------+-------------------+| Valor | Comprimento em Bytes ||--------------------+----------------------------------|| 0x034B8233023F480B | 8 |+--------------------+-------------------+

Também podemos ver que o comprimento é reduzido em conformidade.

Portanto, neste caso, nossos resultados correspondem perfeitamente à documentação da Microsoft – um byte extra foi adicionado para a precisão.

Muitos desenvolvedores supõem que é assim que o SQL Server armazena seu datetime2 valores no banco de dados. No entanto, essa suposição parece estar incorreta.

Dados armazenados em um banco de dados


Neste exemplo, crio um banco de dados que contém uma tabela com vários datetime2(n) colunas. Eu então uso COL_LENGTH() para retornar o comprimento de cada coluna, em bytes. Depois disso, insiro valores nele, antes de usar DBCC PAGE para verificar o tamanho do armazenamento que cada datetime2 valor ocupa o arquivo de paginação.

Crie um banco de dados:
CRIAR BANCO DE DADOS;

Crie uma tabela:
USE Test;CREATE TABLE Datetime2Test ( d0 datetime2(0), d1 datetime2(1), d2 datetime2(2), d3 datetime2(3), d4 datetime2(4), d5 datetime2(5), d6 datetime2(6) ), d7 datetime2(7) );

Neste caso eu crio oito colunas – uma para cada escala definida pelo usuário que podemos usar com datetime2(n) .

Agora podemos verificar o tamanho do armazenamento de cada coluna.

Comprimento em Bytes usando COL_LENGTH()


Use COL_LENGTH() para verificar o comprimento (em bytes) de cada coluna:
SELECT COL_LENGTH ( 'Datetime2Test' , 'd0' ) AS 'd0', COL_LENGTH ( 'Datetime2Test' , 'd1' ) AS 'd1', COL_LENGTH ( 'Datetime2Test' , 'd2' ) AS 'd2', COL_LENGTH ( 'Datetime2Test' , 'd3' ) AS 'd3', COL_LENGTH ( 'Datetime2Test' , 'd4' ) AS 'd4', COL_LENGTH ( 'Datetime2Test' , 'd5' ) AS 'd5', COL_LENGTH ( 'Datetime2Test' , 'd6' ) AS 'd6', COL_LENGTH( 'Datetime2Test' , 'd7' ) AS 'd7'; 

Resultado:
+------+------+------+------+------+------+---- --+------+| d0 | d1 | d2 | d3 | d4 | d5 | d6 | d7 ||------+------+------+------+------+------+----- -+------|| 6 | 6 | 6 | 7 | 7 | 8 | 8 | 8 |+------+------+------+------+------+------+----- -+------+

Então, mais uma vez, parece que não conseguimos o byte extra usado para armazenar a precisão.

Use DBCC PAGE para verificar os dados armazenados


Agora vamos usar DBCC PAGE para encontrar o tamanho real de armazenamento dos dados que armazenamos nesta tabela.

Primeiro, vamos inserir alguns dados:
DECLARE @d datetime2(7) ='2025-05-21 10:15:30.1234567';INSERT INTO Datetime2Test ( d0, d1, d2, d3, d4, d5, d6, d7 )SELECT @d, @d , @d, @d, @d, @d, @d, @d;

Agora selecione os dados (só para conferir):
SELECT * FROM Datetime2Test;

Resultado (usando saída vertical):
d0 | 2025-05-21 10:15:30d1 | 2025-05-21 10:15:30.1d2 | 2025-05-21 10:15:30.12d3 | 21-05-2025 10:15:30.123d4 | 21-05-2025 10:15:30.1235d5 | 21-05-2025 10:15:30.12346d6 | 21-05-2025 10:15:30.123457d7 | 2025-05-21 10:15:30.1234567

Conforme esperado, os valores usam a precisão especificada anteriormente no nível da coluna.

Agora, antes de usarmos DBCC PAGE() , precisamos saber qual PagePID passar para ele. Podemos usar DBCC IND() para encontrar isso.

Encontre o PagePID:
DBCC IND('Teste', 'dbo.Datetime2Test', 0);

Resultado (usando saída vertical):
-[ RECORD 1 ]-------------------------PageFID | 1PáginaPID | 306IAMFID | NULLIAMPID | NULLObjectID | 1205579333IndexID | 0PartitionNumber | 1PartitionID | 72057594043039744iam_chain_type | DataPageType em linha | 10Nível de Índice | NULLNextPageFID | 0PróximaPáginaPID | 0PrevPageFID | 0PrevPagePID | 0-[ RECORD 2 ] -------------------------PageFID | 1PáginaPID | 360IAMFID | 1IAMPID | 306ID do objeto | 1205579333IndexID | 0PartitionNumber | 1PartitionID | 72057594043039744iam_chain_type | DataPageType em linha | 1Nível de Índice | 0PróximaPáginaFID | 0PróximaPáginaPID | 0PrevPageFID | 0PrevPagePID | 0

Isso retorna dois registros. Estamos interessados ​​no PageType de 1 (o segundo registro). Queremos o PagePID desse registro. Neste caso, o PagePID é 360 .

Agora podemos pegar esse PagePID e usá-lo da seguinte forma:
DBCC TRACEON(3604, -1);DBCC PAGE(Teste, 1, 360, 3);

Isso produz muitos dados, mas estamos interessados ​​principalmente na seguinte parte:
Slot 0 Coluna 1 Deslocamento 0x4 Comprimento 6 Comprimento (físico) 6d0 =21-05-2025 10:15:30 Slot 0 Coluna 2 Deslocamento 0xa Comprimento 6 Comprimento (físico) 6d1 =21-05-2025 10:15:30.1 Slot 0 Coluna 3 Offset 0x10 Comprimento 6 Comprimento (físico) 6d2 =2025-05-21 10:15:30.12 Slot 0 Coluna 4 Offset 0x16 Comprimento 7 Comprimento (físico) 7d3 =2025-05-21 10:15:30.123 Slot 0 Coluna 5 Deslocamento 0x1d Comprimento 7 Comprimento (físico) 7d4 =2025-05-21 10:15:30.1235 Slot 0 Coluna 6 Offset 0x24 Comprimento 8 Comprimento (físico) 8d5 =2025-05-21 10:15:30.12346 Slot 0 Coluna 7 Offset 0x2c Comprimento 8 Comprimento (físico) 8d6 =2025-05-21 10:15:30.123457 Slot 0 Coluna 8 Offset 0x34 Comprimento 8 Comprimento (físico) 8d7 =2025-05-21 10:15:30.1234567 

Portanto, parece que ele não usa o byte extra para precisão.

Mas vamos examinar os dados reais antes de chegarmos a qualquer conclusão.

Os dados reais são armazenados nesta parte do arquivo de paginação:
 Memory Dump @0x000000041883A0600000000000000000:10003C00 4290003F 480B95A2 053F480B D459383F .. 0. B ..? H. •. H.zå.Ü0000000000000028:003f480b c1f63499 083f480b 87a311fc 553f480b .?H.Áö4..?H.‡£.üU?H.000000000000003C:080000 ... ... 

Como você pode ver, nada disso se parece com os resultados que obteríamos convertendo o datetime2 valor para varbinary . Mas está bem perto.

Aqui está o que parece se eu excluir algumas coisas:
4290003f 480b95a2 053f480b d459383f480b4b82 33023f48 0bf31603 163f480b 7ae51edc003f480b c1f63499 083f480b 87a311fc 553f480b

Os dígitos hexadecimais restantes contêm todos os nossos dados de data e hora, mas não a precisão . No entanto, devemos reorganizar os espaços para obter os valores reais de cada linha.

Aqui está o resultado final. Coloquei cada valor de data/hora em uma nova linha para melhor legibilidade.
4290003f480b 95a2053f480b d459383f480b 4b8233023f480bf31603163f480b 7ae51edc003f480b c1f63499083f480b 87a311fc553f480b

Esses são os valores hexadecimais reais (menos a precisão ) que obteríamos se convertêssemos o datetime2 valor para varbinary . Para ter certeza, vamos em frente e fazer exatamente isso:
SELECT CONVERT(VARBINARY(10), d0) AS 'd0', CONVERT(VARBINARY(10), d1) AS 'd1', CONVERT(VARBINARY(10), d2) AS 'd2', CONVERT(VARBINARY( 10), d3) COMO 'd3', CONVERT(VARBINARY(10), d4) COMO 'd4', CONVERT(VARBINARY(10), d5) COMO 'd5', CONVERT(VARBINARY(10), d6) COMO 'd6 ', CONVERT(VARBINARY(10), d7) AS 'd7'FROM Datetime2Test;

Resultado (usando saída vertical):
d0 | 0x004290003F480Bd1 | 0x0195A2053F480Bd2 | 0x02D459383F480Bd3 | 0x034B8233023F480Bd4 | 0x04F31603163F480Bd5 | 0x057AE51EDC003F480Bd6 | 0x06C1F63499083F480Bd7 | 0x0787A311FC553F480B

Portanto, obtemos o mesmo resultado - exceto que foi prefixado com a precisão.

Mas para deixar as coisas bem claras, aqui está uma tabela que compara os dados reais do arquivo de página com os resultados do CONVERT() Operação.
Dados do arquivo de página CONVERT() dados
4290003f480b 004290003F480B
95a2053f480b 0195A2053F480B
d459383f480b 02D459383F480B
4b8233023f480b 034B8233023F480B
f31603163f480b 04F31603163F480B
7ae51edc003f480b 057AE51EDC003F480B
c1f63499083f480b 06C1F63499083F480B
87a311fc553f480b 0787A311FC553F480B

Assim, podemos ver claramente que o arquivo de paginação não armazena a precisão, mas o resultado convertido sim.

Eu destaquei as partes reais de data e hora em vermelho. Eu também removi o 0x prefixo dos resultados convertidos, para que apenas os dados de data/hora reais sejam exibidos (junto com a precisão).

Observe também que o hexadecimal não diferencia maiúsculas de minúsculas, portanto, o fato de um usar letras minúsculas e o outro maiúsculas não é um problema.

Conclusão


Ao converter um datetime2 valor para varbinary , ele precisa de um byte extra para armazenar a precisão. Ele precisa da precisão para interpretar a porção de tempo (porque esta é armazenada como um intervalo de tempo, cujo valor exato dependerá da precisão).

Quando armazenado em um banco de dados, a precisão é especificada uma vez no nível da coluna. Isso parece lógico, pois não há necessidade de armazenar um byte extra com cada linha se puder ser especificado no nível da coluna. Então, se você especificar, digamos, datetime2(7) no nível da coluna, cada linha será datetime2(7) . Não há necessidade de reiterar isso em todas as linhas.

Ronen Ariely chegou à mesma conclusão em seu artigo mencionado acima.

Se você tiver um milhão de linhas com datetime2(7) valores, armazenar a precisão com cada linha exigiria 9.000.000 bytes, em comparação com apenas 8.000.001 se a precisão for armazenada uma vez para a coluna inteira.

Isso também fortalece o datetime2 do 's case ao compará-lo com datetime . Mesmo ao usar o mesmo número de casas decimais que datetime (ou seja, 3), o datetime2 tipo de dados usa menos armazenamento (pelo menos quando armazenado em uma tabela com mais de uma linha). E faz isso com maior precisão. Um datahora value usa 8 bytes, enquanto datetime2(3) usa 7 bytes (mais 1 byte de “precisão” que é compartilhado em todas as linhas).