I denne artikel deler jeg nogle observationer, jeg har haft vedrørende datetime2 datatypens lagerstørrelse i SQL Server. Måske vil jeg præcisere nogle punkter om den faktiske lagerstørrelse, der bruges af denne datatype, når den er gemt i en database.
Jeg ser især på følgende:
- Microsofts dokumentation
- Data gemt i en variabel
- Længde i bytes ved hjælp af
DATALENGTH()
- Længde i bytes ved hjælp af
DATALENGTH()
efter konvertering til varbinary
- Længde i bytes ved hjælp af
- Data gemt i en database
- Længde i bytes ved hjælp af
COL_LENGTH()
- Længde i bytes ved hjælp af
DBCC PAGE()
- Længde i bytes ved hjælp af
Nogle af dem ser ud til at modsige hinanden, og du vil se to forskellige lagerstørrelser til samme værdi, afhængigt af hvor du kigger.
En datetime2 værdi kan vise en anden lagerstørrelse, afhængig af om den er gemt i en database, som en datetime2 variabel eller konverteret til varbinær .
Men der er en plausibel forklaring på dette – det afhænger af, hvor præcisionen er bliver gemt.
Under min forskning i dette spørgsmål fandt jeg Ronen Arielys dybdegående artikel om, hvordan datetime2 er gemt i datafilen meget informativ, og det fik mig til at køre nogle lignende tests i mit eget udviklingsmiljø og præsentere dem her.
Microsofts dokumentation
Lad os først se på, hvad den officielle dokumentation siger.
Microsofts dokumentation om datetime2 datatypen angiver, at dens lagerstørrelse er som følger:
6 bytes for præcision mindre end 3.
7 bytes for præcision 3 eller 4.
Al anden præcision kræver 8 bytes.
Men det kvalificerer ovenstående tabel med følgende udsagn:
Den første byte af en datetime2 værdi gemmer nøjagtigheden af værdien, hvilket betyder den faktiske lagring, der kræves for en datetime2 værdi er lagerstørrelsen angivet i tabellen ovenfor plus 1 ekstra byte til at gemme præcisionen. Dette gør den maksimale størrelse af en datetime2 værdi 9 bytes – 1 byte gemmer præcision plus 8 bytes til datalagring med maksimal præcision.
Så givet ovenstående information ville den indlysende konklusion at drage være, at tabellen kunne/(bør?) skrives som følger:
7 bytes for præcision mindre end 3.
8 bytes for præcision 3 eller 4.
Al anden præcision kræver 9 bytes.
På den måde behøver de ikke at kvalificere det med de ekstra oplysninger om præcision.
Men det er ikke helt så enkelt som det.
Data gemt i en variabel
Lad os først gemme en datetime2 værdi i en variabel og kontroller dens lagerstørrelse. Så konverterer jeg denne værdi til varbinary og tjek det igen.
Længde i bytes ved hjælp af DATALENGTH
Her er, hvad der sker, hvis vi bruger DATALENGTH()
funktion til at returnere antallet af bytes brugt til en datetime2(7) værdi:
DECLARE @d datetime2(7);SET @d ='2025-05-21 10:15:30.1234567';SELECT @d AS 'Value', DATALENGTH(@d) AS 'Length in Bytes';Resultat
+-------------------------------------+---------------- ---+| Værdi | Længde i bytes ||-------------------------------------+-------------- ----|| 2025-05-21 10:15:30.1234567 | 8 |+-------------------------------------+---------------- --+Værdien i dette eksempel har den maksimale skala på 7 (fordi jeg erklærer variablen som datetime2(7) ), og den returnerer en længde på 8 bytes.
Dette lader til at modsige, hvad Microsoft siger om at have brug for en ekstra byte til at gemme præcisionen. For at citere Microsoft,
Dette gør den maksimale størrelse af en datetime2 værdi 9 bytes – 1 byte gemmer præcision plus 8 bytes til datalagring med maksimal præcision..Selvom det er rigtigt, at vi ser ud til at få
8 bytes til datalagring, ser vi ud til at mangle den 1 byte, der bruges til at gemme præcisionen.Men hvis vi konverterer værdien til varbinary vi får en anden historie.
Længde i bytes efter konvertering til 'varbinary'
Her er, hvad der sker, hvis vi konverterer vores datetime2 værdi til 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ængde i bytes';Resultat
+-----------------------+-----------------+| Værdi | Længde i bytes ||-----------------------------+------------------|| 0x0787A311FC553F480B | 9 |+-----------------------------+-----------------+I dette tilfælde får vi 9 bytes.
Dette er en hexadecimal repræsentation af datetime2 værdi. Den faktiske dato-tidsværdi (og dens præcision) er alt efter
0x
. Hvert par hex-tegn er en byte. Der er 9 par og derfor 9 bytes. Dette bekræftes, når vi brugerDATALENGTH()
for at returnere længden i bytes.I dette eksempel kan vi se, at den første byte er
07
. Dette repræsenterer præcisionen (jeg brugte en skala fra 7, og det er det, der vises her).Hvis jeg ændrer skalaen, kan vi se, at den første byte ændres til at matche skalaen:
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ængde i bytes';Resultat
+---------------------+-----------------+| Værdi | Længde i bytes ||--------------------+------------------------|| 0x034B8233023F480B | 8 |+--------------------+------------------------+Vi kan også se, at længden reduceres tilsvarende.
Så i dette tilfælde matcher vores resultater Microsoft-dokumentationen perfekt – der er tilføjet en ekstra byte for præcisionen.
Mange udviklere antager, at det er sådan, SQL Server gemmer sin datetime2 værdier i databasen. Den antagelse ser dog ud til at være forkert.
Data gemt i en database
I dette eksempel opretter jeg en database, der indeholder en tabel med forskellige datetime2(n) kolonner. Jeg bruger derefter
COL_LENGTH()
for at returnere hver kolonnes længde i bytes. Derefter indsætter jeg værdier i det, før jeg brugerDBCC PAGE
for at kontrollere lagerstørrelsen for hver datetime2 værdi optager på sidefilen.Opret en database:
CREATE DATABASE Test;Opret en tabel:
BRUG Test; OPRET TABEL 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) );I dette tilfælde opretter jeg otte kolonner – en for hver brugerdefineret skala, som vi kan bruge med datetime2(n) .
Nu kan vi kontrollere lagerstørrelsen for hver kolonne.
Længde i bytes ved hjælp af COL_LENGTH()
Brug
COL_LENGTH()
for at kontrollere længden (i bytes) af hver kolonne: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';Resultat:
+------+------+------+------+------+------+---- --+------+| d0 | d1 | d2 | d3 | d4 | d5 | d6 | d7 ||------+------+------+------+------+------+----- -+------|| 6 | 6 | 6 | 7 | 7 | 8 | 8 | 8 |+------+------+------+------+------+------+----- -+------+Så endnu en gang ser vi ikke ud til at få brugt den ekstra byte til at lagre præcisionen.
Brug DBCC PAGE til at kontrollere de lagrede data
Lad os nu bruge
DBCC PAGE
for at finde den faktiske lagerstørrelse for de data, vi gemmer i denne tabel.Lad os først indsætte nogle data:
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;Vælg nu dataene (bare for at kontrollere dem):
VÆLG * FRA Datetime2Test;Resultat (ved hjælp af lodret output):
d0 | 21-05-2025 10:15:30d1 | 21-05-2025 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.1234567Som forventet bruger værdierne den præcision, der tidligere er angivet på kolonneniveau.
Nu, før vi bruger
DBCC PAGE()
, skal vi vide, hvilket PagePID der skal sendes til det. Vi kan brugeDBCC IND()
at finde det.Find PagePID:
DBCC IND('Test', 'dbo.Datetime2Test', 0);Resultat (ved hjælp af lodret output):
-[ RECORD 1 ]-------------------------PageFID | 1PagePID | 306IAMFID | NULLIAMPID | NULLObjectID | 1205579333Indeks-ID | 0PartitionNumber | 1PartitionID | 72057594043039744iam_chain_type | In-row dataPageType | 10Indeksniveau | NULLNextPageFID | 0NextPagePID | 0PrevPageFID | 0PrevPagePID | 0-[ RECORD 2 ]--------------------------------PageFID | 1PagePID | 360IAMFID | 1IAMPID | 306Objekt-ID | 1205579333Indeks-ID | 0PartitionNumber | 1PartitionID | 72057594043039744iam_chain_type | In-row dataPageType | 1Indeksniveau | 0NextPageFID | 0NextPagePID | 0PrevPageFID | 0PrevPagePID | 0Dette returnerer to poster. Vi er interesserede i PageType of 1 (den 2. post). Vi vil have PagePID fra den post. I dette tilfælde er PagePID 360 .
Nu kan vi tage det PagePID og bruge det i følgende:
DBCC TRACEON(3604, -1);DBCC PAGE(Test, 1, 360, 3);Dette producerer en masse data, men vi er primært interesserede i følgende del:
Slot 0 Kolonne 1 Offset 0x4 Længde 6 Længde (fysisk) 6d0 =2025-05-21 10:15:30 Slot 0 Kolonne 2 Offset 0xa Længde 6 Længde (fysisk) 6d1 =2025-05-251:10:30.1 Slot 0 Kolonne 3 Offset 0x10 Længde 6 Længde (fysisk) 6d2 =2025-05-21 10:15:30.12 Slot 0 Kolonne 4 Offset 0x16 Længde 7 Længde (fysisk) 7d3 =2025-05-05:30. 0 Kolonne 5 Offset 0x1d Længde 7 Længde (fysisk) 7d4 =2025-05-21 10:15:30.1235 Slot 0 Kolonne 6 Offset 0x24 Længde 8 Længde (fysisk) 8d5 =2025-05-25360 Slot 15360 15360. 7 Forskydning 0x2c Længde 8 Længde (fysisk) 8d6 =2025-05-21 10:15:30.123457 Slot 0 Kolonne 8 Offset 0x34 Længde 8 Længde (fysisk) 8d7 =2025-05-21 10.713:4 10.713:4 10.713.Så det ser ud til, at den ikke bruger den ekstra byte til præcision.
Men lad os undersøge de faktiske data, før vi når nogen konklusioner.
De faktiske data gemmes i denne del af sidefilen:
hukommelsesdump @0x000000041883A0600000000000000000:10003C00 4290003F 480B95A2 053F480B D459383F .. <. B ..? H. • ¢.? H.ôy8? 00000000000014:480b4b82 33023f48 H.zå.Ü00000000000000028:003f480b c1f63499 083f480b 87a311fc 553f480b .?H.Áö4..?H.‡£.üU?H.0000000> ...preSom du kan se, ligner intet af det de resultater, vi ville få ved at konvertere datetime2 værdi til varbinary . Men det er ret tæt på.
Sådan ser det ud, hvis jeg sletter et par ting:
4290003f 480b95a2 053f480b d459383f480b4b82 33023f48 0bf31603 163f480b 7ae51edc003f480b c1f834498 8f>De resterende hex-cifre indeholder alle vores dato- og tidsdata, men ikke præcisionen . Vi skal dog omarrangere mellemrummene for at få de faktiske værdier for hver række.
Her er slutresultatet. Jeg har placeret hver dato/tidsværdi på en ny linje for bedre læsbarhed.
4290003f480b 95a2053f480b d459383f480b 4b8233023f480bf31603163f480b 7ae51edc003f480b c1f63498018f53f63498018f5f5ae51edc003f480b c1f63498018f50183f5083f5018bDet er de faktiske hexadecimale værdier (minus præcisionen ), som vi ville få, hvis vi konverterede datetime2 værdi til varbinary . For at være sikker, lad os gå videre og gøre netop det:
VÆLG KONVERTER(VARBINARY(10); d0) SOM 'd0', KONVERTER(VARBINARY(10), d1) SOM 'd1', KONVERTER(VARBINARY(10); d2) SOM 'd2', KONVERTER(VARBINARY( 10), d3) SOM 'd3', KONVERTER(VARBINARY(10), d4) SOM 'd4', KONVERTER(VARBINARY(10), d5) SOM 'd5', KONVERTER(VARBINARY(10), d6) SOM 'd6 ', CONVERT(VARBINARY(10); d7) SOM 'd7'FRA Datetime2Test;Resultat (ved hjælp af lodret output):
d0 | 0x004290003F480Bd1 | 0x0195A2053F480Bd2 | 0x02D459383F480Bd3 | 0x034B8233023F480Bd4 | 0x04F31603163F480Bd5 | 0x057AE51EDC003F480Bd6 | 0x06C1F63499083F480Bd7 | 0x0787A311FC553F480BSå vi får det samme resultat – bortset fra at det er blevet præpenderet med præcisionen.
Men for at gøre tingene krystalklare er her en tabel, der sammenligner de faktiske sidefildata med resultaterne af
CONVERT()
operation.
Sidefildata | CONVERT() data |
---|---|
4290003f480b | 004290003F480B |
95a2053f480b | 0195A2053F480B |
d459383f480b | 02D459383F480B |
4b8233023f480b | 034B8233023F480B |
f31603163f480b | 04F31603163F480B |
7ae51edc003f480b | 057AE51EDC003F480B |
c1f63499083f480b | 06C1F63499083F480B |
87a311fc553f480b | 0787A311FC553F480B |
Så vi kan tydeligt se, at sidefilen ikke gemmer præcisionen, men det gør det konverterede resultat.
Jeg fremhævede de faktiske dato- og tidsdele med rødt. Jeg fjernede også 0x
præfiks fra de konverterede resultater, så kun de faktiske dato/tidsdata vises (sammen med præcisionen).
Bemærk også, at hexadecimal ikke skelner mellem store og små bogstaver, så det er ikke et problem, at den ene bruger små bogstaver og den anden bruger store bogstaver.
Konklusion
Når du konverterer en datetime2 værdi til varbinary , den har brug for en ekstra byte for at gemme præcisionen. Den har brug for præcisionen for at fortolke tidsdelen (fordi denne er gemt som et tidsinterval, hvis nøjagtige værdi vil afhænge af præcisionen).
Ved lagring i en database angives præcisionen én gang på kolonneniveau. Dette virker logisk, da der ikke er behov for at gemme en ekstra byte med hver række, hvis den kan specificeres på kolonneniveau. Så hvis du angiver, siger datetime2(7) på kolonneniveau, så vil hver enkelt række være datetime2(7) . Det er ikke nødvendigt at gentage dette på hver række.
Ronen Ariely nåede til samme konklusion i sin artikel nævnt ovenfor.
Hvis du har en million rækker med datetime2(7) værdier, vil lagring af præcisionen med hver række kræve 9.000.000 bytes sammenlignet med kun 8.000.001, hvis præcisionen gemmes én gang for hele kolonnen.
Dette styrker også datetime2 's tilfælde, når man sammenligner det med datetime . Selv når du bruger det samme antal decimaler som datetime (dvs. 3), datetime2 datatypen bruger mindre lagerplads (i det mindste når den er gemt i en tabel med mere end én række). Og det gør det med højere nøjagtighed. En dato og klokkeslæt værdi bruger 8 bytes, hvorimod datetime2(3) bruger 7 bytes (plus 1 "præcisions" byte, der deles på tværs af alle rækker).