Sqlserver
 sql >> Datenbank >  >> RDS >> Sqlserver

Grundlegendes zur Speichergröße „datetimeoffset“ in SQL Server

In diesem Artikel schaue ich mir an, wie der datetimeoffset Datentyp in SQL Server gespeichert wird und wie Sie unterschiedliche gemeldete Speichergrößenergebnisse erhalten können, je nachdem, was Sie damit machen.

Dies ähnelt dem, was ich mit datetime2 gemacht habe Datentyp.

Insbesondere schaue ich mir Folgendes an:

  • Dokumentation von Microsoft
  • In einer Variablen gespeicherte Daten
    • Länge in Bytes mit DATALENGTH()
    • Länge in Bytes mit DATALENGTH() nach der Konvertierung in varbinary
  • In einer Datenbank gespeicherte Daten
    • Länge in Bytes mit COL_LENGTH()
    • Länge in Bytes mit DBCC PAGE()

Microsoft-Dokumentation

Die offizielle Dokumentation von Microsoft zum datetimeoffset Datentyp gibt an, dass seine Speichergröße zwischen 8 und 10 Bytes liegt, abhängig von der verwendeten Genauigkeit.

Ähnlich wie datetime2(n) , können Sie datetimeoffset(n) verwenden um die Genauigkeit anzugeben, wobei n ist eine Skala zwischen 0 und 7.

Hier sind die Daten, die Microsoft für diesen Datentyp präsentiert:

Angegebener Maßstab Ergebnis (Präzision, Skalierung) Spaltenlänge (Bytes) Präzision in Sekundenbruchteilen
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

Für die Zwecke dieses Artikels interessiere ich mich hauptsächlich für die Spaltenlänge (Byte) Säule. Dies sagt uns, wie viele Bytes verwendet werden, um diesen Datentyp in einer Datenbank zu speichern.

Der Hauptgrund, warum ich diesen Artikel schreiben (und die folgenden Experimente durchführen) wollte, ist, dass die Microsoft-Dokumentation nicht erklärt, dass ein zusätzliches Byte für die Genauigkeit verwendet wird (wie es in der Dokumentation für datetime2 Datentyp). In seiner Dokumentation für datetime2 , heißt es:

Das erste Byte eines datetime2 value speichert die Genauigkeit des Werts, d. h. den tatsächlichen Speicherbedarf für ein datetime2 Wert ist die in der obigen Tabelle angegebene Speichergröße plus 1 zusätzliches Byte zum Speichern der Genauigkeit. Dies ergibt die maximale Größe eines datetime2 Wert 9 Byte – 1 Byte speichert die Genauigkeit plus 8 Byte für die Datenspeicherung mit maximaler Genauigkeit.

Aber die Dokumentation für datetimeoffset enthält diesen Text nicht und die Zeit auch nicht Dokumentation.

Dies veranlasste mich zu der Frage, ob es einen Unterschied gibt, wie diese Datentypen ihre Werte speichern. Logic sagte mir, dass sie gleich funktionieren sollten, da sie alle eine benutzerdefinierte Genauigkeit haben, aber ich wollte es herausfinden.

Die kurze Antwort ist ja, datetimeoffset scheint genauso zu funktionieren wie datetime2 (in Bezug auf das zusätzliche Byte), obwohl es nicht als solches dokumentiert ist.

Der Rest des Artikels durchläuft verschiedene Beispiele, in denen ich die Speichergröße von datetimeoffset zurückgebe Werte in verschiedenen Kontexten.

In einer Variablen gespeicherte Daten

Lassen Sie uns ein datetimeoffset speichern Wert in einer Variablen und überprüfen Sie ihre Speichergröße. Dann konvertiere ich diesen Wert in varbinary und überprüfe es erneut.

Länge in Bytes mit DATALENGTH

Folgendes passiert, wenn wir DATALENGTH() verwenden Funktion, um die Anzahl der Bytes zurückzugeben, die für ein datetimeoffset(7) verwendet werden Wert:

DECLARE @d datetimeoffset(7);SET @d ='2025-05-21 10:15:30.1234567 +07:00';SELECT @d AS 'Value', DATALENGTH(@d) AS 'Length in Bytes ';

Ergebnis

+------------------------------------+--------- ----------+| Wert | Länge in Bytes ||------------------------------------+-------- -----------|| 2025-05-21 10:15:30.1234567 +07:00 | 10 |+------------------------------------+---------- ---------+

Der Wert in diesem Beispiel hat die maximale Skalierung von 7 (weil ich die Variable als datetimeoffset(7) deklariere ) und gibt eine Länge von 10 Byte zurück.

Keine Überraschungen, dies ist genau die Speichergröße, die die Microsoft-Dokumentation angibt.

Wenn wir den Wert jedoch in varbinary umwandeln wir erhalten ein anderes Ergebnis.

Länge in Bytes nach der Konvertierung in „varbinary“

