Sqlserver
 sql >> Database >  >> RDS >> Sqlserver

Comprensione delle dimensioni di archiviazione "time" in SQL Server

In questo articolo esamino le dimensioni dello spazio di archiviazione del tempo tipo di dati in SQL Server.

In particolare, guardo quanto segue:

  • Documentazione di Microsoft
  • Dati memorizzati in una variabile
    • Lunghezza in byte utilizzando DATALENGTH()
    • Lunghezza in byte utilizzando DATALENGTH() dopo la conversione in varbinary
  • Dati archiviati in un database
    • Lunghezza in byte utilizzando COL_LENGTH()
    • Lunghezza in byte utilizzando DBCC PAGE()

Documentazione Microsoft

La documentazione ufficiale di Microsoft sull'ora tipo di dati indica che la sua dimensione di archiviazione è compresa tra 3 e 5 byte, a seconda della precisione utilizzata.

Questo tipo di dati consente la precisione definita dall'utente. Puoi utilizzare tempo(n) per specificare la precisione, dove n è una scala compresa tra 0 e 7.

Ecco i dati che Microsoft presenta per il tempo tipo di dati:

Scala specificata Risultato (precisione, scala) Lunghezza della colonna (byte) Precisione frazionaria di secondi
tempo (16,7) 5 7
ora(0) (8,0) 3 0-2
tempo(1) (10,1) 3 0-2
tempo(2) (11,2) 3 0-2
tempo(3) (12,3) 4 3-4
tempo(4) (13,4) 4 3-4
tempo(5) (14,5) 5 5-7
tempo(6) (15,6) 5 5-7
tempo(7) (16,7) 5 5-7

Ai fini di questo articolo, sono principalmente interessato alla Lunghezza della colonna (byte) colonna. Questo ci dice quanti byte vengono utilizzati per memorizzare questo tipo di dati in un database.

Dal punto di vista dell'utente, il tempo il tipo di dati funziona allo stesso modo della parte temporale di datetime2 . Ha una precisione in secondi frazionari definita dall'utente e accetta una scala da 0 a 7.

Il resto di questo articolo illustra vari esempi in cui restituisco la dimensione di archiviazione di tempo valori in diversi contesti.

Dati archiviati in una variabile

Innanzitutto, conserverò un tempo valore in una variabile e verificarne la dimensione di archiviazione. Quindi convertirò quel valore in varbinary e controllalo di nuovo.

Lunghezza in byte utilizzando DATALENGTH

Ecco cosa succede se utilizziamo DATALENGTH() funzione per restituire il numero di byte utilizzati per un time(7) valore:

DECLARE @t time(7);
SET @t = '10:15:30.1234567';
SELECT 
  @t AS 'Value',
  DATALENGTH(@t) AS 'Length in Bytes';

Risultato

+------------------+-------------------+
| Value            | Length in Bytes   |
|------------------+-------------------|
| 10:15:30.1234567 | 5                 |
+------------------+-------------------+

Il valore in questo esempio ha la scala massima di 7 (perché dichiaro la variabile come time(7) ), e restituisce una lunghezza di 5 byte.

Questo è prevedibile, poiché corrisponde alla dimensione di archiviazione delineata nella tabella di Microsoft.

Tuttavia, se convertiamo il valore in varbinary otteniamo un risultato diverso.

Lunghezza in byte dopo la conversione in 'varbinary'

Ad alcuni sviluppatori piace convertire tempo o dataora2 variabili in variabile perché è più rappresentativo del modo in cui SQL Server lo archivia nel database. Sebbene ciò sia parzialmente vero, i risultati non sono esattamente gli stessi del valore memorizzato (ne parleremo più avanti).

Ecco cosa succede se convertiamo il nostro tempo valore a variabile :

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';

Risultato

+----------------+-------------------+
| Value          | Length in Bytes   |
|----------------+-------------------|
| 0x0787A311FC55 | 6                 |
+----------------+-------------------+

In questo caso otteniamo 6 byte. Il nostro valore ora utilizza 1 byte in più rispetto a quanto indicato nella documentazione.

Questo perché ha bisogno di un byte in più per memorizzare la precisione.

Questa è una rappresentazione esadecimale del tempo valore. Il valore del tempo effettivo (e la sua precisione) è tutto dopo 0x . Ogni coppia di caratteri esadecimali è un byte. Ci sono 6 coppie, e quindi 6 byte. Ciò è confermato quando utilizziamo DATALENGTH() per restituire la lunghezza in byte.

