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
- Comprimento em bytes usando
- Dados armazenados em um banco de dados
- Comprimento em bytes usando
COL_LENGTH()
- Comprimento em bytes usando
DBCC PAGE()
- Comprimento em bytes usando
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 obter8 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 o0x
. Cada par de caracteres hexadecimais é um byte. Existem 9 pares e, portanto, 9 bytes. Isso é confirmado quando usamosDATALENGTH()
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 usoCOL_LENGTH()
para retornar o comprimento de cada coluna, em bytes. Depois disso, insiro valores nele, antes de usarDBCC 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()
UseCOL_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 usarDBCC 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 usarmosDBCC PAGE()
, precisamos saber qual PagePID passar para ele. Podemos usarDBCC 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 doCONVERT()
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).