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

Entendendo o tamanho do armazenamento 'datetimeoffset' no SQL Server


Neste artigo, vejo como o datetimeoffset tipo de dados é armazenado no SQL Server e como você pode obter diferentes resultados de tamanho de armazenamento relatados, dependendo do que você está fazendo com ele.

Isso é semelhante ao que fiz com o datetime2 tipo 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()


Documentação da Microsoft


Documentação oficial da Microsoft sobre o datetimeoffset tipo de dados indica que seu tamanho de armazenamento está entre 8 e 10 bytes, dependendo da precisão que está sendo usada.

Semelhante a datetime2(n) , você pode usar datetimeoffset(n) para especificar a precisão, onde n é uma escala entre 0 e 7.

Aqui estão os dados que a Microsoft apresenta para esse tipo de dados:
Escala especificada Resultado (precisão, escala) Comprimento da coluna (bytes) Precisão de segundos fracionários
deslocamento de data e hora (34,7) 10 7
datetimeoffset(0) (26,0) 8 0-2
datetimeoffset(1) (28,1) 8 0-2
datetimeoffset(2) (29,2) 8 0-2
datetimeoffset(3) (30,3) 9 3-4
datetimeoffset(4) (31,4) 9 3-4
datetimeoffset(5) (32,5) 10 5-7
datetimeoffset(6) (33,6) 10 5-7
datetimeoffset(7) (34,7) 10 5-7

Para os propósitos deste artigo, estou interessado principalmente no Tamanho da coluna (bytes) coluna. Isso nos diz quantos bytes são usados ​​para armazenar esse tipo de dados em um banco de dados.

A principal razão pela qual eu queria escrever este artigo (e executar os experimentos abaixo) é que a documentação da Microsoft não explica que um byte extra é usado para a precisão (como faz em sua documentação para o datetime2 tipo de dados). Em sua documentação para datetime2 , afirma:

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.

Mas a documentação para datetimeoffset não inclui este texto, nem o tempo documentação.

Isso me fez pensar se há uma diferença entre como esses tipos de dados armazenam seus valores. A lógica me disse que eles deveriam funcionar da mesma forma, pois todos eles têm uma precisão definida pelo usuário, mas eu queria descobrir.

A resposta curta é sim, datetimeoffset parece funcionar da mesma forma que datetime2 (no que diz respeito ao byte extra), mesmo que não esteja documentado como tal.

O restante do artigo passa por vários exemplos em que retorno o tamanho de armazenamento de datetimeoffset valores em diferentes contextos.

Dados armazenados em uma variável


Vamos armazenar um offset de data e hora 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 datetimeoffset(7) valor:
DECLARE @d datetimeoffset(7);SET @d ='2025-05-21 10:15:30.1234567 +07:00';SELECT @d AS 'Valor', DATALENGTH(@d) AS 'Comprimento em Bytes ';

Resultado
+------------------------------------+--------- ----------+| Valor | Comprimento em Bytes ||------------------------------------+------------ -----------|| 21-05-2025 10:15:30.1234567 +07:00 | 10 |+------------------------------------+---------- ---------+

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

Sem surpresas aqui, este é o tamanho exato de armazenamento que a documentação da Microsoft indica que deveria ser.

No entanto, se convertermos o valor para varbinary obtemos um resultado diferente.

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


Alguns desenvolvedores gostam de converter datetimeoffset e datetime2 variáveis ​​para varbinary , porque é mais representativo de como o SQL Server o armazena no banco de dados. Embora isso seja parcialmente verdade, os resultados não são exatamente os mesmos que o valor armazenado (como você verá mais adiante).

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

Resultado
+--------------------------+------------------- +| Valor | Comprimento em Bytes ||--------------------------+------------------ -|| 0x0787CBB24F1B3F480BA401 | 11 |+--------------------------+-------------------+ 

Neste caso, obtemos 11 bytes.

Esta é uma representação hexadecimal do datetimeoffset valor. O valor real do deslocamento de data e hora (e sua precisão) é tudo após o 0x . Cada par de caracteres hexadecimais é um byte. Existem 11 pares e, portanto, 11 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 datetimeoffset(3);SET @d ='2025-05-21 10:15:30.1234567 +07:00';SELECT CONVERT(VARBINARY(16), @d) AS 'Value', DATALENGTH( CONVERT(VARBINARY(16), @d)) AS 'Comprimento em Bytes';