In questo esempio possiamo vedere che il primo byte è 07 . Questo rappresenta la precisione (ho usato una scala di 7 e quindi questo è ciò che viene visualizzato qui).

Se cambio la scala, possiamo vedere che il primo byte cambia per corrispondere alla scala:

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';

Risultato

+--------------+-------------------+
| Value        | Length in Bytes   |
|--------------+-------------------|
| 0x034B823302 | 5                 |
+--------------+-------------------+

Possiamo anche vedere che la lunghezza si riduce di conseguenza. Ma ancora una volta, è un byte in più rispetto a quello che la documentazione dice che dovrebbe usare.

Sebbene la documentazione di Microsoft per tempo non lo menziona esplicitamente, la documentazione per datetime2 afferma quanto segue:

Il primo byte di un datetime2 value memorizza la precisione del valore, il che significa lo spazio di archiviazione effettivo richiesto per un datetime2 value è la dimensione di archiviazione indicata nella tabella sopra più 1 byte aggiuntivo per memorizzare la precisione. Ciò rende la dimensione massima di un datetime2 valore 9 byte – 1 byte memorizza la precisione più 8 byte per la memorizzazione dei dati alla massima precisione.

E il datetime2 il tipo di dati funziona esattamente allo stesso modo rispetto agli esempi precedenti. In altre parole, segnala il byte aggiuntivo solo quando viene convertito in varbinary .

Quindi il byte aggiuntivo menzionato nella documentazione Microsoft sembra applicarsi anche al tempo .

Tuttavia, le dimensioni effettive di archiviazione del tuo tempo valori saranno su dove sono archiviati i dati.

Dati archiviati in un database

Quando una colonna del database ha un tipo di ora , la sua precisione è specificata a livello di colonna, non a livello di dati. In altre parole, viene specificato una volta per l'intera colonna. Questo ha senso, perché quando definisci una colonna come time(7) , sai che tutte le righe saranno time(7) . Non c'è bisogno di consumare byte preziosi riaffermando questo fatto su ogni riga.

Quando esamini un tempo valore poiché è archiviato in SQL Server, vedrai che è lo stesso del varbinary risultato, ma senza la precisione.

Di seguito sono riportati esempi che mostrano come tempo i valori sono archiviati in SQL Server.

In questi esempi creo un database con vari time(n) colonne, quindi usa COL_LENGTH() per restituire la lunghezza di ciascuna colonna, in byte. Quindi inserisco i valori in quelle colonne, prima di utilizzare DBCC PAGE per controllare la dimensione dello spazio di archiviazione che ogni volta il valore occupa il file di paging.

Crea un database:

CREATE DATABASE Test;

Crea una tabella:

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)
    );

In questo caso creo otto colonne, una per ogni scala definita dall'utente che possiamo utilizzare con time(n) .

Ora possiamo controllare la dimensione di archiviazione di ciascuna colonna.

Lunghezza in byte utilizzando COL_LENGTH()

Usa COL_LENGTH() per controllare la lunghezza (in byte) di ogni colonna:

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';  

Risultato:

+------+------+------+------+------+------+------+------+
| t0   | t1   | t2   | t3   | t4   | t5   | t6   | t7   |
|------+------+------+------+------+------+------+------|
| 3    | 3    | 3    | 4    | 4    | 5    | 5    | 5    |
+------+------+------+------+------+------+------+------+

Quindi, ancora una volta, otteniamo lo stesso risultato che la documentazione afferma che otterremo. Questo è prevedibile, perché la documentazione afferma esplicitamente "Lunghezza colonna (byte)", che è esattamente ciò che stiamo misurando qui.

Ricorda, questo è prima inseriamo qualsiasi dato. Le colonne stesse determinano la precisione (e quindi la dimensione di archiviazione) di tutti i dati inseriti, non il contrario.

Utilizza DBCC PAGE per controllare i dati archiviati

Ora inseriamo i dati, quindi utilizziamo DBCC PAGE per trovare la dimensione di archiviazione effettiva dei dati che memorizziamo in ciascuna colonna.

Inserisci dati:

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;

Ora seleziona i dati (solo per verificarli):

SELECT * FROM TimeTest;

