Sqlserver
 sql >> Baza danych >  >> RDS >> Sqlserver

Zrozumienie rozmiaru pamięci „datetimeoffset” w SQL Server

W tym artykule przyjrzę się, jak przesunięcie daty i godziny typ danych jest przechowywany w SQL Server i jak można uzyskać różne raportowane wyniki dotyczące rozmiaru magazynu, w zależności od tego, co z nim robisz.

Jest to podobne do tego, co zrobiłem z datetime2 typ danych.

W szczególności patrzę na następujące:

  • Dokumentacja Microsoft
  • Dane przechowywane w zmiennej
    • Długość w bajtach przy użyciu DATALENGTH()
    • Długość w bajtach przy użyciu DATALENGTH() po konwersji na warbinę
  • Dane przechowywane w bazie danych
    • Długość w bajtach przy użyciu COL_LENGTH()
    • Długość w bajtach przy użyciu DBCC PAGE()

Dokumentacja firmy Microsoft

Oficjalna dokumentacja firmy Microsoft dotycząca przesunięcia daty i godziny typ danych wskazuje, że jego rozmiar pamięci wynosi od 8 do 10 bajtów, w zależności od używanej precyzji.

Podobne do datetime2(n) , możesz użyć datetimeoffset(n) aby określić precyzję, gdzie n to skala od 0 do 7.

Oto dane, które firma Microsoft przedstawia dla tego typu danych:

Określona skala Wynik (precyzja, skala) Długość kolumny (w bajtach) Dokładność ułamków sekund
przesunięcie daty i godziny (34,7) 10 7
przesunięcie daty i godziny(0) (26,0) 8 0-2
przesunięcie daty i godziny(1) (28,1) 8 0-2
przesunięcie daty i godziny(2) (29,2) 8 0-2
przesunięcie daty i godziny(3) (30,3) 9 3-4
przesunięcie daty i godziny(4) (31,4) 9 3-4
przesunięcie daty i godziny(5) (32,5) 10 5-7
przesunięcie daty i godziny(6) (33,6) 10 5-7
przesunięcie daty i godziny(7) (34,7) 10 5-7

Na potrzeby tego artykułu interesuje mnie głównie długość kolumny (w bajtach) kolumna. To mówi nam, ile bajtów jest używanych do przechowywania tego typu danych w bazie danych.

Głównym powodem, dla którego chciałem napisać ten artykuł (i przeprowadzić poniższe eksperymenty), jest to, że dokumentacja Microsoft nie wyjaśnia, że ​​dodatkowy bajt jest używany do precyzji (jak to ma miejsce w dokumentacji dla datetime2 typ danych). W swojej dokumentacji dla datetime2 , stwierdza:

Pierwszy bajt datetime2 wartość przechowuje precyzję wartości, co oznacza rzeczywistą pamięć wymaganą dla datetime2 value to rozmiar pamięci wskazany w powyższej tabeli plus 1 dodatkowy bajt do przechowywania precyzji. To sprawia, że ​​maksymalny rozmiar datetime2 wartość 9 bajtów – 1 bajt przechowuje precyzję plus 8 bajtów do przechowywania danych z maksymalną precyzją.

Ale dokumentacja datetimeoffset nie zawiera tego tekstu, podobnie jak czas dokumentacja.

To spowodowało, że zacząłem się zastanawiać, czy istnieje różnica między tym, jak te typy danych przechowują swoje wartości. Logic powiedział mi, że powinny działać tak samo, ponieważ wszystkie mają precyzję zdefiniowaną przez użytkownika, ale chciałem się dowiedzieć.

Krótka odpowiedź brzmi:tak, przesunięcie daty i godziny wygląda na to, że działa tak samo jak datetime2 (w odniesieniu do dodatkowego bajtu), mimo że nie jest to udokumentowane jako takie.

Pozostała część artykułu zawiera różne przykłady, w których zwracam rozmiar pamięci datetimeoffset wartości w różnych kontekstach.

Dane przechowywane w zmiennej

Przechowujmy przesunięcie daty i godziny wartość w zmiennej i sprawdź jej rozmiar pamięci. Następnie przekonwertuję tę wartość na varbinary i sprawdź ponownie.

Długość w bajtach przy użyciu DATALENGTH

Oto, co się stanie, jeśli użyjemy DATALENGTH() funkcja zwracająca liczbę bajtów użytych dla datetimeoffset(7) wartość:

DECLARE @d datetimeoffset(7);SET @d ='2025-05-21 10:15:30.1234567 +07:00';SELECT @d AS 'Wartość', DATALENGTH(@d) AS 'Długość w bajtach ';

Wynik

