sql >> Databáze >  >> RDS >> Sqlserver

Vysvětlení velikosti úložiště „datetimeoffset“ na serveru SQL

V tomto článku se podívám na to, jak datetimeoffset datový typ je uložen na serveru SQL Server a jak můžete získat různé výsledky hlášené velikosti úložiště v závislosti na tom, co s ním děláte.

Je to podobné tomu, co jsem udělal s datetime2 datový typ.

Konkrétně se dívám na následující:

  • Dokumentace společnosti Microsoft
  • Data uložená v proměnné
    • Délka v bajtech pomocí DATALENGTH()
    • Délka v bajtech pomocí DATALENGTH() po převodu na varbinary
  • Data uložená v databázi
    • Délka v bajtech pomocí COL_LENGTH()
    • Délka v bajtech pomocí DBCC PAGE()

Dokumentace společnosti Microsoft

Oficiální dokumentace společnosti Microsoft o datetimeoffset datový typ označuje, že velikost jeho úložiště je mezi 8 a 10 bajty, v závislosti na použité přesnosti.

Podobné jako datetime2(n) , můžete použít datetimeoffset(n) k určení přesnosti, kde n je stupnice mezi 0 a 7.

Zde jsou data, která společnost Microsoft prezentuje pro tento typ dat:

Zadané měřítko Výsledek (přesnost, měřítko) Délka sloupce (bajty) Přesnost na zlomky sekund
datetimeoffset (34,7) 10 7
datetimeoffset(0) (26,0) 8 0–2
datetimeoffset(1) (28,1) 8 0–2
datetimeoffset(2) (29,2) 8 0–2
datetimeoffset(3) (30,3) 9 3–4
datetimeoffset(4) (31,4) 9 3–4
datetimeoffset(5) (32,5) 10 5–7
datetimeoffset(6) (33,6) 10 5–7
datetimeoffset(7) (34,7) 10 5–7

Pro účely tohoto článku mě zajímá hlavně délka sloupce (bajty) sloupec. To nám říká, kolik bajtů se používá k uložení tohoto datového typu v databázi.

Hlavním důvodem, proč jsem chtěl napsat tento článek (a provést níže uvedené experimenty), je to, že dokumentace společnosti Microsoft nevysvětluje, že se pro přesnost používá bajt navíc (jak je tomu v dokumentaci pro datetime2 silný> datový typ). V dokumentaci pro datetime2 , uvádí:

První bajt datetime2 value ukládá přesnost hodnoty, což znamená skutečné uložení požadované pro datetime2 hodnota je velikost úložiště uvedená v tabulce výše plus 1 další bajt pro uložení přesnosti. Tím je maximální velikost datetime2 hodnota 9 bajtů – 1 bajt ukládá přesnost plus 8 bajtů pro ukládání dat s maximální přesností.

Ale dokumentace pro datetimeoffset nezahrnuje tento text a ani čas dokumentaci.

To mě přimělo přemýšlet, zda existuje rozdíl mezi tím, jak tyto datové typy ukládají své hodnoty. Logic mi řekl, že by měly fungovat stejně, protože všechny mají uživatelsky definovanou přesnost, ale chtěl jsem to zjistit.

Krátká odpověď je ano, datetimeoffset zdá se, že funguje stejně jako datetime2 (s ohledem na bajt navíc), i když to jako takové není zdokumentováno.

Zbytek článku prochází různými příklady, kde vracím velikost úložiště datetimeoffset hodnoty v různých kontextech.

Data uložená v proměnné

Uložme datetimeoffset hodnotu v proměnné a zkontrolujte její velikost úložiště. Potom tuto hodnotu převedu na varbinary a znovu to zkontrolujte.

Délka v bajtech pomocí DATALENGTH

Zde je to, co se stane, když použijeme DATALENGTH() funkce, která vrátí počet bajtů použitých pro datetimeoffset(7) hodnota:

DECLARE @d datetimeoffset(7);SET @d ='2025-05-21 10:15:30.1234567 +07:00';SELECT @d AS 'Value', DATALENGTH(@d) AS 'Délka v bytech ';

Výsledek

+------------------------------------+--------- ----------+| Hodnota | Délka v bajtech ||------------------------------------+--------- -----------|| 2025-05-21 10:15:30.1234567 +07:00 | 10 |+------------------------------------+---------- ---------+

Hodnota v tomto příkladu má maximální měřítko 7 (protože proměnnou deklaruji jako datetimeoffset(7) ) a vrátí délku 10 bajtů.

