sql >> Databasteknik >  >> RDS >> Sqlserver

Förstå "datetime2" lagringsstorlek i SQL Server

I den här artikeln delar jag med mig av några observationer jag har haft angående datetime2 datatypens lagringsstorlek i SQL Server. Jag kanske kommer att förtydliga några punkter om den faktiska lagringsstorleken som används av denna datatyp när den lagras i en databas.

Jag tittar särskilt på följande:

  • Microsofts dokumentation
  • Data lagrad i en variabel
    • Längd i byte med DATALENGTH()
    • Längd i byte med DATALENGTH() efter konvertering till varbinary
  • Data lagrad i en databas
    • Längd i byte med COL_LENGTH()
    • Längd i byte med DBCC PAGE()

Vissa av dessa verkar motsäga varandra, och du kommer att se två olika lagringsstorlekar för samma värde, beroende på var du tittar.

En datetime2 värde kan visa en annan lagringsstorlek, beroende på om den är lagrad i en databas, som en datetime2 variabel, eller omvandlas till variabel .

Men det finns en rimlig förklaring till detta – det beror på var precisionen är lagras.

Under min forskning om denna fråga hittade jag Ronen Arielys djupgående artikel om hur datetime2 lagras i datafilen mycket informativ, och det fick mig att köra några liknande tester i min egen utvecklingsmiljö och presentera dem här.

Microsofts dokumentation

Låt oss först titta på vad den officiella dokumentationen säger.

Microsofts dokumentation om datetime2 datatypen anger att dess lagringsstorlek är som följer:

6 byte för precision mindre än 3.
7 byte för precision 3 eller 4.
All annan precision kräver 8 byte.

Men det kvalificerar tabellen ovan med följande uttalande:

Den första byten i en datetime2 värde lagrar värdets precision, vilket innebär den faktiska lagring som krävs för en datetime2 värde är lagringsstorleken som anges i tabellen ovan plus 1 extra byte för att lagra precisionen. Detta gör den maximala storleken för en datetime2 värde 9 byte – 1 byte lagrar precision plus 8 byte för datalagring med maximal precision.

Så givet ovanstående information skulle den uppenbara slutsatsen att dra vara att tabellen skulle kunna/(bör?) skrivas enligt följande:

7 byte för precision mindre än 3.
8 byte för precision 3 eller 4.
All annan precision kräver 9 byte.

På så sätt skulle de inte behöva kvalificera det med den extra informationen om precision.

Men det är inte riktigt så enkelt.

Data lagrad i en variabel

Låt oss först lagra en datetime2 värde i en variabel och kontrollera dess lagringsstorlek. Sedan konverterar jag det värdet till varbinary och kontrollera det igen.

Längd i byte med DATALENGTH

Här är vad som händer om vi använder DATALENGTH() funktion för att returnera antalet byte som används för en datetime2(7) värde:

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ärde | Längd i byte ||-------------------------------------+-------------------- ----|| 2025-05-21 10:15:30.1234567 | 8 |+-------------------------------------+---------------- --+

Värdet i det här exemplet har den maximala skalan på 7 (eftersom jag deklarerar variabeln som datetime2(7) ), och den returnerar en längd på 8 byte.

Detta verkar motsäga vad Microsoft säger om att behöva en extra byte för att lagra precisionen. För att citera Microsoft, Detta gör den maximala storleken på en datetime2 värde 9 byte – 1 byte lagrar precision plus 8 byte för datalagring med maximal precision. .

Även om det är sant att vi verkar få 8 byte för datalagring , vi verkar sakna den 1 byte som används för att lagra precisionen.

Men om vi konverterar värdet till varbinary vi får en annan historia.

Längd i byte efter konvertering till 'varbinary'

Här är vad som händer om vi konverterar vår datetime2 värde till 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ängd i bytes';

Resultat

+-----------------------------+-----------------+| Värde | Längd i byte ||-----------------------------+------------------|| 0x0787A311FC553F480B | 9 |+-----------------------+------------------------+

I det här fallet får vi 9 byte.

Detta är en hexadecimal representation av datetime2 värde. Det faktiska datum- och tidsvärdet (och dess precision) är allt efter 0x . Varje par av hexadecken är en byte. Det finns 9 par och därför 9 byte. Detta bekräftas när vi använder DATALENGTH() för att returnera längden i byte.