+-------------------------------------+--------- ----------+| Wartość | Długość w bajtach ||------------------------------------+-------- -----------|| 2025-05-21 10:15:30.1234567 +07:00 | 10 |+-----------------------------------------+---------- ---------+

Wartość w tym przykładzie ma maksymalną skalę 7 (ponieważ deklaruję zmienną jako datetimeoffset(7) ) i zwraca długość 10 bajtów.

Nie ma tu niespodzianek, jest to dokładny rozmiar pamięci, który powinien być zgodny z dokumentacją Microsoft.

Jeśli jednak przekonwertujemy wartość na varbinary otrzymujemy inny wynik.

Długość w bajtach po konwersji na „zmienną”

Niektórzy programiści lubią konwertować przesunięcie daty i godziny i data/godzina2 zmienne do zmiennej , ponieważ jest bardziej reprezentatywny dla sposobu, w jaki SQL Server przechowuje go w bazie danych. Chociaż jest to częściowo prawda, wyniki nie są dokładnie takie same jak przechowywana wartość (jak zobaczysz później).

Oto, co się stanie, jeśli przekonwertujemy nasze przesunięcie daty i godziny wartość do zmiennej :

DECLARE @d datetimeoffset(7);SET @d ='2025-05-21 10:15:30.1234567 +07:00';SELECT CONVERT(VARBINARY(16), @d) AS 'Wartość', DATALENGTH( CONVERT(VARBINARY(16), @d)) AS 'Długość w bajtach';

Wynik

+--------------------------+------------------- +| Wartość | Długość w bajtach ||-------------------------------------+------------------ -|| 0x0787CBB24F1B3F480BA401 | 11 |+--------------------------+-------------------+ 

W tym przypadku otrzymujemy 11 bajtów.

To jest szesnastkowa reprezentacja przesunięcia daty i godziny wartość. Rzeczywista wartość przesunięcia daty i czasu (i jej precyzja) to wszystko po 0x . Każda para znaków szesnastkowych to bajt. Jest 11 par, a więc 11 bajtów. Potwierdzamy to, gdy używamy DATALENGTH() aby zwrócić długość w bajtach.

W tym przykładzie widzimy, że pierwszy bajt to 07 . To reprezentuje precyzję (użyłem skali 7, więc to jest tutaj wyświetlane).

Jeśli zmienię skalę, zobaczymy, że pierwszy bajt zmienia się, aby dopasować skalę:

DECLARE @d datetimeoffset(3);SET @d ='2025-05-21 10:15:30.1234567 +07:00';SELECT CONVERT(VARBINARY(16), @d) AS 'Wartość', DATALENGTH( CONVERT(VARBINARY(16), @d)) AS 'Długość w bajtach';

Wynik

+-------------------------+--------------------+| Wartość | Długość w bajtach ||------------------------+-------------------| | 0x03CBFCB2003F480BA401 | 10 |+-------------------------+-------------------+

Widzimy również, że długość jest odpowiednio zmniejszona.

Dane przechowywane w bazie danych

W tym przykładzie tworzę bazę danych z różnymi datetimeoffset(n) kolumn, a następnie użyj COL_LENGTH() aby zwrócić długość każdej kolumny w bajtach. Następnie wstawiam wartości do kolumn, przed użyciem DBCC PAGE aby sprawdzić rozmiar pamięci, jaki każdy przesunięcie daty i godziny wartość zajmuje plik strony.

Utwórz bazę danych:

UTWÓRZ test bazy danych;

Utwórz tabelę:

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

W tym przypadku tworzę osiem kolumn – po jednej dla każdej zdefiniowanej przez użytkownika skali, której możemy użyć z datetimeoffset(n) .

Teraz możemy sprawdzić rozmiar pamięci każdej kolumny.

Długość w bajtach przy użyciu COL_LENGTH()

Użyj COL_LENGTH() aby sprawdzić długość (w bajtach) każdej kolumny:

SELECT COL_LENGTH ( 'Test_przesunięcia_daty' , 'd0' ) AS 'd0', COL_LENGTH ( 'Test_przesunięcia-daty' , 'd1' ) AS 'd1', COL_LENGTH ( 'Test przesunięcia-daty' , 'COL2_2') ( 'DatetimeoffsetTest' , 'd3' ) AS 'd3', COL_LENGTH ( 'DatetimeoffsetTest' , 'd4' ) AS 'd4', COL_LENGTH ( 'DatetimeoffsetTest' , 'd5' ) AS 'd5',offset', COLestLENGTH 'd6' ) JAK 'd6', COL_LENGTH ( 'Test przesunięcia daty i godziny' , 'd7' ) JAK 'd7'; 