Žádné překvapení, toto je přesná velikost úložiště, kterou by měla mít dokumentace společnosti Microsoft.

Pokud však hodnotu převedeme na varbinary dostaneme jiný výsledek.

Délka v bajtech po převodu na ‚varbinary‘

Někteří vývojáři rádi převádějí datetimeoffset a datetime2 proměnné na varbinary , protože to více reprezentuje způsob, jakým jej SQL Server ukládá do databáze. I když je to částečně pravda, výsledky nejsou úplně stejné jako uložená hodnota (jak uvidíte později).

Co se stane, když převedeme náš datetimeoffset hodnotu na varbinary :

DECLARE @d datetimeoffset(7);SET @d ='2025-05-21 10:15:30.1234567 +07:00';SELECT CONVERT(VARBINARY(16), @d) AS 'Value', DATALENGTH( CONVERT(VARBINARY(16); @d)) AS 'Délka v bytech';

Výsledek

+--------------------------+------------------- +| Hodnota | Délka v bajtech ||--------------------------+------------------- -|| 0x0787CBB24F1B3F480BA401 | 11 |+--------------------------+-------------------+ 

V tomto případě dostaneme 11 bajtů.

Toto je hexadecimální reprezentace datetimeoffset hodnota. Skutečná hodnota posunutí data a času (a její přesnost) je vše za 0x . Každý pár hexadecimálních znaků je bajt. Existuje 11 párů, a tedy 11 bajtů. To se potvrdí, když použijeme DATALENGTH() vrátíte délku v bajtech.

V tomto příkladu vidíme, že první bajt je 07 . To představuje přesnost (použil jsem měřítko 7 a tak je to zde zobrazeno).

Pokud změním měřítko, uvidíme, že se první bajt změní tak, aby odpovídal měřítku:

DECLARE @d datetimeoffset(3);SET @d ='2025-05-21 10:15:30.1234567 +07:00';SELECT CONVERT(VARBINARY(16), @d) AS 'Value', DATALENGTH( CONVERT(VARBINARY(16); @d)) AS 'Délka v bytech';

Výsledek

+------------------------+-------------------+| Hodnota | Délka v bajtech ||------------------------+-------------------| | 0x03CBFCB2003F480BA401 | 10 |+------------------------+-------------------+ 

Můžeme také vidět, že délka se odpovídajícím způsobem zkrátí.

Data uložená v databázi

V tomto příkladu vytvořím databázi s různými datetimeoffset(n) sloupce a poté použijte COL_LENGTH() vrátit délku každého sloupce v bajtech. Poté vložím hodnoty do sloupců před použitím DBCC PAGE zkontrolovat velikost úložiště, o kterou každý datetimeoffset value zabírá soubor stránky.

Vytvořte databázi:

Test CREATE DATABASE;

Vytvořte tabulku:

USE Test;CREATE TABLE DatetimeoffsetTest ( d0 datetimeoffset(0), d1 datetimeoffset(1), d2 datetimeoffset(2), d3 datetimeoffset(3), d4 datetimeoffset(4), d5 datetimeoffset(5), d6 datetimeoffset(6 ), d7 datetimeoffset(7) );

V tomto případě vytvořím osm sloupců – jeden pro každé uživatelem definované měřítko, které můžeme použít s datetimeoffset(n) .

Nyní můžeme zkontrolovat velikost úložiště každého sloupce.

Délka v bajtech pomocí COL_LENGTH()

Použijte COL_LENGTH() pro kontrolu délky (v bajtech) každého sloupce:

SELECT COL_LENGTH ( 'DatetimeoffsetTest' , 'd0' ) AS 'd0', COL_LENGTH ( 'DatetimeoffsetTest' , 'd1' ) AS 'd1', COL_LENGTH ( 'DatetimeoffsetTest' , 'd2' ) AS 'd2' ) AS ( 'DatetimeoffsetTest' , 'd3' ) AS 'd3', COL_LENGTH ( 'DatetimeoffsetTest' , 'd4' ) AS 'd4', COL_LENGTH ( 'DatetimeoffsetTest' , 'd5' ) AS 'd5', offset'COLDLEGTtime 'd6' ) AS 'd6', COL_LENGTH ( 'DatetimeoffsetTest' , 'd7' ) AS 'd7'; 

Výsledek:

+------+------+------+------+------+------+---- --+------+| d0 | d1 | d2 | d3 | d4 | d5 | d6 | d7 ||------+------+------+------+------+------+------ -+------|| 8 | 8 | 8 | 9 | 9 | 10 | 10 | 10 |+------+------+------+------+------+------+----- -+------+

