sql >> Database >  >> RDS >> Sqlserver

Inzicht in 'datetime2' opslaggrootte in SQL Server

In dit artikel deel ik enkele observaties die ik heb gehad met betrekking tot de datetime2 de opslaggrootte van het gegevenstype in SQL Server. Misschien zal ik enkele punten verduidelijken over de werkelijke opslaggrootte die door dit gegevenstype wordt gebruikt wanneer het in een database wordt opgeslagen.

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

Sommige lijken elkaar tegen te spreken, en u ziet twee verschillende opslagcapaciteiten voor dezelfde waarde, afhankelijk van waar u kijkt.

Een datetime2 waarde kan een andere opslaggrootte tonen, afhankelijk van of het in een database is opgeslagen, als een datetime2 variabele, of geconverteerd naar varbinary .

Maar hier is een plausibele verklaring voor - het hangt af van waar de precisie wordt opgeslagen.

Tijdens mijn onderzoek naar dit onderwerp vond ik het diepgaande artikel van Ronen Ariely over hoe datetime2 is zeer informatief opgeslagen in het gegevensbestand en het heeft me ertoe aangezet een aantal soortgelijke tests uit te voeren in mijn eigen ontwikkelomgeving en deze hier te presenteren.

Documentatie van Microsoft

Laten we eerst eens kijken naar wat de officiële documentatie zegt.

Documentatie van Microsoft op de datetime2 datatype geeft aan dat de opslaggrootte als volgt is:

6 bytes voor precisie minder dan 3.
7 bytes voor precisie 3 of 4.
Alle andere precisie vereist 8 bytes.

Maar het kwalificeert de bovenstaande tabel met de volgende verklaring:

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.

Dus gezien de bovenstaande informatie, zou de voor de hand liggende conclusie zijn dat de tabel als volgt zou kunnen/(moeten?) worden geschreven:

7 bytes voor precisie minder dan 3.
8 bytes voor precisie 3 of 4.
Alle andere precisie vereist 9 bytes.

Op die manier hoeven ze het niet te kwalificeren met de extra informatie over precisie.

Maar zo eenvoudig is het niet.

Gegevens opgeslagen in een variabele

Laten we eerst een datetime2 . opslaan 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 datetime2(7) waarde:

DECLARE @d datetime2(7);
SET @d = '2025-05-21 10:15:30.1234567';
SELECT 
  @d AS 'Value',
  DATALENGTH(@d) AS 'Length in Bytes';

Resultaat

+-----------------------------+-------------------+
| Value                       | Length in Bytes   |
|-----------------------------+-------------------|
| 2025-05-21 10:15:30.1234567 | 8                 |
+-----------------------------+-------------------+

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

Dit lijkt in tegenspraak met wat Microsoft zegt over het nodig hebben van een extra byte om de precisie op te slaan. Om Microsoft te citeren:Dit maakt de maximale grootte van een datetime2 waarde 9 bytes – 1 byte slaat precisie op plus 8 bytes voor gegevensopslag met maximale precisie. .

Hoewel het waar is dat we 8 bytes lijken te krijgen voor gegevensopslag , lijken we de 1 byte te missen die wordt gebruikt om de precisie op te slaan.

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

Lengte in bytes na conversie naar 'varbinary'

Dit is wat er gebeurt als we onze datetime2 . converteren waarde naar 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 'Length in Bytes';

Resultaat

+----------------------+-------------------+
| Value                | Length in Bytes   |
|----------------------+-------------------|
| 0x0787A311FC553F480B | 9                 |
+----------------------+-------------------+

In dit geval krijgen we 9 bytes.

Dit is een hexadecimale weergave van de datetime2 waarde. De werkelijke datum-tijdwaarde (en de precisie ervan) is alles na de 0x . Elk paar hexadecimale tekens is een byte. Er zijn 9 paren, en dus 9 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 @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 'Length in Bytes';

Resultaat

+--------------------+-------------------+
| Value              | Length in Bytes   |
|--------------------+-------------------|
| 0x034B8233023F480B | 8                 |
+--------------------+-------------------+

We kunnen ook zien dat de lengte dienovereenkomstig wordt verminderd.

Dus in dit geval komen onze resultaten perfect overeen met de Microsoft-documentatie - er is een extra byte toegevoegd voor de precisie.

