sql >> Database >  >> RDS >> Sqlserver

'Tijd'-opslaggrootte in SQL Server begrijpen

In dit artikel kijk ik naar de opslaggrootte van de tijd gegevenstype in SQL Server.

Ik kijk in het bijzonder naar het volgende:

  • Microsoft-documentatie
  • Gegevens opgeslagen in een variabele
    • Lengte in bytes met DATALENGTH()
    • Lengte in bytes met DATALENGTH() na conversie naar varbinary
  • Gegevens opgeslagen in een database
    • Lengte in bytes met COL_LENGTH()
    • Lengte in bytes met DBCC PAGE()

Documentatie van Microsoft

De officiële documentatie van Microsoft over de tijd datatype geeft aan dat de opslaggrootte tussen 3 en 5 bytes is, afhankelijk van de precisie die wordt gebruikt.

Dit gegevenstype zorgt voor door de gebruiker gedefinieerde precisie. U kunt time(n) . gebruiken om de precisie op te geven, waarbij n is een schaal tussen 0 en 7.

Dit zijn de gegevens die Microsoft voor de tijd presenteert gegevenstype:

Opgegeven schaal Resultaat (precisie, schaal) Kolomlengte (bytes) Nauwkeurigheid van fracties van seconden
tijd (16,7) 5 7
time(0) (8,0) 3 0-2
tijd(1) (10,1) 3 0-2
tijd(2) (11,2) 3 0-2
tijd(3) (12,3) 4 3-4
time(4) (13,4) 4 3-4
time(5) (14,5) 5 5-7
time(6) (15,6) 5 5-7
time(7) (16,7) 5 5-7

Voor de doeleinden van dit artikel ben ik vooral geïnteresseerd in de Kolomlengte (bytes) kolom. Dit vertelt ons hoeveel bytes er worden gebruikt om dit gegevenstype in een database op te slaan.

Vanuit het perspectief van een gebruiker is de tijd gegevenstype werkt op dezelfde manier als het tijdgedeelte van de datetime2 . Het heeft een door de gebruiker gedefinieerde precisie van fractionele seconden en accepteert een schaal van 0 tot 7.

De rest van dit artikel behandelt verschillende voorbeelden waarbij ik de opslaggrootte van tijd . retourneer waarden in verschillende contexten.

Gegevens opgeslagen in een variabele

Eerst sla ik een tijd op waarde in een variabele en controleer de opslaggrootte. Vervolgens converteer ik die waarde naar varbinary en controleer het opnieuw.

Lengte in bytes met DATALENGTH

Dit is wat er gebeurt als we de DATALENGTH() . gebruiken functie om het aantal bytes terug te geven dat is gebruikt voor een tijd(7) waarde:

DECLARE @t time(7);
SET @t = '10:15:30.1234567';
SELECT 
  @t AS 'Value',
  DATALENGTH(@t) AS 'Length in Bytes';

Resultaat

+------------------+-------------------+
| Value            | Length in Bytes   |
|------------------+-------------------|
| 10:15:30.1234567 | 5                 |
+------------------+-------------------+

De waarde in dit voorbeeld heeft de maximale schaal van 7 (omdat ik de variabele declareer als time(7) ), en het geeft een lengte van 5 bytes terug.

Dit is te verwachten, aangezien het overeenkomt met de opslagruimte die wordt beschreven in de tabel van Microsoft.

Als we de waarde echter converteren naar varbinary we krijgen een ander resultaat.

Lengte in bytes na conversie naar 'varbinary'

Sommige ontwikkelaars vinden het leuk om tijd te converteren of datetime2 variabelen naar varbinary omdat het meer representatief is voor hoe SQL Server het in de database opslaat. Hoewel dit gedeeltelijk waar is, zijn de resultaten niet precies hetzelfde als de opgeslagen waarde (daarover hieronder meer).

Dit is wat er gebeurt als we onze tijd omzetten waarde naar 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';

Resultaat

+----------------+-------------------+
| Value          | Length in Bytes   |
|----------------+-------------------|
| 0x0787A311FC55 | 6                 |
+----------------+-------------------+

In dit geval krijgen we 6 bytes. Onze waarde gebruikt nu 1 byte meer dan in de documentatie wordt vermeld.

Dat komt omdat het een extra byte nodig heeft om de precisie op te slaan.

Dit is een hexadecimale weergave van de tijd waarde. De werkelijke tijdwaarde (en zijn precisie) is alles na de 0x . Elk paar hexadecimale tekens is een byte. Er zijn 6 paren, en dus 6 bytes. Dit wordt bevestigd wanneer we DATALENGTH() . gebruiken om de lengte in bytes terug te geven.