Resultado
+------------------------+-------------------+| Valor | Comprimento em Bytes ||------------------------+---------------------------------| | 0x03CBFCB2003F480BA401 | 10 |+------------------------+-------------------+ 
Também podemos ver que o comprimento é reduzido em conformidade.

Dados armazenados em um banco de dados


Neste exemplo, crio um banco de dados com vários datetimeoffset(n) colunas e, em seguida, use COL_LENGTH() para retornar o comprimento de cada coluna, em bytes. Em seguida, insiro valores nas colunas, antes de usar DBCC PAGE para verificar o tamanho do armazenamento que cada datetimeoffset valor ocupa o arquivo de paginação.

Crie um banco de dados:
CRIAR BANCO DE DADOS;

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

Neste caso eu crio oito colunas – uma para cada escala definida pelo usuário que podemos usar com datetimeoffset(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 ( 'DatetimeoffsetTest' , 'd0' ) AS 'd0', COL_LENGTH ( 'DatetimeoffsetTest' , 'd1' ) AS 'd1', COL_LENGTH( 'DatetimeoffsetTest' , 'd2' ) AS 'd2', COL_LENGTH ( 'DatetimeoffsetTest' , 'd3' ) AS 'd3', COL_LENGTH ( 'DatetimeoffsetTest' , 'd4' ) AS 'd4', COL_LENGTH ( 'DatetimeoffsetTest' , 'd5' ) AS 'd5', COL_LENGTH ( 'DatetimeoffsetTest' , 'd6' ) AS 'd6', COL_LENGTH( 'DatetimeoffsetTest' , 'd7' ) AS 'd7'; 

Resultado:
+------+------+------+------+------+------+---- --+------+| d0 | d1 | d2 | d3 | d4 | d5 | d6 | d7 ||------+------+------+------+------+------+----- -+------|| 8 | 8 | 8 | 9 | 9 | 10 | 10 | 10 |+------+------+------+------+------+------+----- -+------+

Então, mais uma vez, obtemos o mesmo resultado que a documentação afirma que obteremos. Isso é esperado, porque a documentação declara explicitamente “comprimento da coluna (bytes)”, que é exatamente o que estamos medindo aqui.

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 datetimeoffset(7) ='2025-05-21 10:15:30.1234567 +07:00';INSERT INTO DatetimeoffsetTest ( 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 DatetimeoffsetTest;

Resultado (usando saída vertical):
d0 | 2025-05-21 10:15:30.0000000 +07:00d1 | 2025-05-21 10:15:30.1000000 +07:00d2 | 2025-05-21 10:15:30.1200000 +07:00d3 | 21-05-2025 10:15:30.1230000 +07:00d4 | 21-05-2025 10:15:30.1235000 +07:00d5 | 21-05-2025 10:15:30.1234600 +07:00d6 | 21-05-2025 10:15:30.1234570 +07:00d7 | 21-05-2025 10:15:30.1234567 +07:00

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

Observe que meu sistema exibe zeros à direita. O seu pode ou não fazê-lo. Independentemente disso, isso não afeta a precisão ou exatidão real.

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.DatetimeoffsetTest', 0);

Resultado (usando saída vertical):
-[ RECORD 1 ]-------------------------PageFID | 1PáginaPID | 307IAMFID | NULLIAMPID | NULLObjectID | 1525580473IndexID | 0PartitionNumber | 1PartitionID | 72057594043170816iam_chain_type | DataPageType em linha | 10Nível de Índice | NULLNextPageFID | 0PróximaPáginaPID | 0PrevPageFID | 0PrevPagePID | 0-[ RECORD 2 ] -------------------------PageFID | 1PáginaPID | 376IAMFID | 1IAMPID | 307ID do objeto | 1525580473IndexID | 0PartitionNumber | 1PartitionID | 72057594043170816iam_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 é 376 .

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

No momento, estamos interessados ​​principalmente na seguinte parte:
Slot 0 Coluna 1 Offset 0x4 Comprimento 8 Comprimento (físico) 8d0 =2025-05-21 10:15:30 +07:00 Slot 0 Coluna 2 Offset 0xc Comprimento 8 Comprimento (físico) 8d1 =2025-05-21 10:15:30.1 +07:00 Slot 0 Coluna 3 Offset 0x14 Comprimento 8 Comprimento (físico) 8d2 =2025-05-21 10:15:30.12 +07:00 Slot 0 Coluna 4 Offset 0x1c Comprimento 9 Comprimento (físico) 9d3 =2025-05-21 10:15:30.123 +07:00 Slot 0 Coluna 5 Offset 0x25 Comprimento 9 Comprimento (físico) 9d4 =2025-05-21 10:15:30.1235 +07:00Slot 0 Coluna 6 Offset 0x2e Comprimento 10 Comprimento (físico) 10d5 =21-05-2025 10:15:30.12346 +07:00 Slot 0 Coluna 7 Offset 0x38 Comprimento 10 Comprimento (físico) 10d6 =2025-05-21 10:15:30.123457 +07:00 Slot 0 Coluna 8 Deslocamento 0x42 Comprimento 10 Comprimento (físico) 10d7 =2025-05-21 10:15:30.1234567 +07:00 

Assim, obtemos o mesmo resultado novamente. Exatamente como consta na documentação.

Enquanto estamos aqui, vamos examinar os dados – os valores reais de data/hora conforme são armazenados no SQL Server.

Os valores reais são armazenados nesta parte do arquivo de paginação:
 Memory dump @0x000000041951a0600000000000000000:10004C00 D22D003F 480BA401 35CA013F 480BA401 ..L.C. ò. H.5FFAFAFB. H. ¤.óßý0000000000000028:063F480B A4017ABF EA45003F 480BA401 C17A2BB. ..

Isso ainda inclui alguns bits extras. Vamos excluir algumas coisas, para que apenas nossos valores de data e hora permaneçam:
d22d003f 480ba401 35ca013f 480ba40114e6113f 480ba401 cbfcb200 3f480ba4 01f3dffd063f480b a4017abf ea45003f 480ba401 c17a2bbb023f480b a40187 
Os dígitos hexadecimais restantes contêm todos os nossos dados de data e hora, mas não a precisão . No entanto, eles são organizados em pedaços de 4 bytes, portanto, devemos reorganizar os espaços para obter os valores individuais.

Aqui está o resultado final. Coloquei cada valor de data/hora em uma nova linha para melhor legibilidade.
d22d003f480ba401 35ca013f480ba40114e6113f480ba401 cbfcb2003f480ba401f3dffd063f480ba4017abfea45003f480ba401 c17a2bbb023f480ba40187cbb24f1b3fpre 
Esses são os valores hexadecimais reais (menos a precisão ) que obteríamos se convertêssemos o datetimeoffset valor para varbinary . Assim:
SELECT CONVERT(VARBINARY(16), d0) AS 'd0', CONVERT(VARBINARY(16), d1) AS 'd1', CONVERT(VARBINARY(16), d2) AS 'd2', CONVERT(VARBINARY( 16), d3) COMO 'd3', CONVERT(VARBINARY(16), d4) COMO 'd4', CONVERT(VARBINARY(16), d5) COMO 'd5', CONVERT(VARBINARY(16), d6) COMO 'd6 ', CONVERT(VARBINARY(16), d7) AS 'd7'FROM DatetimeoffsetTest;

Resultado (usando saída vertical):
d0 | 0x00D22D003F480BA401d1 | 0x0135CA013F480BA401d2 | 0x0214E6113F480BA401d3 | 0x03CBFCB2003F480BA401d4 | 0x04F3DFFD063F480BA401d5 | 0x057ABFEA45003F480BA401d6 | 0x06C17A2BBB023F480BA401d7 | 0x0787CBB24F1B3F480BA401

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

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
d22d003f480ba401 00D22D003F480BA401
35ca013f480ba401 0135CA013F480BA401
14e6113f480ba401 0214E6113F480BA401
cbfcb2003f480ba401 03CBFCB2003F480BA401
f3dffd063f480ba401 04F3DFFD063F480BA401
7abfea45003f480ba401 057ABFEA45003F480BA401
c17a2bbb023f480ba401 06C17A2BBB023F480BA401
87cbb24f1b3f480ba401 0787CBB24F1B3F480BA401

Assim, podemos ver 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 offset de data e hora 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 adicionar a precisão a cada linha quando todas as linhas usam a mesma precisão de qualquer maneira. Isso exigiria um byte extra para cada linha, o que aumentaria desnecessariamente os requisitos de armazenamento.