Veel ontwikkelaars gaan ervan uit dat SQL Server zijn datetime2 . op deze manier opslaat waarden in de database. Die veronderstelling blijkt echter onjuist te zijn.

Gegevens opgeslagen in een database

In dit voorbeeld maak ik een database aan die een tabel bevat met verschillende datetime2(n) kolommen. Ik gebruik dan COL_LENGTH() om de lengte van elke kolom in bytes te retourneren. Daarna voeg ik er waarden in, voordat ik DBCC PAGE . gebruik om de opslaggrootte te controleren die elke datetime2 waarde neemt het paginabestand in beslag.

Maak een database:

CREATE DATABASE Test;

Maak een tabel:

USE Test;

CREATE TABLE 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)
    );

In dit geval maak ik acht kolommen - één voor elke door de gebruiker gedefinieerde schaal die we kunnen gebruiken met datetime2(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 ( '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';  

Resultaat:

+------+------+------+------+------+------+------+------+
| d0   | d1   | d2   | d3   | d4   | d5   | d6   | d7   |
|------+------+------+------+------+------+------+------|
| 6    | 6    | 6    | 7    | 7    | 8    | 8    | 8    |
+------+------+------+------+------+------+------+------+

Dus nogmaals, we lijken niet de extra byte te krijgen die wordt gebruikt om de precisie op te slaan.

Gebruik DBCC PAGE om de opgeslagen gegevens te controleren

Laten we nu DBCC PAGE gebruiken om de werkelijke opslaggrootte te vinden van de gegevens die we in deze tabel opslaan.

Laten we eerst wat gegevens invoegen:

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;

Selecteer nu de gegevens (om het te controleren):

SELECT * FROM Datetime2Test;

Resultaat (met verticale uitvoer):

d0 | 2025-05-21 10:15:30
d1 | 2025-05-21 10:15:30.1
d2 | 2025-05-21 10:15:30.12
d3 | 2025-05-21 10:15:30.123
d4 | 2025-05-21 10:15:30.1235
d5 | 2025-05-21 10:15:30.12346
d6 | 2025-05-21 10:15:30.123457
d7 | 2025-05-21 10:15:30.1234567

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

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.Datetime2Test', 0);

Resultaat (met verticale uitvoer):

-[ RECORD 1 ]-------------------------
PageFID         | 1
PagePID         | 306
IAMFID          | NULL
IAMPID          | NULL
ObjectID        | 1205579333
IndexID         | 0
PartitionNumber | 1
PartitionID     | 72057594043039744
iam_chain_type  | In-row data
PageType        | 10
IndexLevel      | NULL
NextPageFID     | 0
NextPagePID     | 0
PrevPageFID     | 0
PrevPagePID     | 0
-[ RECORD 2 ]-------------------------
PageFID         | 1
PagePID         | 360
IAMFID          | 1
IAMPID          | 306
ObjectID        | 1205579333
IndexID         | 0
PartitionNumber | 1
PartitionID     | 72057594043039744
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 360 .

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

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

Dit levert veel data op, maar we zijn vooral geïnteresseerd in het volgende deel:

Slot 0 Column 1 Offset 0x4 Length 6 Length (physical) 6

d0 = 2025-05-21 10:15:30            

Slot 0 Column 2 Offset 0xa Length 6 Length (physical) 6

d1 = 2025-05-21 10:15:30.1          

Slot 0 Column 3 Offset 0x10 Length 6 Length (physical) 6

d2 = 2025-05-21 10:15:30.12         

Slot 0 Column 4 Offset 0x16 Length 7 Length (physical) 7

d3 = 2025-05-21 10:15:30.123   

Slot 0 Column 5 Offset 0x1d Length 7 Length (physical) 7

d4 = 2025-05-21 10:15:30.1235       

Slot 0 Column 6 Offset 0x24 Length 8 Length (physical) 8

d5 = 2025-05-21 10:15:30.12346      

Slot 0 Column 7 Offset 0x2c Length 8 Length (physical) 8

d6 = 2025-05-21 10:15:30.123457     

Slot 0 Column 8 Offset 0x34 Length 8 Length (physical) 8

d7 = 2025-05-21 10:15:30.1234567                                   

Het lijkt er dus op dat het de extra byte niet gebruikt voor precisie.

Maar laten we de feitelijke gegevens onderzoeken voordat we conclusies trekken.

De feitelijke gegevens worden in dit deel van het paginabestand opgeslagen:

Memory Dump @0x000000041883A060

0000000000000000:   10003c00 4290003f 480b95a2 053f480b d459383f  ..<.B..?H.•¢.?H.ÔY8?
0000000000000014:   480b4b82 33023f48 0bf31603 163f480b 7ae51edc  H.K‚3.?H.ó...?H.zå.Ü
0000000000000028:   003f480b c1f63499 083f480b 87a311fc 553f480b  .?H.Áö4..?H.‡£.üU?H.
000000000000003C:   080000                                        ...                                           ...   

Zoals je kunt zien, lijkt niets van dat alles op de resultaten die we zouden krijgen door de datetime2 te converteren waarde naar varbinary . Maar het komt aardig in de buurt.

Zo ziet het eruit als ik een paar dingen verwijder:

4290003f 480b95a2 053f480b d459383f
480b4b82 33023f48 0bf31603 163f480b 7ae51edc
003f480b c1f63499 083f480b 87a311fc 553f480b

De overige hexadecimale cijfers bevatten al onze datum- en tijdgegevens, maar niet de precisie . We moeten de spaties echter opnieuw rangschikken om de werkelijke waarden voor elke rij te krijgen.

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

4290003f480b 
95a2053f480b 
d459383f480b 
4b8233023f480b
f31603163f480b 
7ae51edc003f480b 
c1f63499083f480b 
87a311fc553f480b

Dat zijn de werkelijke hexadecimale waarden (minus de precisie ) die we zouden krijgen als we de datetime2 . zouden converteren waarde naar varbinary . Laten we voor de zekerheid gewoon doorgaan en precies dat doen:

SELECT 
  CONVERT(VARBINARY(10), d0) AS 'd0',
  CONVERT(VARBINARY(10), d1) AS 'd1',
  CONVERT(VARBINARY(10), d2) AS 'd2',
  CONVERT(VARBINARY(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) AS 'd7'
FROM Datetime2Test;

Resultaat (met verticale uitvoer):

d0 | 0x004290003F480B
d1 | 0x0195A2053F480B
d2 | 0x02D459383F480B
d3 | 0x034B8233023F480B
d4 | 0x04F31603163F480B
d5 | 0x057AE51EDC003F480B
d6 | 0x06C1F63499083F480B
d7 | 0x0787A311FC553F480B

We krijgen dus hetzelfde resultaat - behalve dat het met de precisie is toegevoegd.

Maar om alles glashelder te maken, is hier een tabel die de daadwerkelijke paginabestandsgegevens vergelijkt met de resultaten van de CONVERT() bediening.

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

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

Ik heb de feitelijke datum- en tijddelen in 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 converteren van een datetime2 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, aangezien het niet nodig is om bij elke rij een extra byte op te slaan als dit op kolomniveau kan worden opgegeven. Dus als je specificeert, zeg, datetime2(7) op kolomniveau, dan is elke rij datetime2(7) . Het is niet nodig om dit elke rij te herhalen.

Ronen Ariely kwam tot dezelfde conclusie in zijn hierboven genoemde artikel.

Als je een miljoen rijen hebt met datetime2(7) waarden, zou het opslaan van de precisie bij elke rij 9.000.000 bytes vereisen, vergeleken met slechts 8.000.001 als de precisie één keer wordt opgeslagen voor de hele kolom.

Dit versterkt ook de datetime2 's geval bij vergelijking met datetime . Zelfs bij gebruik van hetzelfde aantal decimalen als datetime (d.w.z. 3), de datetime2 gegevenstype gebruikt minder opslagruimte (tenminste wanneer opgeslagen in een tabel met meer dan één rij). En het doet dit met een hogere nauwkeurigheid. Een datetime waarde gebruikt 8 bytes, terwijl datetime2(3) gebruikt 7 bytes (plus 1 "precisie" byte die door alle rijen wordt gedeeld).


  1. Maak een "in plaats van" trigger in SQL Server

  2. UTF-8:Algemeen? bak? Unicode?

  3. WHERE IS NULL, IS NOT NULL of NO WHERE-clausule, afhankelijk van de parameterwaarde van SQL Server

  4. Vergelijk twee rijen en identificeer kolommen waarvan de waarden verschillend zijn