Takže ještě jednou dostaneme stejný výsledek, jak uvádí dokumentace, že dostaneme. To se dalo očekávat, protože dokumentace výslovně uvádí „délku sloupce (bajty)“, což je přesně to, co zde měříme.

Ke kontrole uložených dat použijte DBCC PAGE

Nyní použijeme DBCC PAGE zjistit skutečnou velikost úložiště dat, která ukládáme do této tabulky.

Nejprve vložíme nějaká data:

DECLARE @d datetimeoffset(7) ='2025-05-21 10:15:30.1234567 +07:00';INSERT INTO DatetimeoffsetTest ( d0, d1, d2, d3, d4, d5, d6, SELECT @ d7) d, @d, @d, @d, @d, @d, @d, @d;

Nyní vyberte data (jen pro kontrolu):

SELECT * FROM DatetimeoffsetTest;

Výsledek (při použití vertikálního výstupu):

d0 | 2025-05-21 10:15:30.0000000 +07:00d1 | 2025-05-21 10:15:30.1000000 +07:00d2 | 2025-05-21 10:15:30.1200000 +07:00d3 | 2025-05-21 10:15:30.1230000 +07:00d4 | 2025-05-21 10:15:30.1235000 +07:00d5 | 2025-05-21 10:15:30.1234600 +07:00d6 | 2025-05-21 10:15:30.1234570 +07:00d7 | 2025-05-21 10:15:30,1234567 +07:00

Podle očekávání používají hodnoty přesnost, která byla dříve zadána na úrovni sloupců.

Všimněte si, že můj systém zobrazuje koncové nuly. Vaše to může, ale nemusí. Bez ohledu na to to nemá vliv na skutečnou přesnost nebo přesnost.

Nyní, než použijeme DBCC PAGE() , potřebujeme vědět, které PagePID mu předat. Můžeme použít DBCC IND() najít to.

Najděte PagePID:

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

Výsledek (při použití vertikálního výstupu):

-[ ZÁZNAM 1 ]-------------------------PageFID | 1PagePID | 307IAMFID | NULLIAMPID | NULLObjectID | 1525580473ID indexu | 0Číslo oddílu | 1ID oddílu | 72057594043170816iam_chain_type | DataPageType v řádku | 10Úroveň indexu | NULLNextPageFID | 0DalšíPagePID | 0PrevPageFID | 0PrevPagePID | 0-[ ZÁZNAM 2 ]-------------------------PageFID | 1PagePID | 376IAMFID | 1IAMPID | 307ObjectID | 1525580473ID indexu | 0Číslo oddílu | 1ID oddílu | 72057594043170816iam_chain_type | DataPageType v řádku | 1Úroveň indexu | 0DalšíPageFID | 0DalšíPagePID | 0PrevPageFID | 0PrevPagePID | 0

Tím se vrátí dva záznamy. Zajímá nás PageType 1 (druhý záznam). Chceme PagePID z tohoto záznamu. V tomto případě je PagePID 376 .

Nyní můžeme vzít toto PagePID a použít jej v následujícím:

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

Právě teď nás zajímá hlavně následující část:

Sloup 0 Posun sloupce 1 0x4 Délka 8 Délka (fyzická) 8d0 =2025-05-21 10:15:30 +07:00 Slot 0 Posun sloupce 2 0xc Délka 8 Délka (fyzická) 8d1 =2025-05-21 10:15:30.1 +07:00 Slot 0 Sloupec 3 Odsazení 0x14 Délka 8 Délka (fyzická) 8d2 =2025-05-21 10:15:30.12 +07:00 Slot 0 Sloupec 4 Odsazení 0x1c Délka 9 Délka (fyzická délka) 9d =2025-05-21 10:15:30.123 +07:00 Slot 0 Sloupec 5 Offset 0x25 Délka 9 Délka (fyzická) 9d4 =2025-05-21 10:15:30.1235 +07:00 ColumSlot 1x Offset 020e Délka (fyzická) 10d5 =2025-05-21 10:15:30.12346 +07:00 Blok 0 Sloupec 7 Odsazení 0x38 Délka 10 Délka (fyzická) 10d6 =2025-05-21 10:15:30,10 Blok Posun sloupce 8 0x42 Délka 10 Délka (fyzická) 10d7 =2025-05-21 10:15:30,1234567 +07:00 

Takže dostaneme opět stejný výsledek. Přesně jak uvádí dokumentace.

Když už jsme tady, podívejme se na data – skutečné hodnoty data a času, jak jsou uloženy na serveru SQL.

