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

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

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

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..•¢.ÔY8K‚3.ó..
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.


  1. Indsæt i fra CTE

  2. Installer MySQL på Ubuntu 14.04

  3. postgresql:datatype for md5-meddelelsessammendrag?

  4. MySQL-konverteringsfunktion