Risultato (usando l'output verticale):

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

Come previsto, i valori utilizzano la precisione precedentemente specificata a livello di colonna.

Nota che il mio sistema mostra zeri finali. Il tuo può o non può farlo. Indipendentemente da ciò, ciò non influisce sulla precisione o accuratezza effettiva.

Ora, prima di usare DBCC PAGE() , abbiamo bisogno di sapere quale PagePID passargli. Possiamo usare DBCC IND() per trovarlo.

Trova il PagePID:

DBCC IND('Test', 'dbo.TimeTest', 0);

Risultato (usando l'output verticale):

-[ 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

Questo restituisce due record. Siamo interessati al PageType di 1 (il 2° record). Vogliamo il PagePID da quel record. In questo caso il PagePID è 384 .

Ora possiamo prendere quel PagePID e usarlo nel modo seguente:

DBCC TRACEON(3604, -1);
DBCC PAGE(Test, 1, 384, 3);

In questo momento siamo principalmente interessati alla parte seguente:

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                                                                      

Quindi otteniamo lo stesso risultato degli stati della documentazione. Ciò suggerirebbe che la precisione non viene memorizzata con i valori.

Possiamo confermarlo esaminando i dati effettivi.

I valori di tempo effettivi sono memorizzati in questa parte del file di paging:

Memory Dump @0x0000000423ADA060

0000000000000000:   10002400 42900095 a205d459 384b8233 02f31603  ..$.B..•¢.ÔY8K‚3.ó..
0000000000000014:   167ae51e dc00c1f6 34990887 a311fc55 080000    .zå.Ü.Áö4..‡£.üU...

Possiamo estrarre i valori temporali effettivi rimuovendo alcune cose. Una volta rimossi, rimarranno:

42900095 a205d459 384b8233 02f31603
167ae51e dc00c1f6 34990887 a311fc55

Queste cifre esadecimali contengono tutti i nostri dati temporali, ma ​​non la precisione . Tuttavia, sono organizzati in blocchi di 4 byte, quindi dobbiamo riorganizzare gli spazi per ottenere i singoli valori.

Ecco il risultato finale. Ho inserito ogni valore di data/ora su una nuova riga per una migliore leggibilità.

429000
95a205
d45938
4b823302
f3160316
7ae51edc00
c1f6349908
87a311fc55

Questi sono i valori esadecimali effettivi (meno la precisione ) che otterremmo se convertissimo il tempo valore a variabile . In questo modo:

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;

Risultato (usando l'output verticale):

t0 | 0x00429000
t1 | 0x0195A205
t2 | 0x02D45938
t3 | 0x034B823302
t4 | 0x04F3160316
t5 | 0x057AE51EDC00
t6 | 0x06C1F6349908
t7 | 0x0787A311FC55

Quella query produce lo stesso risultato, tranne per il fatto che ogni valore è stato anteposto con la precisione.

Ecco una tabella che confronta i dati effettivi del file di paging con i risultati di CONVERT() operazione.

Dati del file di pagina Dati CONVERT()
429000 00429000
95a205 0195A205
d45938 02D45938
4b823302 034B823302
f3160316 04F3160316
7ae51edc00 057AE51EDC00
c1f6349908 06C1F6349908
87a311fc55 0787A311FC55

Quindi possiamo vedere che il file di paging non memorizza la precisione, ma il risultato convertito lo fa.

Ho evidenziato la data e l'ora effettive in rosso. Ho anche rimosso 0x prefisso dai risultati convertiti, in modo che vengano visualizzati solo i dati di data/ora effettivi (insieme alla precisione).

Tieni inoltre presente che l'esadecimale non fa distinzione tra maiuscole e minuscole, quindi il fatto che uno utilizzi le minuscole e l'altro le maiuscole non è un problema.

Conclusione

Quando si converte un tempo valore a variabile , ha bisogno di un byte aggiuntivo per memorizzare la precisione. Ha bisogno della precisione per interpretare la porzione di tempo (perché questa è memorizzata come un intervallo di tempo, il cui valore esatto dipenderà dalla precisione).

Quando viene archiviata in un database, la precisione viene specificata una volta a livello di colonna. Sembra logico, poiché non è necessario aggiungere la precisione a ciascuna riga quando tutte le righe hanno comunque la stessa precisione. Ciò richiederebbe un byte aggiuntivo per ogni riga, il che aumenterebbe inutilmente i requisiti di archiviazione.