Einige Entwickler konvertieren gerne datetimeoffset und datetime2 Variablen in varbinary , weil es repräsentativer dafür ist, wie SQL Server es in der Datenbank speichert. Obwohl dies teilweise zutrifft, stimmen die Ergebnisse nicht genau mit dem gespeicherten Wert überein (wie Sie später sehen werden).

Folgendes passiert, wenn wir unseren datetimeoffset umwandeln Wert auf 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 'Länge in Bytes';

Ergebnis

+---------------------+------------------- +| Wert | Länge in Bytes ||---------------------+----------------- -|| 0x0787CBB24F1B3F480BA401 | 11 |+---------------------+-------------------+ 

In diesem Fall erhalten wir 11 Bytes.

Dies ist eine hexadezimale Darstellung des datetimeoffset Wert. Der tatsächliche Datums-Zeit-Offsetwert (und seine Genauigkeit) ist alles nach dem 0x . Jedes Paar Hexadezimalzeichen ist ein Byte. Es gibt 11 Paare und daher 11 Bytes. Dies wird bestätigt, wenn wir DATALENGTH() verwenden um die Länge in Bytes zurückzugeben.

In diesem Beispiel sehen wir, dass das erste Byte 07 ist . Dies stellt die Genauigkeit dar (ich habe eine Skala von 7 verwendet und das wird hier angezeigt).

Wenn ich die Skalierung ändere, können wir sehen, dass sich das erste Byte ändert, um der Skalierung zu entsprechen:

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 'Länge in Bytes';

Ergebnis

+------------------------+-------------------+| Wert | Länge in Byte ||--------------------------------+--------------| | 0x03CBFCB2003F480BA401 | 10 |+------------------------+--------------+ 

Wir können auch sehen, dass die Länge entsprechend reduziert wird.

In einer Datenbank gespeicherte Daten

In diesem Beispiel erstelle ich eine Datenbank mit verschiedenen datetimeoffset(n) Spalten und verwenden Sie dann COL_LENGTH() um die Länge jeder Spalte in Bytes zurückzugeben. Ich füge dann Werte in die Spalten ein, bevor ich DBCC PAGE verwende um die Speichergröße jedes datetimeoffset zu überprüfen Wert nimmt die Auslagerungsdatei auf.

Erstellen Sie eine Datenbank:

CREATE DATABASE Test;

Erstellen Sie eine Tabelle:

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 diesem Fall erstelle ich acht Spalten – eine für jede benutzerdefinierte Skala, die wir mit datetimeoffset(n) verwenden können .

Jetzt können wir die Speichergröße jeder Spalte überprüfen.

Länge in Bytes mit COL_LENGTH()

Verwenden Sie COL_LENGTH() um die Länge (in Bytes) jeder Spalte zu überprüfen:

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

Ergebnis:

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

Wir erhalten also noch einmal das gleiche Ergebnis wie die Dokumentation, die wir erhalten werden. Dies ist zu erwarten, da in der Dokumentation ausdrücklich „Spaltenlänge (Bytes)“ angegeben ist, was wir hier genau messen.

Verwenden Sie DBCC PAGE, um die gespeicherten Daten zu überprüfen

Lassen Sie uns nun DBCC PAGE verwenden um die tatsächliche Speichergröße der Daten zu finden, die wir in dieser Tabelle speichern.

Lassen Sie uns zunächst einige Daten einfügen:

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;

Wählen Sie nun die Daten aus (nur zur Kontrolle):

SELECT * FROM DatetimeoffsetTest;

Ergebnis (bei vertikaler Ausgabe):

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

Wie erwartet verwenden die Werte die Genauigkeit, die zuvor auf Spaltenebene angegeben wurde.

Beachten Sie, dass mein System nachgestellte Nullen anzeigt. Ihre kann dies tun oder auch nicht. Unabhängig davon hat dies keinen Einfluss auf die tatsächliche Präzision oder Genauigkeit.

Nun, bevor wir DBCC PAGE() verwenden , müssen wir wissen, welche PagePID an sie übergeben werden soll. Wir können DBCC IND() verwenden um das zu finden.

Suchen Sie die PagePID:

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

Ergebnis (bei vertikaler Ausgabe):

-[ RECORD 1 ]-------------------------PageFID | 1PagePID | 307IAMFID | NULLIAMPID | NULLObjectID | 1525580473IndexID | 0Partitionsnummer | 1Partitions-ID | 72057594043170816iam_chain_type | Zeileninterner dataPageType | 10IndexLevel | NULLNächsteSeiteFID | 0NächsteSeitePID | 0VorherigeSeiteFID | 0VorherigeSeitePID | 0-[ RECORD 2 ]-------------------------SeiteFID | 1PagePID | 376IAMFID | 1IAMPID | 307ObjektID | 1525580473IndexID | 0Partitionsnummer | 1Partitions-ID | 72057594043170816iam_chain_type | Zeileninterner dataPageType | 1IndexLevel | 0NächsteSeiteFID | 0NächsteSeitePID | 0VorherigeSeiteFID | 0VorherigeSeitePID | 0

