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

Entendendo o tamanho do armazenamento de 'tempo' no SQL Server


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
  • 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 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..•¢.ÔY8K‚3.ó..
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.