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
- Längd i byte med
- Data lagrad i en databas
- Längd i byte med
COL_LENGTH()
- Längd i byte med
DBCC PAGE()
- Längd i byte med
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änderDATALENGTH()
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änderDBCC 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.1234567Som 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ändaDBCC 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 | 0Detta 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.713Så 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> ...preSom 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 8bDe å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 | 0x0787A311FC553F480BSå 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).