I det här exemplet kan vi se att den första byten är 07 . Detta representerar precisionen (jag använde en skala på 7 och så det är vad som visas här).

Om jag ändrar skalan kan vi se att den första byten ändras för att matcha skalan:

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ängd i bytes';

Resultat

+--------------------+------------------------+| Värde | Längd i byte ||------------------------+------------------------|| 0x034B8233023F480B | 8 |+--------------------+------------------------+

Vi kan också se att längden minskar i motsvarande mån.

Så i det här fallet matchar våra resultat Microsofts dokumentation perfekt – en extra byte har lagts till för precisionen.

Många utvecklare antar att det är så SQL Server lagrar sin datetime2 värden i databasen. Det antagandet verkar dock vara felaktigt.

Data lagrad i en databas

I det här exemplet skapar jag en databas som innehåller en tabell med olika datetime2(n) kolumner. Jag använder sedan COL_LENGTH() för att returnera varje kolumns längd, i byte. Efter det lägger jag in värden i den innan jag använder DBCC PAGE för att kontrollera lagringsstorleken som varje datetime2 värde tar upp på sidfilen.

Skapa en databas:

CREATE DATABASE Test;

Skapa en tabell:

ANVÄND Test; SKAPA TABELL 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 det här fallet skapar jag åtta kolumner – en för varje användardefinierad skala som vi kan använda med datetime2(n) .

Nu kan vi kontrollera lagringsstorleken för varje kolumn.

Längd i byte med COL_LENGTH()

Använd COL_LENGTH() för att kontrollera längden (i byte) för varje kolumn:

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å än en gång verkar vi inte få den extra byten som används för att lagra precisionen.

Använd DBCC PAGE för att kontrollera lagrad data

Låt oss nu använda DBCC PAGE för att hitta den faktiska lagringsstorleken för data som vi lagrar i den här tabellen.

Låt oss först infoga lite 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älj nu data (bara för att kontrollera det):

VÄLJ * FRÅN Datetime2Test;

Resultat (med vertikal utdata):

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

Som förväntat använder värdena den precision som tidigare har angetts på kolumnnivå.

Nu, innan vi använder DBCC PAGE() måste vi veta vilket PagePID som ska skickas till det. Vi kan använda DBCC IND() för att hitta det.

Hitta PagePID:

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

Resultat (med vertikal utdata):

-[ RECORD 1 ]--------------------------------PageFID | 1PagePID | 306IAMFID | NULLIAMPID | NULLObjectID | 1205579333Index-ID | 0PartitionNumber | 1PartitionID | 72057594043039744iam_chain_type | In-row dataPageType | 10Indexnivå | NULLNextPageFID | 0NextPagePID | 0PrevPageFID | 0PrevPagePID | 0-[ RECORD 2 ]--------------------------------PageFID | 1PagePID | 360IAMFID | 1IAMPID | 306Objekt-ID | 1205579333Index-ID | 0PartitionNumber | 1PartitionID | 72057594043039744iam_chain_type | In-row dataPageType | 1Indexnivå | 0NextPageFID | 0NextPagePID | 0PrevPageFID | 0PrevPagePID | 0

Detta returnerar två rekord. Vi är intresserade av PageType of 1 (den andra posten). Vi vill ha PagePID från den posten. I det här fallet är PagePID 360 .

Nu kan vi ta det PagePID och använda det i följande:

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

Detta producerar mycket data, men vi är främst intresserade av följande del:

Flott 0 Kolumn 1 Offset 0x4 Längd 6 Längd (fysisk) 6d0 =2025-05-21 10:15:30 Slot 0 Kolumn 2 Offset 0xa Längd 6 Längd (fysisk) 6d1 =2025-05-251 10:30.1 Slot 0 Kolumn 3 Offset 0x10 Längd 6 Längd (fysisk) 6d2 =2025-05-21 10:15:30.12 Slot 0 Kolumn 4 Offset 0x16 Längd 7 Längd (fysisk) 7d3 =2025-05:05:30. 0 Kolumn 5 Offset 0x1d Längd 7 Längd (fysisk) 7d4 =2025-05-21 10:15:30.1235 Slot 0 Kolumn 6 Offset 0x24 Längd 8 Längd (fysisk) 8d5 =2025-05-123460 Slot. 7 Offset 0x2c Längd 8 Längd (fysisk) 8d6 =2025-05-21 10:15:30.123457 Slot 0 Kolumn 8 Offset 0x34 Längd 8 Längd (fysisk) 8d7 =2025-05-21 10.713 10.713:4 10.713 