Dies gibt zwei Datensätze zurück. Uns interessiert der PageType von 1 (der 2. Datensatz). Wir wollen die PagePID aus diesem Datensatz. In diesem Fall ist die PagePID 376 .

Jetzt können wir diese PagePID nehmen und sie im Folgenden verwenden:

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

Im Moment interessiert uns hauptsächlich der folgende Teil:

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

So erhalten wir wieder das gleiche Ergebnis. Genau wie in der Dokumentation angegeben.

Während wir hier sind, lassen Sie uns die Daten untersuchen – die tatsächlichen Datums-/Uhrzeitwerte, wie sie in SQL Server gespeichert sind.

Die eigentlichen Werte werden in diesem Teil der Auslagerungsdatei gespeichert:

 Memory Dump @0x000000041951A06000000000000000:10004C00 D22D003F 480BA401 35CA013F 480BA401 ..L.ò-. H.óßý000000000000000028:063F480B A4017ABF EA45003F 480BA401 C17A2BBBB. ..

Das beinhaltet noch ein paar zusätzliche Bits. Lassen Sie uns ein paar Dinge löschen, sodass nur unsere Datums- und Zeitwerte übrig bleiben:

 D22D003F 480BA401 35CA013F 480BA40114E6113F 480BA401 CBFCB200 3F480BA4 01F3DFFD063F480B A4017ABF EA45003F 480BA401 C17A223F40B0b 380BA401 C17A223F40B0b 380ba401 

Die verbleibenden Hexadezimalziffern enthalten alle unsere Datums- und Zeitdaten, aber nicht die Genauigkeit . Sie sind jedoch in 4-Byte-Blöcke angeordnet, sodass wir die Leerzeichen neu anordnen müssen, um die einzelnen Werte zu erhalten.

Hier ist das Endergebnis. Zur besseren Lesbarkeit habe ich jeden Datums-/Uhrzeitwert in eine neue Zeile eingefügt.

d22d003f480ba401 35ca013f480ba40114e6113f480ba401 cbfcb2003f480ba401f3dffd063f480ba4017abfea45003f480ba401 c17a2bbb023f480ba40187cbb24f1b3f1 

Das sind die tatsächlichen Hexadezimalwerte (abzüglich der Genauigkeit ), die wir erhalten würden, wenn wir den datetimeoffset konvertieren würden Wert auf varbinary . So:

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

Ergebnis (bei vertikaler Ausgabe):

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

Wir erhalten also dasselbe Ergebnis – außer dass ihm die Genauigkeit vorangestellt wurde.

Hier ist eine Tabelle, die die tatsächlichen Daten der Auslagerungsdatei mit den Ergebnissen von CONVERT() vergleicht Betrieb.

Auslagerungsdateidaten CONVERT()-Daten
d22d003f480ba401 00D22D003F480BA401
35ca013f480ba401 0135CA013F480BA401
14e6113f480ba401 0214E6113F480BA401
cbfcb2003f480ba401 03CBFCB2003F480BA401
f3dffd063f480ba401 04F3DFFD063F480BA401
7abfea45003f480ba401 057ABFEA45003F480BA401
c17a2bbb023f480ba401 06C17A2BBB023F480BA401
87cbb24f1b3f480ba401 0787CBB24F1B3F480BA401

Wir können also sehen, dass die Auslagerungsdatei die Genauigkeit nicht speichert, aber das konvertierte Ergebnis.

Ich habe die tatsächlichen Datums- und Uhrzeitteile rot hervorgehoben. Ich habe auch das 0x entfernt Präfix aus den konvertierten Ergebnissen, sodass nur die tatsächlichen Datums-/Uhrzeitdaten angezeigt werden (zusammen mit der Genauigkeit).

Beachten Sie auch, dass bei Hexadezimalzahlen die Groß- und Kleinschreibung nicht beachtet wird, sodass die Tatsache, dass der eine Kleinbuchstaben und der andere Großbuchstaben verwendet, kein Problem darstellt.

Schlussfolgerung

Beim Konvertieren eines datetimeoffset Wert auf varbinary , benötigt es ein zusätzliches Byte, um die Genauigkeit zu speichern. Es benötigt die Genauigkeit, um den Zeitabschnitt zu interpretieren (weil dies als Zeitintervall gespeichert wird, dessen genauer Wert von der Genauigkeit abhängt).

Beim Speichern in einer Datenbank wird die Genauigkeit einmal auf Spaltenebene angegeben. Dies erscheint logisch, da die Genauigkeit nicht jeder Zeile hinzugefügt werden muss, wenn alle Zeilen ohnehin dieselbe Genauigkeit verwenden. Das würde ein zusätzliches Byte für jede Zeile erfordern, was den Speicherbedarf unnötig erhöhen würde.