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

Comprensione delle dimensioni di archiviazione "datetimeoffset" in SQL Server

In questo articolo guardo come il datetimeoffset il tipo di dati è archiviato in SQL Server e come ottenere risultati di dimensioni di archiviazione segnalati diversi, a seconda di ciò che stai facendo con esso.

È simile a quello che ho fatto con datetime2 tipo di dati.

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 su datetimeoffset tipo di dati indica che la sua dimensione di archiviazione è compresa tra 8 e 10 byte, a seconda della precisione utilizzata.

Simile a datetime2(n) , puoi utilizzare datetimeoffset(n) per specificare la precisione, dove n è una scala compresa tra 0 e 7.

Ecco i dati che Microsoft presenta per questo tipo di dati:

Scala specificata Risultato (precisione, scala) Lunghezza della colonna (byte) Precisione frazionaria di secondi
datetimeoffset (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

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.

Il motivo principale per cui volevo scrivere questo articolo (ed eseguire gli esperimenti di seguito) è che la documentazione Microsoft non spiega che viene utilizzato un byte in più per la precisione (come fa nella sua documentazione per datetime2 tipo di dati). Nella sua documentazione per datetime2 , afferma:

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.

Ma la documentazione per datetimeoffset non include questo testo e nemmeno il tempo documentazione.

Questo mi ha fatto chiedere se c'è una differenza nel modo in cui questi tipi di dati memorizzano i loro valori. La logica mi ha detto che dovrebbero funzionare allo stesso modo, poiché hanno tutti una precisione definita dall'utente, ma volevo scoprirlo.

La risposta breve è sì, datetimeoffset sembra funzionare come datetime2 (per quanto riguarda il byte extra), anche se non è documentato come tale.

Il resto dell'articolo illustra vari esempi in cui restituisco la dimensione di archiviazione di datetimeoffset valori in diversi contesti.

Dati archiviati in una variabile

Archiviamo un datetimeoffset 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 datetimeoffset(7) valore:

DECLARE @d datetimeoffset(7);SET @d ='2025-05-21 10:15:30.1234567 +07:00';SELECT @d COME 'Valore', DATALENGTH(@d) COME 'Lunghezza in byte ';

Risultato

+------------------------------------+---------- ----------+| Valore | Lunghezza in byte ||-----------------------+--------- -----------|| 2025-05-21 10:15:30.1234567 +07:00 | 10 |+-------------------------------------+---------- ---------+

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

Nessuna sorpresa qui, questa è la dimensione di archiviazione esatta che la documentazione Microsoft indica che dovrebbe essere.

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

Lunghezza in byte dopo la conversione in 'varbinary'

Ad alcuni sviluppatori piace convertire datetimeoffset e 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 (come vedrai più avanti).

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

DICHIARA @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)) COME 'Lunghezza in byte';

Risultato

+--------------------------+-------------------- +| Valore | Lunghezza in byte ||--------------------------+------------------ -|| 0x0787CBB24F1B3F480BA401 | 11 |+--------------------------+-------------------+ 

In questo caso otteniamo 11 byte.

Questa è una rappresentazione esadecimale di datetimeoffset valore. L'effettivo valore di offset della data e dell'ora (e la sua precisione) è tutto dopo il 0x . Ogni coppia di caratteri esadecimali è un byte. Ci sono 11 coppie, e quindi 11 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:

DICHIARA @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)) COME 'Lunghezza in byte';

Risultato

+------------------------+--------------------+| Valore | Lunghezza in byte ||-----------+-------------------| | 0x03CBFCB2003F480BA401 | 10 |+-------------------------+--------------------+

Possiamo anche vedere che la lunghezza si riduce di conseguenza.

Dati archiviati in un database

In questo esempio, creo un database con vari datetimeoffset(n) colonne, quindi usa COL_LENGTH() per restituire la lunghezza di ciascuna colonna, in byte. Quindi inserisco i valori nelle colonne, prima di utilizzare DBCC PAGE per controllare la dimensione di archiviazione di ogni datetimeoffset il valore occupa il file di paging.

Crea un database:

Test CREATE DATABASE;

Crea una tabella:

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

In questo caso creo otto colonne, una per ogni scala definita dall'utente che possiamo utilizzare con datetimeoffset(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 ( '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'; 

Risultato:

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

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

Utilizza DBCC PAGE per controllare i dati archiviati

Ora usiamo DBCC PAGE per trovare la dimensione di archiviazione effettiva dei dati che memorizziamo in questa tabella.

Per prima cosa, inseriamo alcuni dati:

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;

Ora seleziona i dati (solo per verificarli):

SELECT * FROM DatetimeoffsetTest;