Skutečné hodnoty jsou uloženy v této části souboru stránky:

 Výpis z paměti @0x000000041951A0600000000000000000:10004C00 D22D003F 480BA401 35CA013F 480BA401 ..L.ò-? H.K.óßy0000000000000028:063F480B A4017ABF EA45003F 480BA401 C17A2BBB. ..

To ještě obsahuje pár kousků navíc. Smažeme několik věcí, aby zůstaly pouze naše hodnoty data a času:

D22D003F 480BA401 35CA013F 480BA40114E6113F 480BA401 CBFCB200 3F480BA4 01F3DFFD063F480B A4017ABF EA45003F 480BA401 C17A2BBBBBI

Zbývající hexadecimální číslice obsahují všechny naše údaje o datu a čase, ale ne přesnost . Jsou však uspořádány do 4bajtových bloků, takže musíme změnit uspořádání mezer, abychom získali jednotlivé hodnoty.

Zde je konečný výsledek. Pro lepší čitelnost jsem každou hodnotu data/času umístil na nový řádek.

d22d003f480ba401 35ca013f480ba40114e6113f480ba401 cbfcb2003f480ba401f3dffd063f480ba4017abfea45003f4870ba201 cbb40ba401 

Toto jsou skutečné hexadecimální hodnoty (minus přesnost ), které bychom získali, kdybychom převedli datetimeoffset hodnotu na varbinary . Takhle:

SELECT CONVERT(VARBINARY(16), d0) AS 'd0', CONVERT(VARBINARY(16), d1) AS 'd1', CONVERT(VARBINARY(16), d2) AS 'd2', CONVERT(VARBINARY( 16), d3) AS 'd3', CONVERT(VARBINARY(16), d4) AS 'd4', CONVERT(VARBINARY(16), d5) AS 'd5', CONVERT(VARBINARY(16), d6) AS 'd6 ', CONVERT(VARBINARY(16); d7) AS 'd7'FROM DatetimeoffsetTest;

Výsledek (při použití vertikálního výstupu):

d0 | 0x00D22D003F480BA401d1 | 0x0135CA013F480BA401d2 | 0x0214E6113F480BA401d3 | 0x03CBFCB2003F480BA401d4 | 0x04F3DFFD063F480BA401d5 | 0x057ABFEA45003F480BA401d6 | 0x06C17A2BBB023F480BA401d7 | 0x0787CBB24F1B3F480BA401

Dostaneme tedy stejný výsledek – kromě toho, že byl předřazen s přesností.

Zde je tabulka, která porovnává skutečná data souboru stránky s výsledky CONVERT() operace.

Data souboru stránky CONVERT() Data
d22d003f480ba401 00D22D003F480BA401
35ca013f480ba401 0135CA013F480BA401
14e6113f480ba401 0214E6113F480BA401
cbfcb2003f480ba401 03CBFCB2003F480BA401
f3dffd063f480ba401 04F3DFFD063F480BA401
7abfea45003f480ba401 057ABFEA45003F480BA401
c17a2bbb023f480ba401 06C17A2BBB023F480BA401
87cbb24f1b3f480ba401 0787CBB24F1B3F480BA401

Můžeme tedy vidět, že soubor stránky neukládá přesnost, ale převedený výsledek ano.

Skutečné části data a času jsem zvýraznil červeně. Také jsem odstranil 0x prefix z převedených výsledků, takže se zobrazí pouze aktuální datum/čas (spolu s přesností).

Všimněte si také, že v šestnáctkové soustavě se nerozlišují velká a malá písmena, takže skutečnost, že jedna používá malá a druhá velká, není problém.

Závěr

Při převodu datetimeoffset hodnotu na varbinary , potřebuje k uložení přesnosti bajt navíc. K interpretaci časové části potřebuje přesnost (protože je uložena jako časový interval, jehož přesná hodnota bude záviset na přesnosti).

Při ukládání do databáze se přesnost zadává jednou na úrovni sloupců. Zdá se to logické, protože není potřeba přidávat přesnost ke každému řádku, když všechny řádky stejně používají stejnou přesnost. To by vyžadovalo bajt navíc pro každý řádek, což by zbytečně zvýšilo nároky na úložiště.


  1. Jak vypočítat rozdíl mezi dvěma daty v PostgreSQL/Oracle

  2. Jarní podpora JDBC a velká datová sada

  3. Příklady DAY() – MySQL

  4. Jaký je rozdíl mezi zadním zaškrtnutím a hranatou závorkou v příkazech SQL?