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

Zrozumienie rozmiaru pamięci „czas” w SQL Server

W tym artykule przyjrzę się rozmiarowi pamięci w czasie typ danych w SQL Server.

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 Microsoft czas typ danych wskazuje, że jego rozmiar pamięci wynosi od 3 do 5 bajtów, w zależności od używanej precyzji.

Ten typ danych pozwala na precyzję zdefiniowaną przez użytkownika. Możesz użyć czas(n) aby określić precyzję, gdzie n to skala od 0 do 7.

Oto dane, które Microsoft prezentuje na czas typ danych:

Określona skala Wynik (precyzja, skala) Długość kolumny (w bajtach) Dokładność ułamków sekund
czas (16,7) 5 7
czas(0) (8,0) 3 0-2
czas(1) (10,1) 3 0-2
czas(2) (11,2) 3 0-2
czas(3) (12,3) 4 3-4
czas(4) (13,4) 4 3-4
czas(5) (14,5) 5 5-7
czas(6) (15,6) 5 5-7
czas(7) (16,7) 5 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.

Z perspektywy użytkownika czas typ danych działa tak samo, jak część czasowa datetime2 . Ma zdefiniowaną przez użytkownika precyzję ułamków sekund i akceptuje skalę od 0 do 7.

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

Dane przechowywane w zmiennej

Najpierw zapiszę czas 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żywanych przez czas(7) wartość:

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

Wynik

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

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

Należy się tego spodziewać, ponieważ odpowiada rozmiarowi pamięci przedstawionemu w tabeli 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ć czas lub datagodzina2 zmienne do zmiennej ponieważ jest bardziej reprezentatywny dla tego, jak SQL Server przechowuje go w bazie danych. Chociaż jest to częściowo prawda, wyniki nie są dokładnie takie same jak przechowywana wartość (więcej na ten temat poniżej).

Oto, co się stanie, jeśli przekonwertujemy nasz czas wartość do zmiennej :

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

Wynik

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

W tym przypadku otrzymujemy 6 bajtów. Nasza wartość używa teraz 1 bajtu więcej niż podano w dokumentacji.

To dlatego, że potrzebuje dodatkowego bajtu do przechowywania precyzji.

To jest szesnastkowe przedstawienie czasu wartość. Rzeczywista wartość czasu (i jej precyzja) to wszystko po 0x . Każda para znaków szesnastkowych to bajt. Jest 6 par, a więc 6 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 @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';

Wynik

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

Widzimy również, że długość jest odpowiednio skrócona. Ale znowu, jest to o jeden bajt więcej niż to, co dokumentacja mówi, że powinien użyć.

Chociaż dokumentacja Microsoftu na czas nie wspomina o tym wyraźnie, dokumentacja datetime2 stwierdza co następuje:

Pierwszy bajt datetime2 value 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ą.

Oraz datetime2 typ danych działa dokładnie w ten sam sposób w odniesieniu do powyższych przykładów. Innymi słowy, zgłasza dodatkowy bajt tylko wtedy, gdy jest konwertowany na varbinary .

Tak więc dodatkowy bajt wspomniany w dokumentacji Microsoft wydaje się również dotyczyć czasu .

Jednak rzeczywisty rozmiar pamięci Twojego czasu wartości będą na miejscu przechowywania danych.

Dane przechowywane w bazie danych

Kiedy kolumna bazy danych ma typ czasu , jego precyzja jest określona na poziomie kolumny – nie na poziomie danych. Innymi słowy, jest określony raz dla całej kolumny. Ma to sens, ponieważ kiedy definiujesz kolumnę jako czas(7) , wiesz, że wszystkie wiersze będą oznaczać czas(7) . Nie ma potrzeby zużywania cennych bajtów na powtarzanie tego faktu w każdym wierszu.

Kiedy badasz czas wartość, ponieważ jest przechowywana w SQL Server, zobaczysz, że jest taka sama jak varbinary wynik, ale bez precyzja.

Poniżej znajdują się przykłady pokazujące, jak czas wartości są przechowywane w SQL Server.

W tych przykładach tworzę bazę danych z różnymi czasem(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 tych kolumn, przed użyciem DBCC PAGE aby sprawdzić rozmiar pamięci, który za każdym razem wartość zajmuje plik strony.

Utwórz bazę danych:

CREATE DATABASE Test;

Utwórz 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)
    );

W tym przypadku tworzę osiem kolumn – po jednej dla każdej zdefiniowanej przez użytkownika skali, której możemy użyć z time(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 ( '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';  

Wynik:

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

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.

Pamiętaj, to jest przed wstawiamy dowolne dane. Same kolumny określają precyzję (a tym samym rozmiar pamięci) wszelkich wstawianych danych – nie na odwrót.

Użyj strony DBCC PAGE do sprawdzenia zapisanych danych

Teraz wstawmy dane, a następnie użyjmy DBCC PAGE aby znaleźć rzeczywisty rozmiar przechowywania danych, które przechowujemy w każdej kolumnie.

Wstaw dane:

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;

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

SELECT * FROM TimeTest;

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

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

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

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

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

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

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

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

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

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                                                                      

Otrzymujemy więc taki sam wynik, jak stwierdza dokumentacja. Sugerowałoby to, że precyzja nie jest przechowywana z wartościami.

Możemy to potwierdzić, sprawdzając rzeczywiste dane.

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

Memory Dump @0x0000000423ADA060

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

Możemy wyodrębnić rzeczywiste wartości czasu, usuwając kilka rzeczy. Po usunięciu pozostaną następujące elementy:

42900095 a205d459 384b8233 02f31603
167ae51e dc00c1f6 34990887 a311fc55

Te cyfry szesnastkowe zawierają wszystkie nasze dane dotyczące czasu, 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.

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

To są rzeczywiste wartości szesnastkowe (minus precyzja ), które otrzymalibyśmy, gdybyśmy przekonwertowali czas wartość do zmiennej . Tak:

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;

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

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

To zapytanie daje ten sam wynik – z wyjątkiem tego, że każda wartość została poprzedzona precyzją.

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

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

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

Podczas konwersji czasu 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 mają taką samą precyzję. 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. Zobacz historię zadań agenta SQL Server za pomocą programu SSMS

  2. Analiza wydajności we/wy dla SQL Server

  3. SQL Server 2008 Spatial:znajdź punkt w wielokącie

  4. Dodaj domyślną wartość pola datetime w SQL Server do znacznika czasu

  5. Zbiorcze wstawianie częściowo cytowanego pliku CSV w programie SQL Server