Risultato (usando l'output verticale):

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 | 2025-05-21 10:15:30.1230000 +07:00d4 | 2025-05-21 10:15:30.1235000 +07:00d5 | 2025-05-21 10:15:30.1234600 +07:00d6 | 2025-05-21 10:15:30.1234570 +07:00d7 | 2025-05-21 10:15:30.1234567 +07:00

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

Risultato (usando l'output verticale):

-[ RECORD 1 ]-------------------------PageFID | 1PaginaPID | 307IAMFID | NULLIAMPID | NULLIDOggetto | 1525580473IDIndice | 0Numerodipartizione | 1IDpartizione | 72057594043170816iam_chain_type | DataPageType nella riga | 10LivelloIndice | NULLPagina successivaFID | 0Pagina successivaPID | 0PaginaprecedenteFID | 0PrevPaginaPID | 0-[ RECORD 2 ]-------------------------PageFID | 1PaginaPID | 376IAMFID | 1IAMPID | 307IDOggetto | 1525580473IDIndice | 0Numerodipartizione | 1IDpartizione | 72057594043170816iam_chain_type | DataPageType nella riga | 1LivelloIndice | 0Pagina successivaFID | 0Pagina successivaPID | 0PaginaprecedenteFID | 0PrevPaginaPID | 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 è 376 .

Ora possiamo prendere quel PagePID e usarlo nel modo seguente:

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

In questo momento siamo principalmente interessati alla parte seguente:

Slot 0 Colonna 1 Offset 0x4 Lunghezza 8 Lunghezza (fisico) 8d0 =2025-05-21 10:15:30 +07:00 Slot 0 Colonna 2 Offset 0xc Lunghezza 8 Lunghezza (fisico) 8d1 =2025-05-21 10:15:30.1 +07:00 Slot 0 Colonna 3 Offset 0x14 Lunghezza 8 Lunghezza (fisico) 8d2 =2025-05-21 10:15:30.12 +07:00 Slot 0 Colonna 4 Offset 0x1c Lunghezza 9 Lunghezza (fisico) 9d3 =2025-05-21 10:15:30.123 +07:00 Slot 0 Colonna 5 Offset 0x25 Lunghezza 9 Lunghezza (fisico) 9d4 =2025-05-21 10:15:30.1235 +07:00Slot 0 Colonna 6 Offset 0x2e Lunghezza 10 Lunghezza (fisico) 10d5 =2025-05-21 10:15:30.12346 +07:00 Slot 0 Colonna 7 Offset 0x38 Lunghezza 10 Lunghezza (fisico) 10d6 =2025-05-21 10:15:30.123457 +07:00 Slot 0 Colonna 8 Offset 0x42 Lunghezza 10 Lunghezza (fisica) 10d7 =21-05-2025 10:15:30.1234567 +07:00 

Quindi otteniamo di nuovo lo stesso risultato. Esattamente come afferma la documentazione.

Mentre siamo qui, esaminiamo i dati:i valori di data/ora effettivi come sono archiviati in SQL Server.

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

 Dump di memoria @0x000000041951A0600000000000000000:10004C00 D22D003F 480BA401 35CA013F 480BA401 ..l.ò-. H.¤.5ê. H.¤.óßý0000000000000028:063F480B A4017ABF EA45003F 480BA401 C17A2BBBBB. ..

Ciò include ancora alcuni bit in più. Eliminiamo alcune cose, in modo che rimangano solo i nostri valori di data e ora:

d22d003f 480ba401 35ca013f 480ba40114e6113f 480ba401 cbfcb200 3f480ba4 01f3dffd063f480b a4017abf ea45003f 480ba401 c17a2bbb023f480b>8 b20181b>8 b201817 

Le restanti cifre esadecimali contengono tutti i nostri dati di data e ora, 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à.

d22d003f480ba401 35ca013f480ba40114e6113f480ba401 cbfcb2003f480ba401f3dffd063f480ba4017abfea45003f480ba401 c17a2bbb023f480ba40187cbb240f1b>31 

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

SELECT CONVERT(VARBINARY(16), d0) AS 'd0', CONVERT(VARBINARY(16), d1) AS 'd1', CONVERT(VARBINARY(16), d2) AS 'd2', CONVERT(VARBINARY( 16), d3) COME 'd3', CONVERT(VARBINARY(16), d4) COME 'd4', CONVERT(VARBINARY(16), d5) COME 'd5', CONVERT(VARBINARY(16), d6) COME 'd6 ', CONVERT(VARBINARY(16), d7) AS 'd7'FROM DatetimeoffsetTest;

Risultato (usando l'output verticale):

d0 | 0x00D22D003F480BA401d1 | 0x0135CA013F480BA401d2 | 0x0214E6113F480BA401d3 | 0x03CBFCB2003F480BA401d4 | 0x04F3DFFD063F480BA401d5 | 0x057ABFEA45003F480BA401d6 | 0x06C17A2BBB023F480BA401d7 | 0x0787CBB24F1B3F480BA401

Quindi otteniamo lo stesso risultato, tranne per il fatto che è 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()
d22d003f480ba401 00D22D003F480BA401
35ca013f480ba401 0135CA013F480BA401
14e6113f480ba401 0214E6113F480BA401
cbfcb2003f480ba401 03CBFCB2003F480BA401
f3dffd063f480ba401 04F3DFFD063F480BA401
7abfea45003f480ba401 057ABFEA45003F480BA401
c17a2bbb023f480ba401 06C17A2BBB023F480BA401
87cbb24f1b3f480ba401 0787CBB24F1B3F480BA401

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 datetimeoffset 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 utilizzano comunque la stessa precisione. Ciò richiederebbe un byte aggiuntivo per ogni riga, il che aumenterebbe inutilmente i requisiti di archiviazione.