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

Verständnis der Speichergröße „datetime2“ in SQL Server

In diesem Artikel teile ich einige Beobachtungen, die ich bezüglich datetime2 gemacht habe Speichergröße des Datentyps in SQL Server. Vielleicht werde ich einige Punkte über die tatsächliche Speichergröße klären, die von diesem Datentyp verwendet wird, wenn er in einer Datenbank gespeichert wird.

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

Einige davon scheinen einander zu widersprechen, und je nachdem, wo Sie suchen, sehen Sie zwei unterschiedliche Speichergrößen für denselben Wert.

Ein datetime2 -Wert kann eine andere Speichergröße anzeigen, je nachdem, ob er in einer Datenbank gespeichert ist, als datetime2 variabel oder in varbinary konvertiert .

Dafür gibt es aber eine plausible Erklärung – es kommt darauf an, wo die Präzision liegt wird gespeichert.

Während meiner Recherche zu diesem Thema fand ich Ronen Arielys ausführlichen Artikel darüber, wie datetime2 ist in der Datendatei sehr aufschlussreich und hat mich veranlasst, einige ähnliche Tests in meiner eigenen Entwicklungsumgebung durchzuführen und sie hier vorzustellen.

Microsoft-Dokumentation

Schauen wir uns zunächst an, was die offizielle Dokumentation sagt.

Microsofts Dokumentation zu datetime2 Datentyp gibt an, dass seine Speichergröße wie folgt ist:

6 Byte für eine Genauigkeit von weniger als 3.
7 Byte für eine Genauigkeit von 3 oder 4.
Alle anderen Genauigkeiten erfordern 8 Byte.

Aber es qualifiziert die obige Tabelle mit der folgenden Aussage:

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.

Angesichts der obigen Informationen wäre die offensichtliche Schlussfolgerung, dass die Tabelle wie folgt geschrieben werden könnte/(sollte?):

7 Bytes für eine Genauigkeit von weniger als 3.
8 Bytes für eine Genauigkeit von 3 oder 4.
Alle anderen Genauigkeiten erfordern 9 Bytes.

Auf diese Weise müssten sie es nicht mit den zusätzlichen Informationen zur Genauigkeit qualifizieren.

Aber ganz so einfach ist es nicht.

In einer Variablen gespeicherte Daten

Lassen Sie uns zuerst ein datetime2 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 für ein datetime2(7) verwendeten Bytes zurückzugeben Wert:

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

Ergebnis

+-------------------------------------+---------------- ---+| Wert | Länge in Byte ||-------------------------------------+--------------- ----|| 2025-05-21 10:15:30.1234567 | 8 |+-------------------------------------+---------------- --+

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

Dies scheint der Aussage von Microsoft zu widersprechen, dass ein zusätzliches Byte zum Speichern der Genauigkeit erforderlich ist. Um Microsoft zu zitieren: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. .

Es stimmt zwar, dass wir anscheinend 8 Byte für die Datenspeicherung bekommen , scheint uns das 1 Byte zu fehlen, das zum Speichern der Genauigkeit verwendet wird.

Wenn wir den Wert jedoch in varbinary umwandeln wir bekommen eine andere Geschichte.

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

Folgendes passiert, wenn wir unsere datetime2 umwandeln Wert auf varbinary :

DECLARE @d datetime2(7);SET @d ='2025-05-21 10:15:30.1234567';SELECT CONVERT(VARBINARY(10), @d) AS 'Value', DATALENGTH(CONVERT(VARBINARY( 10), @d)) AS 'Länge in Bytes';

Ergebnis

+---------------------+--------------+| Wert | Länge in Byte ||---------------------+--------------|| 0x0787A311FC553F480B | 9 |+----------------------+-------------------+

In diesem Fall erhalten wir 9 Bytes.

Dies ist eine hexadezimale Darstellung von datetime2 Wert. Der tatsächliche Datumszeitwert (und seine Genauigkeit) ist alles nach dem 0x . Jedes Paar Hexadezimalzeichen ist ein Byte. Es gibt 9 Paare und daher 9 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 datetime2(3);SET @d ='2025-05-21 10:15:30.1234567';SELECT CONVERT(VARBINARY(10), @d) AS 'Value', DATALENGTH(CONVERT(VARBINARY( 10), @d)) AS 'Länge in Bytes';

Ergebnis

+--------------------+---------------+| Wert | Länge in Byte ||--------------------+---------------------------|| 0x034B8233023F480B | 8 |+--------------------+--------------+

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