Wynik:

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

Więc po raz kolejny otrzymujemy ten sam wynik, co dokumentacja, którą otrzymamy. Należy się tego spodziewać, ponieważ dokumentacja wyraźnie stwierdza „Długość kolumny (w bajtach)”, co jest dokładnie tym, co tutaj mierzymy.

Użyj strony DBCC PAGE do sprawdzenia zapisanych danych

Teraz użyjmy DBCC PAGE aby znaleźć rzeczywisty rozmiar danych, które przechowujemy w tej tabeli.

Najpierw wstawmy trochę danych:

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

Teraz wybierz dane (tylko po to, aby je sprawdzić):

WYBIERZ * Z DatatimeoffsetTest;

Wynik (przy użyciu wyjścia pionowego):

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

Zgodnie z oczekiwaniami wartości wykorzystują precyzję, która została wcześniej określona na poziomie kolumny.

Zauważ, że mój system wyświetla końcowe zera. Twój może, ale nie musi. Niezależnie od tego nie wpływa to na rzeczywistą precyzję ani dokładność.

Teraz, zanim użyjemy DBCC PAGE() , musimy wiedzieć, który PagePID przekazać do niego. Możemy użyć DBCC IND() aby to znaleźć.

Znajdź identyfikator strony:

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

Wynik (przy użyciu wyjścia pionowego):

-[ REKORD 1 ]-------------------------PageFID | 1StronaPID | 307IAMFID | NULLIAMPID | NULLObjectID | 1525580473Identyfikator indeksu | 0Numer partycji | 1ID partycji | 72057594043170816iam_chain_type | Typ strony danych w wierszu | 10Poziom indeksu | NULLNastępnaStronaFID | 0NastępnaStronaPID | 0PoprzedniaStronaFID | 0PoprzedniaStronaPID | 0-[ REKORD 2 ]-------------------------PageFID | 1StronaPID | 376IAMFID | 1IAMPID | 307Identyfikator obiektu | 1525580473Identyfikator indeksu | 0Numer partycji | 1ID partycji | 72057594043170816iam_chain_type | Typ strony danych w wierszu | 1Poziom indeksu | 0NastępnaStronaFID | 0NastępnaStronaPID | 0PoprzedniaStronaFID | 0PoprzedniaStronaPID | 0

Zwraca to dwa rekordy. Interesuje nas PageType 1 (drugi rekord). Chcemy mieć PagePID z tego rekordu. W tym przypadku PagePID to 376 .

Teraz możemy wziąć ten PagePID i użyć go w następujący sposób:

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

W tej chwili interesuje nas głównie następująca część:

Slot 0 Kolumna 1 Przesunięcie 0x4 Długość 8 Długość (fizyczna) 8d0 =2025-05-21 10:15:30 +07:00 Szczelina 0 Kolumna 2 Przesunięcie 0xc Długość 8 Długość (fizyczna) 8d1 =2025-05-21 10:15:30.1 +07:00 Szczelina 0 Kolumna 3 Przesunięcie 0x14 Długość 8 Długość (fizyczna) 8d2 =2025-05-21 10:15:30.12 +07:00 Szczelina 0 Kolumna 4 Przesunięcie 0x1c Długość 9 Długość (fizyczna) 9d3 =2025-05-21 10:15:30.123 +07:00 Szczelina 0 Kolumna 5 Przesunięcie 0x25 Długość 9 Długość (fizyczna) 9d4 =2025-05-21 10:15:30.1235 +07:00 Szczelina 0 Kolumna 6 Przesunięcie 0x2e Długość 10 Długość (fizyczna) 10d5 =2025-05-21 10:15:30.12346 +07:00 Slot 0 Kolumna 7 Przesunięcie 0x38 Długość 10 Długość (fizyczna) 10d6 =2025-05-21 10:15:30.123457 +07:00 Slot 0 Kolumna 8 Przesunięcie 0x42 Długość 10 Długość (fizyczna) 10d7 =2025-05-21 10:15:30.1234567 +07:00 

Więc znowu otrzymujemy ten sam wynik. Dokładnie tak, jak stwierdza dokumentacja.

Skoro już tu jesteśmy, przyjrzyjmy się danym – rzeczywistym wartościom daty/godziny, które są przechowywane w SQL Server.

Rzeczywiste wartości są przechowywane w tej części pliku strony:

