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

Forståelse af 'datetime2'-lagerstørrelse i SQL Server

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

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 bruger DATALENGTH() 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 bruger DBCC 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.1234567

Som 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 bruge DBCC 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 | 0

Dette 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> ...pre 

Som 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 c1f63498018f5
 Det 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 | 0x0787A311FC553F480B

Så 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).


  1. SQL Server Internals:Problematiske Operatører Pt. II – Hashing

  2. Tips til lagring af PostgreSQL-sikkerhedskopier på Google Cloud (GCP)

  3. PDO:MySQL-serveren er forsvundet

  4. Sådan beskytter du dine PostgreSQL-databaser mod cyberangreb med SQL Firewall