I denne artikel ser jeg på lagerstørrelsen for tiden datatype i SQL Server.
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
Microsofts dokumentation
Microsofts officielle dokumentation på tiden datatype angiver, at dens lagerstørrelse er mellem 3 og 5 bytes, afhængigt af den anvendte præcision.
Denne datatype giver mulighed for brugerdefineret præcision. Du kan bruge tid(n) for at angive præcisionen, hvor n er en skala mellem 0 og 7.
Her er de data, som Microsoft præsenterer for gangen datatype:
Specificeret skala | Resultat (præcision, skala) | Kolonnelængde (bytes) | Brøksekunders præcision |
---|---|---|---|
tid | (16,7) | 5 | 7 |
tid(0) | (8,0) | 3 | 0-2 |
tid(1) | (10,1) | 3 | 0-2 |
tid(2) | (11,2) | 3 | 0-2 |
tid(3) | (12,3) | 4 | 3-4 |
tid(4) | (13,4) | 4 | 3-4 |
tid(5) | (14,5) | 5 | 5-7 |
tid(6) | (15,6) | 5 | 5-7 |
tid(7) | (16,7) | 5 | 5-7 |
I forbindelse med denne artikel er jeg primært interesseret i Kolonnelængden (bytes) kolonne. Dette fortæller os, hvor mange bytes der bruges til at gemme denne datatype i en database.
Fra en brugers perspektiv er tiden datatypen fungerer på samme måde som tidsdelen af datetime2 . Den har en brugerdefineret brøksekunders præcision, og den accepterer en skala fra 0 til 7.
Resten af denne artikel gennemgår forskellige eksempler, hvor jeg returnerer lagerstørrelsen tid værdier i forskellige sammenhænge.
Data gemt i en variabel
Først vil jeg gemme en tid 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 i en tid(7) værdi:
DECLARE @t time(7); SET @t = '10:15:30.1234567'; SELECT @t AS 'Value', DATALENGTH(@t) AS 'Length in Bytes';
Resultat
+------------------+-------------------+ | Value | Length in Bytes | |------------------+-------------------| | 10:15:30.1234567 | 5 | +------------------+-------------------+
Værdien i dette eksempel har den maksimale skala på 7 (fordi jeg erklærer variablen som tid(7) ), og det returnerer en længde på 5 bytes.
Dette kan forventes, da det matcher lagerstørrelsen skitseret i Microsofts tabel.
Men hvis vi konverterer værdien til varbinary vi får et andet resultat.
Længde i bytes efter konvertering til 'varbinary'
Nogle udviklere kan lide at konvertere tid eller datetime2 variabler til variable fordi det er mere repræsentativt for, hvordan SQL Server gemmer det i databasen. Selvom dette er delvist sandt, er resultaterne ikke nøjagtigt de samme som den lagrede værdi (mere om det nedenfor).
Her er, hvad der sker, hvis vi konverterer vores tid værdi til varbinary :
DECLARE @t time(7); SET @t = '10:15:30.1234567'; SELECT CONVERT(VARBINARY(16), @t) AS 'Value', DATALENGTH(CONVERT(VARBINARY(16), @t)) AS 'Length in Bytes';
Resultat
+----------------+-------------------+ | Value | Length in Bytes | |----------------+-------------------| | 0x0787A311FC55 | 6 | +----------------+-------------------+
I dette tilfælde får vi 6 bytes. Vores værdi bruger nu 1 byte mere end det, der er angivet i dokumentationen.
Det er fordi den har brug for en ekstra byte for at gemme præcisionen.
Dette er en hexadecimal repræsentation af tiden værdi. Den faktiske tidsværdi (og dens præcision) er alt efter 0x
. Hvert par hex-tegn er en byte. Der er 6 par og derfor 6 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 @t time(3); SET @t = '10:15:30.1234567'; SELECT CONVERT(VARBINARY(16), @t) AS 'Value', DATALENGTH(CONVERT(VARBINARY(16), @t)) AS 'Length in Bytes';
Resultat
+--------------+-------------------+ | Value | Length in Bytes | |--------------+-------------------| | 0x034B823302 | 5 | +--------------+-------------------+
Vi kan også se, at længden reduceres tilsvarende. Men igen, det er en byte mere end hvad dokumentationen siger, den skal bruge.
Selvom Microsofts dokumentation for tid nævner ikke eksplicit dette, dokumentationen for datetime2 står der følgende:
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.
Og datetime2 datatypen fungerer på nøjagtig samme måde med hensyn til ovenstående eksempler. Med andre ord rapporterer den kun den ekstra byte, når den er konverteret til varbinary .
Så den ekstra byte, der er nævnt i Microsoft-dokumentationen, ser også ud til at gælde for tid .
Men den faktiske lagerstørrelse på din tid værdier afhænger af, hvor dataene er gemt.
Data gemt i en database
Når en databasekolonne har en type tid , dens præcision er angivet på kolonneniveau – ikke på dataniveau. Med andre ord er det angivet én gang for hele kolonnen. Dette giver mening, for når du definerer en kolonne som tid(7) , du ved, at alle rækker vil være tid(7) . Ingen grund til at bruge dyrebare bytes, der gentager dette faktum på hver række.
Når du undersøger en tid værdi, som den er gemt i SQL Server, vil du se, at den er den samme som varbinary resultat, men uden præcisionen.
Nedenfor er eksempler, der viser, hvordan tid værdier gemmes i SQL Server.
I disse eksempler opretter jeg en database med forskellige tid(n) kolonner, og brug derefter COL_LENGTH()
for at returnere hver kolonnes længde i bytes. Jeg indsætter derefter værdier i disse kolonner, før jeg bruger DBCC PAGE
for at kontrollere lagerstørrelsen, der hver gang værdi optager på sidefilen.
Opret en database:
CREATE DATABASE Test;
Opret en tabel:
USE Test; CREATE TABLE TimeTest ( t0 time(0), t1 time(1), t2 time(2), t3 time(3), t4 time(4), t5 time(5), t6 time(6), t7 time(7) );
I dette tilfælde opretter jeg otte kolonner – en for hver brugerdefineret skala, som vi kan bruge med tid(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 ( 'TimeTest' , 't0' ) AS 't0', COL_LENGTH ( 'TimeTest' , 't1' ) AS 't1', COL_LENGTH ( 'TimeTest' , 't2' ) AS 't2', COL_LENGTH ( 'TimeTest' , 't3' ) AS 't3', COL_LENGTH ( 'TimeTest' , 't4' ) AS 't4', COL_LENGTH ( 'TimeTest' , 't5' ) AS 't5', COL_LENGTH ( 'TimeTest' , 't6' ) AS 't6', COL_LENGTH ( 'TimeTest' , 't7' ) AS 't7';
Resultat:
+------+------+------+------+------+------+------+------+ | t0 | t1 | t2 | t3 | t4 | t5 | t6 | t7 | |------+------+------+------+------+------+------+------| | 3 | 3 | 3 | 4 | 4 | 5 | 5 | 5 | +------+------+------+------+------+------+------+------+
Så endnu en gang får vi det samme resultat, som det fremgår af dokumentationen, at vi får. Dette kan forventes, fordi dokumentationen udtrykkeligt angiver "Kolonnelængde (bytes)", hvilket er præcis, hvad vi måler her.
Husk, dette er før vi indsætter alle data. Kolonnerne bestemmer selv præcisionen (og derfor lagerstørrelsen) af alle data, der indsættes – ikke omvendt.
Brug DBCC PAGE til at kontrollere de lagrede data
Lad os nu indsætte data og derefter bruge DBCC PAGE
for at finde den faktiske lagerstørrelse for de data, vi gemmer i hver kolonne.
Indsæt data:
DECLARE @t time(7) = '10:15:30.1234567'; INSERT INTO TimeTest ( t0, t1, t2, t3, t4, t5, t6, t7 ) SELECT @t, @t, @t, @t, @t, @t, @t, @t;
Vælg nu dataene (bare for at kontrollere dem):
SELECT * FROM TimeTest;
Resultat (ved hjælp af lodret output):
t0 | 10:15:30 t1 | 10:15:30.1000000 t2 | 10:15:30.1200000 t3 | 10:15:30.1230000 t4 | 10:15:30.1235000 t5 | 10:15:30.1234600 t6 | 10:15:30.1234570 t7 | 10:15:30.1234567
Som forventet bruger værdierne den præcision, der tidligere er angivet på kolonneniveau.
Bemærk, at mit system viser efterfølgende nuller. Dine gør det måske eller ikke. Uanset hvad påvirker dette ikke den faktiske præcision eller nøjagtighed.
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.TimeTest', 0);
Resultat (ved hjælp af lodret output):
-[ RECORD 1 ]------------------------- PageFID | 1 PagePID | 308 IAMFID | NULL IAMPID | NULL ObjectID | 1541580530 IndexID | 0 PartitionNumber | 1 PartitionID | 72057594043236352 iam_chain_type | In-row data PageType | 10 IndexLevel | NULL NextPageFID | 0 NextPagePID | 0 PrevPageFID | 0 PrevPagePID | 0 -[ RECORD 2 ]------------------------- PageFID | 1 PagePID | 384 IAMFID | 1 IAMPID | 308 ObjectID | 1541580530 IndexID | 0 PartitionNumber | 1 PartitionID | 72057594043236352 iam_chain_type | In-row data PageType | 1 IndexLevel | 0 NextPageFID | 0 NextPagePID | 0 PrevPageFID | 0 PrevPagePID | 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 384 .
Nu kan vi tage det PagePID og bruge det i følgende:
DBCC TRACEON(3604, -1); DBCC PAGE(Test, 1, 384, 3);
Lige nu er vi primært interesserede i følgende del:
Slot 0 Column 1 Offset 0x4 Length 3 Length (physical) 3 t0 = 10:15:30 Slot 0 Column 2 Offset 0x7 Length 3 Length (physical) 3 t1 = 10:15:30.1 Slot 0 Column 3 Offset 0xa Length 3 Length (physical) 3 t2 = 10:15:30.12 Slot 0 Column 4 Offset 0xd Length 4 Length (physical) 4 t3 = 10:15:30.123 Slot 0 Column 5 Offset 0x11 Length 4 Length (physical) 4 t4 = 10:15:30.1235 Slot 0 Column 6 Offset 0x15 Length 5 Length (physical) 5 t5 = 10:15:30.12346 Slot 0 Column 7 Offset 0x1a Length 5 Length (physical) 5 t6 = 10:15:30.123457 Slot 0 Column 8 Offset 0x1f Length 5 Length (physical) 5 t7 = 10:15:30.1234567
Så vi får samme resultat som dokumentationen siger. Dette tyder på, at præcisionen ikke bliver gemt med værdierne.
Det kan vi bekræfte ved at undersøge de faktiske data.
De faktiske tidsværdier gemmes i denne del af sidefilen:
Memory Dump @0x0000000423ADA060 0000000000000000: 10002400 42900095 a205d459 384b8233 02f31603 ..$.B..¢.ÔY8K3.ó.. 0000000000000014: 167ae51e dc00c1f6 34990887 a311fc55 080000 .zå.Ü.Áö4..£.üU...
Vi kan udtrække de faktiske tidsværdier ved at fjerne et par ting. Når den er fjernet, forbliver følgende:
42900095 a205d459 384b8233 02f31603 167ae51e dc00c1f6 34990887 a311fc55
Disse hex-cifre indeholder alle vores tidsdata, men ikke præcisionen . De er dog arrangeret i 4 byte bidder, så vi skal omarrangere mellemrummene for at få de individuelle værdier.
Her er slutresultatet. Jeg har placeret hver dato/tidsværdi på en ny linje for bedre læsbarhed.
429000 95a205 d45938 4b823302 f3160316 7ae51edc00 c1f6349908 87a311fc55
Det er de faktiske hexadecimale værdier (minus præcisionen ), som vi ville få, hvis vi konverterede tiden værdi til varbinary . Sådan:
SELECT CONVERT(VARBINARY(16), t0) AS 't0', CONVERT(VARBINARY(16), t1) AS 't1', CONVERT(VARBINARY(16), t2) AS 't2', CONVERT(VARBINARY(16), t3) AS 't3', CONVERT(VARBINARY(16), t4) AS 't4', CONVERT(VARBINARY(16), t5) AS 't5', CONVERT(VARBINARY(16), t6) AS 't6', CONVERT(VARBINARY(16), t7) AS 't7' FROM TimeTest;
Resultat (ved hjælp af lodret output):
t0 | 0x00429000 t1 | 0x0195A205 t2 | 0x02D45938 t3 | 0x034B823302 t4 | 0x04F3160316 t5 | 0x057AE51EDC00 t6 | 0x06C1F6349908 t7 | 0x0787A311FC55
Denne forespørgsel giver det samme resultat – bortset fra at hver værdi er blevet sat foran med præcisionen.
Her er en tabel, der sammenligner de faktiske sidefildata med resultaterne af CONVERT()
operation.
Sidefildata | CONVERT() data |
---|---|
429000 | 00429000 |
95a205 | 0195A205 |
d45938 | 02D45938 |
4b823302 | 034B823302 |
f3160316 | 04F3160316 |
7ae51edc00 | 057AE51EDC00 |
c1f6349908 | 06C1F6349908 |
87a311fc55 | 0787A311FC55 |
Så vi kan 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 tid 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 tilføje præcisionen til hver række, når alle rækker alligevel har samme præcision. Det ville kræve en ekstra byte for hver række, hvilket ville øge lagerkravene unødigt.