Neste artigo, analiso o tamanho do armazenamento do tempo tipo de dados no SQL Server.
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
Documentação da Microsoft
Documentação oficial da Microsoft sobre o horário tipo de dados indica que seu tamanho de armazenamento está entre 3 e 5 bytes, dependendo da precisão que está sendo usada.
Esse tipo de dados permite precisão definida pelo usuário. Você pode usar tempo(n) para especificar a precisão, onde n é uma escala entre 0 e 7.
Aqui estão os dados que a Microsoft apresenta para o tempo tipo de dados:
Escala especificada | Resultado (precisão, escala) | Comprimento da coluna (bytes) | Precisão de segundos fracionários |
---|---|---|---|
hora | (16,7) | 5 | 7 |
hora(0) | (8,0) | 3 | 0-2 |
hora(1) | (10,1) | 3 | 0-2 |
hora(2) | (11,2) | 3 | 0-2 |
hora(3) | (12,3) | 4 | 3-4 |
hora(4) | (13,4) | 4 | 3-4 |
hora(5) | (14,5) | 5 | 5-7 |
hora(6) | (15,6) | 5 | 5-7 |
hora(7) | (16,7) | 5 | 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.
Do ponto de vista do usuário, o tempo tipo de dados funciona da mesma forma que a parte de hora do datetime2 . Ele tem uma precisão de segundos fracionários definida pelo usuário e aceita uma escala de 0 a 7.
O restante deste artigo apresenta vários exemplos em que retorno o tamanho de armazenamento de tempo valores em diferentes contextos.
Dados armazenados em uma variável
Primeiro, armazenarei um tempo 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 por um time(7) valor:DECLARE @t time(7); SET @t = '10:15:30.1234567'; SELECT @t AS 'Value', DATALENGTH(@t) AS 'Length in Bytes';
Resultado
+------------------+-------------------+ | Value | Length in Bytes | |------------------+-------------------| | 10:15:30.1234567 | 5 | +------------------+-------------------+
O valor neste exemplo tem a escala máxima de 7 (porque eu declaro a variável como time(7) ), e retorna um comprimento de 5 bytes.
Isso é esperado, pois corresponde ao tamanho de armazenamento descrito na tabela da Microsoft.
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 tempo ou datahora2 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 (mais sobre isso abaixo).
Veja o que acontece se convertermos nosso tempo valor para varbinary :
DECLARE @t time(7); SET @t = '10:15:30.1234567'; SELECT CONVERT(VARBINARY(16), @t) AS 'Value', DATALENGTH(CONVERT(VARBINARY(16), @t)) AS 'Length in Bytes';
Resultado
+----------------+-------------------+ | Value | Length in Bytes | |----------------+-------------------| | 0x0787A311FC55 | 6 | +----------------+-------------------+
Neste caso, obtemos 6 bytes. Nosso valor agora usa 1 byte a mais do que o declarado na documentação.
Isso porque ele precisa de um byte extra para armazenar a precisão.
Esta é uma representação hexadecimal da hora valor. O valor de tempo real (e sua precisão) é tudo após o
0x
. Cada par de caracteres hexadecimais é um byte. Existem 6 pares e, portanto, 6 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 @t time(3); SET @t = '10:15:30.1234567'; SELECT CONVERT(VARBINARY(16), @t) AS 'Value', DATALENGTH(CONVERT(VARBINARY(16), @t)) AS 'Length in Bytes';
Resultado
+--------------+-------------------+ | Value | Length in Bytes | |--------------+-------------------| | 0x034B823302 | 5 | +--------------+-------------------+
Também podemos ver que o comprimento é reduzido em conformidade. Mas, novamente, é um byte a mais do que a documentação diz que deveria usar.
Embora a documentação da Microsoft por tempo não menciona isso explicitamente, a documentação para datetime2 afirma o seguinte:
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.
E o datetime2 tipo de dados funciona exatamente da mesma maneira em relação aos exemplos acima. Em outras palavras, ele só informa o byte extra quando é convertido em varbinary .
Portanto, o byte extra mencionado na documentação da Microsoft também parece se aplicar ao tempo .
No entanto, o tamanho real de armazenamento do seu tempo os valores serão de onde os dados são armazenados.
Dados armazenados em um banco de dados
Quando uma coluna de banco de dados tem um tipo de hora , sua precisão é especificada no nível da coluna – não no nível de dados. Em outras palavras, é especificado uma vez para a coluna inteira. Isso faz sentido, porque quando você define uma coluna como time(7) , você sabe que todas as linhas serão time(7) . Não há necessidade de usar bytes preciosos reafirmando esse fato em cada linha.
Quando você examina um tempo valor como está armazenado no SQL Server, você verá que é o mesmo que o varbinary resultado, mas sem a precisão.
Abaixo estão exemplos que mostram como tempo os valores são armazenados no SQL Server.
Nestes exemplos, crio um banco de dados com vários time(n) colunas e, em seguida, use
COL_LENGTH()
para retornar o comprimento de cada coluna, em bytes. Em seguida, insiro valores nessas colunas, antes de usar DBCC PAGE
para verificar o tamanho do armazenamento que cada vez valor ocupa o arquivo de paginação. Crie um banco de dados:
CREATE DATABASE Test;
Crie uma tabela:
USE Test; CREATE TABLE TimeTest ( t0 time(0), t1 time(1), t2 time(2), t3 time(3), t4 time(4), t5 time(5), t6 time(6), t7 time(7) );
Neste caso eu crio oito colunas – uma para cada escala definida pelo usuário que podemos usar com time(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 ( 'TimeTest' , 't0' ) AS 't0', COL_LENGTH ( 'TimeTest' , 't1' ) AS 't1', COL_LENGTH ( 'TimeTest' , 't2' ) AS 't2', COL_LENGTH ( 'TimeTest' , 't3' ) AS 't3', COL_LENGTH ( 'TimeTest' , 't4' ) AS 't4', COL_LENGTH ( 'TimeTest' , 't5' ) AS 't5', COL_LENGTH ( 'TimeTest' , 't6' ) AS 't6', COL_LENGTH ( 'TimeTest' , 't7' ) AS 't7';
Resultado:
+------+------+------+------+------+------+------+------+ | t0 | t1 | t2 | t3 | t4 | t5 | t6 | t7 | |------+------+------+------+------+------+------+------| | 3 | 3 | 3 | 4 | 4 | 5 | 5 | 5 | +------+------+------+------+------+------+------+------+
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.
Lembre-se, isso é antes inserimos quaisquer dados. As próprias colunas determinam a precisão (e, portanto, o tamanho do armazenamento) de qualquer dado inserido – e não o contrário.
Use DBCC PAGE para verificar os dados armazenados
Agora vamos inserir dados, então use
DBCC PAGE
para encontrar o tamanho real de armazenamento dos dados que armazenamos em cada coluna. Inserir dados:
DECLARE @t time(7) = '10:15:30.1234567'; INSERT INTO TimeTest ( t0, t1, t2, t3, t4, t5, t6, t7 ) SELECT @t, @t, @t, @t, @t, @t, @t, @t;
Agora selecione os dados (só para conferir):
SELECT * FROM TimeTest;
Resultado (usando saída vertical):
t0 | 10:15:30 t1 | 10:15:30.1000000 t2 | 10:15:30.1200000 t3 | 10:15:30.1230000 t4 | 10:15:30.1235000 t5 | 10:15:30.1234600 t6 | 10:15:30.1234570 t7 | 10:15:30.1234567
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('Test', 'dbo.TimeTest', 0);
Resultado (usando saída vertical):
-[ RECORD 1 ]------------------------- PageFID | 1 PagePID | 308 IAMFID | NULL IAMPID | NULL ObjectID | 1541580530 IndexID | 0 PartitionNumber | 1 PartitionID | 72057594043236352 iam_chain_type | In-row data PageType | 10 IndexLevel | NULL NextPageFID | 0 NextPagePID | 0 PrevPageFID | 0 PrevPagePID | 0 -[ RECORD 2 ]------------------------- PageFID | 1 PagePID | 384 IAMFID | 1 IAMPID | 308 ObjectID | 1541580530 IndexID | 0 PartitionNumber | 1 PartitionID | 72057594043236352 iam_chain_type | In-row data PageType | 1 IndexLevel | 0 NextPageFID | 0 NextPagePID | 0 PrevPageFID | 0 PrevPagePID | 0
Isso retorna dois registros. Estamos interessados no PageType de 1 (o segundo registro). Queremos o PagePID desse registro. Neste caso, o PagePID é 384 .
Agora podemos pegar esse PagePID e usá-lo da seguinte forma:
DBCC TRACEON(3604, -1); DBCC PAGE(Test, 1, 384, 3);
No momento, estamos interessados principalmente na seguinte parte:
Slot 0 Column 1 Offset 0x4 Length 3 Length (physical) 3 t0 = 10:15:30 Slot 0 Column 2 Offset 0x7 Length 3 Length (physical) 3 t1 = 10:15:30.1 Slot 0 Column 3 Offset 0xa Length 3 Length (physical) 3 t2 = 10:15:30.12 Slot 0 Column 4 Offset 0xd Length 4 Length (physical) 4 t3 = 10:15:30.123 Slot 0 Column 5 Offset 0x11 Length 4 Length (physical) 4 t4 = 10:15:30.1235 Slot 0 Column 6 Offset 0x15 Length 5 Length (physical) 5 t5 = 10:15:30.12346 Slot 0 Column 7 Offset 0x1a Length 5 Length (physical) 5 t6 = 10:15:30.123457 Slot 0 Column 8 Offset 0x1f Length 5 Length (physical) 5 t7 = 10:15:30.1234567
Assim, obtemos o mesmo resultado que a documentação afirma. Isso sugeriria que a precisão não está sendo armazenada com os valores.
Podemos confirmar isso examinando os dados reais.
Os valores de tempo real são armazenados nesta parte do arquivo de paginação:
Memory Dump @0x0000000423ADA060 0000000000000000: 10002400 42900095 a205d459 384b8233 02f31603 ..$.B..¢.ÔY8K3.ó.. 0000000000000014: 167ae51e dc00c1f6 34990887 a311fc55 080000 .zå.Ü.Áö4..£.üU...
Podemos extrair os valores de tempo reais removendo algumas coisas. Uma vez removido, o seguinte permanecerá:
42900095 a205d459 384b8233 02f31603 167ae51e dc00c1f6 34990887 a311fc55
Esses dígitos hexadecimais contêm todos os nossos dados de tempo, 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.
429000 95a205 d45938 4b823302 f3160316 7ae51edc00 c1f6349908 87a311fc55
Esses são os valores hexadecimais reais (menos a precisão ) que obteríamos se convertêssemos o tempo valor para varbinary . Assim:
SELECT CONVERT(VARBINARY(16), t0) AS 't0', CONVERT(VARBINARY(16), t1) AS 't1', CONVERT(VARBINARY(16), t2) AS 't2', CONVERT(VARBINARY(16), t3) AS 't3', CONVERT(VARBINARY(16), t4) AS 't4', CONVERT(VARBINARY(16), t5) AS 't5', CONVERT(VARBINARY(16), t6) AS 't6', CONVERT(VARBINARY(16), t7) AS 't7' FROM TimeTest;
Resultado (usando saída vertical):
t0 | 0x00429000 t1 | 0x0195A205 t2 | 0x02D45938 t3 | 0x034B823302 t4 | 0x04F3160316 t5 | 0x057AE51EDC00 t6 | 0x06C1F6349908 t7 | 0x0787A311FC55
Essa consulta produz o mesmo resultado – exceto que cada valor 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 |
---|---|
429000 | 00429000 |
95a205 | 0195A205 |
d45938 | 02D45938 |
4b823302 | 034B823302 |
f3160316 | 04F3160316 |
7ae51edc00 | 057AE51EDC00 |
c1f6349908 | 06C1F6349908 |
87a311fc55 | 0787A311FC55 |
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 tempo 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 têm a mesma precisão. Isso exigiria um byte extra para cada linha, o que aumentaria desnecessariamente os requisitos de armazenamento.