In diesem Fall stimmen unsere Ergebnisse also perfekt mit der Microsoft-Dokumentation überein – für die Genauigkeit wurde ein zusätzliches Byte hinzugefügt.

Viele Entwickler gehen davon aus, dass SQL Server auf diese Weise sein datetime2 speichert Werte in der Datenbank. Diese Annahme scheint jedoch falsch zu sein.

In einer Datenbank gespeicherte Daten

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

Erstellen Sie eine Datenbank:

CREATE DATABASE Test;

Erstellen Sie eine Tabelle:

USE Test;CREATE TABLE Datetime2Test (d0 datetime2(0), d1 datetime2(1), d2 datetime2(2), d3 datetime2(3), d4 datetime2(4), d5 datetime2(5), d6 datetime2(6 ), d7 datetime2(7) );

In diesem Fall erstelle ich acht Spalten – eine für jede benutzerdefinierte Skala, die wir mit datetime2(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 ( 'Datetime2Test' , 'd0' ) AS 'd0', COL_LENGTH ( 'Datetime2Test' , 'd1' ) AS 'd1', COL_LENGTH ( 'Datetime2Test' , 'd2' ) AS 'd2', COL_LENGTH ( 'Datetime2Test' , 'd3' ) AS 'd3', COL_LENGTH ( 'Datetime2Test' , 'd4' ) AS 'd4', COL_LENGTH ( 'Datetime2Test' , 'd5' ) AS 'd5', COL_LENGTH ( 'Datetime2Test' , 'd6' ) AS 'd6', COL_LENGTH ( 'Datetime2Test' , 'd7' ) AS 'd7'; 

Ergebnis:

+------+------+------+------+------+------+---- --+------+| d0 | d1 | d2 | d3 | d4 | d5 | d6 | d7 ||------+------+------+------+------+------+----- -+------|| 6 | 6 | 6 | 7 | 7 | 8 | 8 | 8 |+------+------+------+------+------+------+----- -+------+

Also noch einmal, wir scheinen das zusätzliche Byte nicht zu bekommen, das zum Speichern der Genauigkeit verwendet wird.

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 datetime2(7) ='2025-05-21 10:15:30.1234567';INSERT INTO Datetime2Test ( 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 Datetime2Test;

Ergebnis (bei vertikaler Ausgabe):

d0 | 2025-05-21 10:15:30d1 | 2025-05-21 10:15:30.1d2 | 2025-05-21 10:15:30.12d3 | 2025-05-21 10:15:30.123d4 | 2025-05-21 10:15:30.1235d5 | 2025-05-21 10:15:30.12346d6 | 2025-05-21 10:15:30.123457d7 | 2025-05-21 10:15:30.1234567

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

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

Ergebnis (bei vertikaler Ausgabe):

-[ RECORD 1 ]-------------------------PageFID | 1PagePID | 306IAMFID | NULLIAMPID | NULLObjectID | 1205579333IndexID | 0Partitionsnummer | 1Partitions-ID | 72057594043039744iam_chain_type | Zeileninterner dataPageType | 10IndexLevel | NULLNächsteSeiteFID | 0NächsteSeitePID | 0VorherigeSeiteFID | 0VorherigeSeitePID | 0-[ RECORD 2 ]-------------------------SeiteFID | 1PagePID | 360IAMFID | 1IAMPID | 306ObjektID | 1205579333IndexID | 0Partitionsnummer | 1Partitions-ID | 72057594043039744iam_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 360 .

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

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

Dies erzeugt eine Menge Daten, aber wir interessieren uns hauptsächlich für den folgenden Teil:

Slot 0 Spalte 1 Offset 0x4 Länge 6 Länge (physisch) 6d0 =2025-05-21 10:15:30 Slot 0 Spalte 2 Offset 0xa Länge 6 Länge (physisch) 6d1 =2025-05-21 10:15:30.1 Slot 0 Spalte 3 Offset 0x10 Länge 6 Länge (physisch) 6d2 =2025-05-21 10:15:30.12 Slot 0 Spalte 4 Offset 0x16 Länge 7 Länge (physisch) 7d3 =2025-05-21 10:15:30.123 Slot 0 Spalte 5 Offset 0x1d Länge 7 Länge (physisch) 7d4 =2025-05-21 10:15:30.1235 Slot 0 Spalte 6 Offset 0x24 Länge 8 Länge (physisch) 8d5 =2025-05-21 10:15:30.12346 Slot 0 Spalte 7 Offset 0x2c Länge 8 Länge (physisch) 8d6 =2025-05-21 10:15:30.123457 Slot 0 Spalte 8 Offset 0x34 Länge 8 Länge (physisch) 8d7 =2025-05-21 10:15:30.1234567 

Es scheint also, dass das zusätzliche Byte nicht für die Genauigkeit verwendet wird.

Aber lassen Sie uns die tatsächlichen Daten untersuchen, bevor wir irgendwelche Schlussfolgerungen ziehen.

Die eigentlichen Daten werden in diesem Teil der Auslagerungsdatei gespeichert:

 Memory Dump @0x000000041883A06000000000000000:10003C00 4290003F 480B95A2 053F480B D459383F .. <. B ..? H. • ¢. H.zå.Ü0000000000000028:003f480b c1f63499 083f480b 87a311fc 553f480b .?H.Áö4..?H.‡£.üU?H.000000000000003C:080000 ... ... 

Wie Sie sehen können, sieht nichts davon wie die Ergebnisse aus, die wir durch Umwandeln von datetime2 erhalten würden Wert auf varbinary . Aber es ist ziemlich nah.

So sieht es aus, wenn ich ein paar Dinge lösche:

4290003f 480b95a2 053f480b d459383f480b4b82 33023f48 0bf31603 163f480b 7ae51edc003f480b c1f63499 083f480b 87a311fc 553f480b

Die verbleibenden Hexadezimalziffern enthalten alle unsere Datums- und Zeitdaten, aber nicht die Genauigkeit . Allerdings müssen wir die Leerzeichen neu anordnen, um die tatsächlichen Werte für jede Zeile zu erhalten.

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

4290003f480b 95a2053f480b d459383f480b 4b8233023f480bf31603163f480b 7ae51edc003f480b c1f63499083f480b 87a311fc553f480b

Das sind die tatsächlichen Hexadezimalwerte (abzüglich der Genauigkeit ), die wir erhalten würden, wenn wir datetime2 konvertieren würden Wert auf varbinary . Um sicherzugehen, gehen wir gleich vor und tun genau das:

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

Ergebnis (bei vertikaler Ausgabe):

d0 | 0x004290003F480Bd1 | 0x0195A2053F480Bd2 | 0x02D459383F480Bd3 | 0x034B8233023F480Bd4 | 0x04F31603163F480Bd5 | 0x057AE51EDC003F480Bd6 | 0x06C1F63499083F480Bd7 | 0x0787A311FC553F480B

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

Aber um die Dinge glasklar zu machen, hier ist eine Tabelle, die die tatsächlichen Daten der Auslagerungsdatei mit den Ergebnissen von CONVERT() vergleicht Betrieb.

Auslagerungsdateidaten CONVERT()-Daten
4290003f480b 004290003F480B
95a2053f480b 0195A2053F480B
d459383f480b 02D459383F480B
4b8233023f480b 034B8233023F480B
f31603163f480b 04F31603163F480B
7ae51edc003f480b 057AE51EDC003F480B
c1f63499083f480b 06C1F63499083F480B
87a311fc553f480b 0787A311FC553F480B

Wir können also deutlich 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 datetime2 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 es nicht erforderlich ist, mit jeder Zeile ein zusätzliches Byte zu speichern, wenn es auf Spaltenebene angegeben werden kann. Wenn Sie also beispielsweise datetime2(7) angeben auf Spaltenebene, dann ist jede einzelne Zeile datetime2(7) . Dies muss nicht in jeder Zeile wiederholt werden.

Ronen Ariely kam in seinem oben erwähnten Artikel zu demselben Schluss.

Wenn Sie eine Million Zeilen mit datetime2(7) haben Werten würde das Speichern der Genauigkeit für jede Zeile 9.000.000 Bytes erfordern, im Vergleich zu nur 8.000.001, wenn die Genauigkeit einmal für die gesamte Spalte gespeichert wird.

Dies stärkt auch datetime2 Dies ist der Fall, wenn Sie es mit datetime vergleichen . Auch wenn die gleiche Anzahl an Dezimalstellen wie datetime verwendet wird (z. B. 3), datetime2 Datentyp benötigt weniger Speicherplatz (zumindest wenn er in einer Tabelle mit mehr als einer Zeile gespeichert wird). Und das mit höherer Genauigkeit. Eine datetime value verwendet 8 Bytes, wohingegen datetime2(3) verwendet 7 Byte (plus 1 „Präzisions“-Byte, das von allen Zeilen gemeinsam genutzt wird).