Zrzut pamięci @0x000000041951A06000000000000000000:10004c00 d22d003f 480ba401 35ca013f 480ba401 ..L.Ò-.?H.¤.5Ê.?H.¤.0000000000000014:14e6113f 480ba401 cbfcb200 3fffba4. H.¤.óßý00000000000000028:063f480b a4017abf ea45003f 480ba401 c17a2bbb .?H.¤.z¿êE.?H.¤.Áz+»000000000000003C:023f480b a40187cb b24f1b3f 480ba401 080000.?Ë.?H. ..

To wciąż zawiera kilka dodatkowych bitów. Usuńmy kilka rzeczy, aby pozostały tylko nasze wartości daty i czasu:

d22d003f 480ba401 35ca013f 480ba40114e6113f 480ba401 cbfcb200 3f480ba4 01f3dffd063f480b a4017abf ea45003f 480ba401 c17a2bbb023f480b a40187cb b24f1b3f>480ba 

Pozostałe cyfry szesnastkowe zawierają wszystkie nasze dane daty i godziny, ale nie precyzję . Są one jednak podzielone na 4-bajtowe fragmenty, więc musimy zmienić rozmieszczenie spacji, aby uzyskać poszczególne wartości.

Oto efekt końcowy. Dla lepszej czytelności umieściłem każdą wartość daty/czasu w nowej linii.

d22d003f480ba401 35ca013f480ba40114e6113f480ba401 cbfcb2003f480ba401f3dffd063f480ba4017abfea45003f480ba401 c17a2bbb023f480ba40187cbb24f1b3f480ba401

To są rzeczywiste wartości szesnastkowe (minus precyzja ), które otrzymalibyśmy, gdybyśmy przekonwertowali przesunięcie daty i godziny wartość do zmiennej . Tak:

 SELECT CONVERT(VARBINARY(16), d0) AS 'd0', CONVERT(VARBINARY(16), d1) AS 'd1', CONVERT(VARBINARY(16), d2) AS 'd2', CONVERT(VARBINARY( 16), d3) JAK 'd3', PRZEKSZTAŁĆ(ZMIENNA(16), d4) JAKO 'd4', PRZEKSZTAŁĆ(ZMIENNA(16), d5) JAKO 'd5', PRZEKSZTAŁĆ(ZMIENNA(16), d6) JAKO 'd6 ', CONVERT(VARBINARY(16), d7) AS 'd7'FROM DatetimeoffsetTest;

Wynik (przy użyciu wyjścia pionowego):

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

Otrzymujemy więc ten sam wynik – z tą różnicą, że został on poprzedzony precyzją.

Oto tabela porównująca rzeczywiste dane pliku strony z wynikami CONVERT() operacja.

Dane pliku strony CONVERT() Dane
d22d003f480ba401 00D22D003F480BA401
35ca013f480ba401 0135CA013F480BA401
14e6113f480ba401 0214E6113F480BA401
cbfcb2003f480ba401 03CBFCB2003F480BA401
f3dffd063f480ba401 04F3DFFD063F480BA401
7abfea45003f480ba401 057ABFEA45003F480BA401
c17a2bbb023f480ba401 06C17A2BBB023F480BA401
87cbb24f1b3f480ba401 0787CBB24F1B3F480BA401

Widzimy więc, że plik strony nie przechowuje precyzji, ale przekonwertowany wynik tak.

Podkreśliłem rzeczywistą datę i godzinę na czerwono. Usunąłem również 0x prefiks z przekonwertowanych wyników, dzięki czemu wyświetlane są tylko rzeczywiste dane daty/godziny (wraz z precyzją).

Pamiętaj też, że w systemie szesnastkowym wielkość liter nie jest rozróżniana, więc to, że jedna używa małych liter, a druga wielkich, nie stanowi problemu.

Wniosek

Konwertując przesunięcie daty i godziny wartość do zmiennej , potrzebuje dodatkowego bajtu do przechowywania precyzji. Potrzebuje precyzji, aby zinterpretować część czasu (ponieważ jest ona przechowywana jako przedział czasu, którego dokładna wartość będzie zależeć od precyzji).

W przypadku przechowywania w bazie danych precyzja jest określana raz na poziomie kolumny. Wydaje się to logiczne, ponieważ nie ma potrzeby dodawania precyzji do każdego wiersza, gdy wszystkie wiersze i tak używają tej samej precyzji. Wymagałoby to dodatkowego bajtu na każdy wiersz, co niepotrzebnie zwiększyłoby wymagania dotyczące pamięci.


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Zapytanie T-SQL, aby wyświetlić definicję tabeli?

  2. Jak wstawić obiekt blob do bazy danych za pomocą studia zarządzania serwerem sql

  3. SQL Server 2016:Utwórz relację

  4. Jak zrozumieć typ danych geograficznych serwera SQL?

  5. Data i godzina programu SQL Server PODOBNY wybór?