In dit voorbeeld kunnen we zien dat de eerste byte 07 . is . Dit vertegenwoordigt de precisie (ik gebruikte een schaal van 7 en dat is dus wat hier wordt weergegeven).

Als ik de schaal verander, kunnen we zien dat de eerste byte verandert om overeen te komen met de schaal:

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';

Resultaat

+--------------+-------------------+
| Value        | Length in Bytes   |
|--------------+-------------------|
| 0x034B823302 | 5                 |
+--------------+-------------------+

We kunnen ook zien dat de lengte dienovereenkomstig wordt verminderd. Maar nogmaals, het is één byte meer dan wat de documentatie zegt dat het zou moeten gebruiken.

Hoewel de documentatie van Microsoft voor tijd vermeldt dit niet expliciet, de documentatie voor datetime2 vermeldt wel het volgende:

De eerste byte van een datetime2 value slaat de precisie van de waarde op, wat betekent dat de daadwerkelijke opslag vereist is voor een datetime2 waarde is de opslaggrootte aangegeven in de bovenstaande tabel plus 1 extra byte om de precisie op te slaan. Dit maakt de maximale grootte van een datetime2 waarde 9 bytes – 1 byte slaat precisie op plus 8 bytes voor gegevensopslag met maximale precisie.

En de datetime2 gegevenstype werkt op precies dezelfde manier met betrekking tot de bovenstaande voorbeelden. Met andere woorden, het rapporteert alleen de extra byte wanneer het wordt geconverteerd naar varbinary .

Dus de extra byte die in de Microsoft-documentatie wordt genoemd, lijkt ook van toepassing te zijn op tijd .

Echter, de werkelijke opslagruimte van uw tijd waarden zal op waar de gegevens zijn opgeslagen.

Gegevens opgeslagen in een database

Wanneer een databasekolom een ​​type tijd heeft , de precisie ervan wordt gespecificeerd op kolomniveau - niet op dataniveau. Met andere woorden, het wordt één keer opgegeven voor de hele kolom. Dit is logisch, want wanneer u een kolom definieert als time(7) , u weet dat alle rijen time(7) . zullen zijn . Het is niet nodig om kostbare bytes te gebruiken om dat feit op elke rij te herhalen.

Wanneer je een tijd bekijkt waarde zoals deze is opgeslagen in SQL Server, zult u zien dat deze hetzelfde is als de varbinary resultaat, maar zonder de precisie.

Hieronder staan ​​voorbeelden die laten zien hoe tijd waarden worden opgeslagen in SQL Server.

In deze voorbeelden maak ik een database met verschillende time(n) kolommen, en gebruik dan COL_LENGTH() om de lengte van elke kolom in bytes te retourneren. Ik voeg dan waarden in die kolommen in, voordat ik DBCC PAGE . gebruik om de opslaggrootte te controleren die elke tijd waarde neemt het paginabestand in beslag.

Maak een database:

CREATE DATABASE Test;

Maak een 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)
    );

In dit geval maak ik acht kolommen - één voor elke door de gebruiker gedefinieerde schaal die we kunnen gebruiken met time(n) .

Nu kunnen we de opslaggrootte van elke kolom controleren.

Lengte in bytes met COL_LENGTH()

Gebruik COL_LENGTH() om de lengte (in bytes) van elke kolom te controleren:

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';  

Resultaat:

+------+------+------+------+------+------+------+------+
| t0   | t1   | t2   | t3   | t4   | t5   | t6   | t7   |
|------+------+------+------+------+------+------+------|
| 3    | 3    | 3    | 4    | 4    | 5    | 5    | 5    |
+------+------+------+------+------+------+------+------+

Dus nogmaals, we krijgen hetzelfde resultaat als in de documentatie staat dat we zullen krijgen. Dit is te verwachten, want de documentatie vermeldt expliciet "Kolomlengte (bytes)", wat precies is wat we hier meten.

Denk eraan, dit is voor we voegen alle gegevens in. De kolommen zelf bepalen de precisie (en dus de opslaggrootte) van alle gegevens die worden ingevoegd – niet andersom.

Gebruik DBCC PAGE om de opgeslagen gegevens te controleren

Laten we nu gegevens invoegen en vervolgens DBCC PAGE . gebruiken om de werkelijke opslaggrootte te vinden van de gegevens die we in elke kolom opslaan.

Gegevens invoegen:

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;

Selecteer nu de gegevens (om het te controleren):

SELECT * FROM TimeTest;

Resultaat (met verticale uitvoer):

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

Zoals verwacht gebruiken de waarden de precisie die eerder is gespecificeerd op kolomniveau.