Så det verkar som att den inte använder den extra byten för precision.

Men låt oss undersöka de faktiska uppgifterna innan vi drar några slutsatser.

Den faktiska informationen lagras i denna del av sidfilen:

 Memory Dump @0x000000041883A0600000000000000000:10003C00 4290003F 480B95A2 053F480B D459383F .. <. B ..? H. • ¢.? H.zå.Ü00000000000000028:003f480b c1f63499 083f480b 87a311fc 553f480b .?H.Áö4..?H.‡£.üU?H.0000000> ...pre 

Som du kan se ser inget av det ut som de resultat vi skulle få genom att konvertera datetime2 värde till varbinary . Men det är ganska nära.

Så här ser det ut om jag tar bort några saker:

4290003f 480b95a2 053f480b d459383f480b4b82 33023f48 0bf31603 163f480b 7ae51edc003f480b c1f63479 8b> 8b31603 163f480b 7ae51edc003f480b c1f634498 8b 

De återstående hexadecimala siffrorna innehåller alla våra datum- och tidsdata, men inte precisionen . Vi måste dock ordna om mellanrummen för att få de faktiska värdena för varje rad.

Här är slutresultatet. Jag har placerat varje datum/tidsvärde på en ny rad för bättre läsbarhet.

4290003f480b 95a2053f480b d459383f480b 4b8233023f480bf31603163f480b 7ae51edc003f480b c1f6349801c4f5ae51edc003f480b c1f6349801c480183f5083f3f6349901c4f> 

Det är de faktiska hexadecimala värdena (minus precisionen ) som vi skulle få om vi konverterade datetime2 värde till varbinary . För att vara säker, låt oss gå direkt och göra just det:

VÄLJ KONVERTERA(VARBINÄR(10), d0) SOM 'd0', KONVERTERA(VARBINÄR(10), d1) SOM 'd1', KONVERTERA(VARBINÄR(10), d2) SOM 'd2', KONVERTERA(VARBINÄR( 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) SOM 'd7'FRÅN Datetime2Test;

Resultat (med vertikal utdata):

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

Så vi får samma resultat – förutom att det har lagts fram med precision.

Men för att göra saker kristallklart, här är en tabell som jämför den faktiska sidfilens data med resultaten av CONVERT() operation.

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

Så vi kan tydligt se att sidfilen inte lagrar precisionen, men det gör det konverterade resultatet.

Jag markerade de faktiska datum- och tidsdelarna i rött. Jag tog också bort 0x prefix från de konverterade resultaten, så att endast de faktiska datum-/tidsdata visas (tillsammans med precisionen).

Observera också att hexadecimal är skiftlägesokänslig, så det faktum att den ena använder gemener och den andra använder versaler är inget problem.

Slutsats

När du konverterar en datetime2 värde till varbinary , behöver den en extra byte för att lagra precisionen. Den behöver precisionen för att tolka tidsdelen (eftersom denna lagras som ett tidsintervall, vars exakta värde beror på precisionen).

När den lagras i en databas anges precisionen en gång på kolumnnivå. Detta verkar logiskt, eftersom det inte finns något behov av att lagra en extra byte med varje rad om den kan specificeras på kolumnnivå. Så om du anger säg, datetime2(7) på kolumnnivå kommer varje enskild rad att vara datetime2(7) . Du behöver inte upprepa detta på varje rad.

Ronen Ariely kom till samma slutsats i sin artikel som nämns ovan.

Om du har en miljon rader med datetime2(7) värden, skulle lagring av precisionen med varje rad kräva 9 000 000 byte, jämfört med bara 8 000 001 om precisionen lagras en gång för hela kolumnen.

Detta stärker också datetime2 s fall när man jämför det med datetime . Även när du använder samma antal decimaler som datetime (dvs. 3), datetime2 datatyp använder mindre lagring (åtminstone när den lagras i en tabell med mer än en rad). Och det gör det med högre noggrannhet. En datumtid värde använder 8 byte, medan datetime2(3) använder 7 byte (plus 1 "precisionsbyte" som delas över alla rader).


  1. OPENXML med xmlns:dt

  2. SQL Server - Auto-inkrementering som tillåter UPDATE-satser

  3. WordPress förberett uttalande med IN()-villkor

  4. En fastighetsbyrådatamodell