Merk op dat mijn systeem volgnullen weergeeft. De jouwe kan dit wel of niet doen. Hoe dan ook, dit heeft geen invloed op de werkelijke precisie of nauwkeurigheid.

Nu, voordat we DBCC PAGE() gebruiken , moeten we weten welke PagePID eraan moet worden doorgegeven. We kunnen DBCC IND() . gebruiken om dat te vinden.

Vind de PagePID:

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

Resultaat (met verticale uitvoer):

-[ 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

Dit levert twee records op. We zijn geïnteresseerd in het PageType van 1 (het 2e record). We willen de PagePID van dat record. In dit geval is de PagePID 384 .

Nu kunnen we die PagePID nemen en gebruiken in het volgende:

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

Op dit moment zijn we vooral geïnteresseerd in het volgende deel:

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                                                                      

Dus we krijgen hetzelfde resultaat als de documentatie aangeeft. Dit zou erop kunnen wijzen dat de precisie niet wordt opgeslagen met de waarden.

We kunnen dat bevestigen door de feitelijke gegevens te onderzoeken.

De actuele tijdwaarden worden in dit deel van het paginabestand opgeslagen:

Memory Dump @0x0000000423ADA060

0000000000000000:   10002400 42900095 a205d459 384b8233 02f31603  ..$.B..•¢.ÔY8K‚3.ó..
0000000000000014:   167ae51e dc00c1f6 34990887 a311fc55 080000    .zå.Ü.Áö4..‡£.üU...

We kunnen de werkelijke tijdwaarden extraheren door een paar dingen te verwijderen. Eenmaal verwijderd, blijft het volgende over:

42900095 a205d459 384b8233 02f31603
167ae51e dc00c1f6 34990887 a311fc55

Deze hexadecimale cijfers bevatten al onze tijdgegevens, maar niet de precisie . Ze zijn echter gerangschikt in brokken van 4 bytes, dus we moeten de spaties herschikken om de individuele waarden te krijgen.

Hier is het eindresultaat. Ik heb elke datum/tijd-waarde op een nieuwe regel geplaatst voor een betere leesbaarheid.

429000
95a205
d45938
4b823302
f3160316
7ae51edc00
c1f6349908
87a311fc55

Dat zijn de werkelijke hexadecimale waarden (minus de precisie ) die we zouden krijgen als we de tijd . zouden omrekenen waarde naar varbinary . Zoals dit:

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;

Resultaat (met verticale uitvoer):

t0 | 0x00429000
t1 | 0x0195A205
t2 | 0x02D45938
t3 | 0x034B823302
t4 | 0x04F3160316
t5 | 0x057AE51EDC00
t6 | 0x06C1F6349908
t7 | 0x0787A311FC55

Die zoekopdracht levert hetzelfde resultaat op, behalve dat elke waarde met de precisie is toegevoegd.

Hier is een tabel die de daadwerkelijke paginabestandsgegevens vergelijkt met de resultaten van de CONVERT() bediening.

Paginabestandsgegevens CONVERTEREN() gegevens
429000 00429000
95a205 0195A205
d45938 02D45938
4b823302 034B823302
f3160316 04F3160316
7ae51edc00 057AE51EDC00
c1f6349908 06C1F6349908
87a311fc55 0787A311FC55

We kunnen dus zien dat het paginabestand de precisie niet opslaat, maar het geconverteerde resultaat wel.

Ik heb de feitelijke datum- en tijddelen in het rood gemarkeerd. Ik heb ook de 0x . verwijderd prefix van de geconverteerde resultaten, zodat alleen de werkelijke datum/tijd-gegevens worden weergegeven (samen met de precisie).

Houd er ook rekening mee dat hexadecimaal niet hoofdlettergevoelig is, dus het feit dat de ene kleine letters en de andere hoofdletters gebruikt, is geen probleem.

Conclusie

Bij het omrekenen van een tijd waarde naar varbinary , heeft het een extra byte nodig om de precisie op te slaan. Het heeft de precisie nodig om het tijdsgedeelte te interpreteren (omdat dit wordt opgeslagen als een tijdsinterval, waarvan de exacte waarde afhangt van de precisie).

Bij opslag in een database wordt de precisie eenmaal gespecificeerd op kolomniveau. Dit lijkt logisch, omdat het niet nodig is om de precisie aan elke rij toe te voegen als alle rijen toch dezelfde precisie hebben. Dat zou een extra byte voor elke rij vereisen, wat de opslagvereisten onnodig zou verhogen.


  1. Oracle RAC en sequenties

  2. SQL Server Collection Inventory Script -2

  3. Hoe voeg ik indexen toe aan MySQL-tabellen?

  4. Hoe haal ik het eerste en laatste record van een gegroepeerd record op in een MySQL-query met geaggregeerde functies?