В тази статия разглеждам как datetimeoffset типът данни се съхранява в SQL Server и как можете да получите различни отчетени резултати за размер на съхранение, в зависимост от това какво правите с него.
Това е подобно на това, което направих с datetime2 тип данни.
По-специално гледам на следното:
- Документацията на Microsoft
- Данни, съхранявани в променлива
- Дължина в байтове с помощта на
DATALENGTH()
- Дължина в байтове с помощта на
DATALENGTH()
след конвертиране в varbinary
- Дължина в байтове с помощта на
- Данни, съхранявани в база данни
- Дължина в байтове с помощта на
COL_LENGTH()
- Дължина в байтове с помощта на
DBCC PAGE()
- Дължина в байтове с помощта на
Документацията на Microsoft
Официалната документация на Microsoft за datetimeoffset Типът данни показва, че размерът му за съхранение е между 8 и 10 байта, в зависимост от използваната прецизност.
Подобно на datetime2(n) , можете да използвате datetimeoffset(n) за да посочите точността, където n е скала между 0 и 7.
Ето данните, които Microsoft представя за този тип данни:
Посочен мащаб | Резултат (прецизност, мащаб) | Дължина на колоната (байтове) | Дробни секунди точност |
---|---|---|---|
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 |
За целите на тази статия се интересувам основно от Дължината на колоната (байтове) колона. Това ни казва колко байта се използват за съхраняване на този тип данни в база данни.
Основната причина, поради която исках да напиша тази статия (и да стартирам експериментите по-долу), е, че документацията на Microsoft не обяснява, че за прецизността се използва допълнителен байт (както се прави в документацията му за datetime2 силно> тип данни). В неговата документация за datetime2 , то гласи:
Първият байт на datetime2 value съхранява прецизността на стойността, което означава действителното съхранение, необходимо за datetime2 стойността е размерът на паметта, посочен в таблицата по-горе, плюс 1 допълнителен байт за съхраняване на точността. Това прави максималния размер на datetime2 стойност 9 байта – 1 байт съхранява прецизност плюс 8 байта за съхранение на данни с максимална точност.
Но документацията за datetimeoffset не включва този текст, както и времето документация.
Това ме накара да се чудя дали има разлика между това как тези типове данни съхраняват своите стойности. Логиката ми каза, че те трябва да работят еднакво, тъй като всички имат дефинирана от потребителя точност, но исках да разбера.
Краткият отговор е да, datetimeoffset изглежда работи по същия начин като datetime2 (по отношение на допълнителния байт), въпреки че не е документиран като такъв.
Останалата част от статията преминава през различни примери, където връщам размера на съхранение на datetimeoffset стойности в различни контексти.
Данни, съхранявани в променлива
Нека съхраним отместване на датата и времето стойност в променлива и проверете нейния размер за съхранение. След това ще преобразувам тази стойност в varbinary и го проверете отново.
Дължина в байтове с използване на DATALENGTH
Ето какво се случва, ако използваме DATALENGTH()
функция за връщане на броя байтове, използвани за datetimeoffset(7) стойност:
DECLARE @d datetimeoffset(7);SET @d ='2025-05-21 10:15:30.1234567 +07:00';ИЗБЕРЕТЕ @d КАТО 'Стойност', DATALENGTH(@d) КАТО 'Дължина в байтове ';
Резултат
+----------------------------------+-------- ----------+| Стойност | Дължина в байтове ||------------------------------------+-------- -----------|| 2025-05-21 10:15:30.1234567 +07:00 | 10 |+----------------------------------+---------- ---------+
Стойността в този пример има максимален мащаб от 7 (защото декларирам променливата като datetimeoffset(7) ) и връща дължина от 10 байта.
Тук няма изненади, това е точният размер за съхранение, който документацията на Microsoft показва, че трябва да бъде.
Ако обаче преобразуваме стойността в varbinary получаваме различен резултат.
Дължина в байтове след преобразуване във „варбиниран“
Някои разработчици обичат да конвертират datetimeoffset и дата и час2 променливи към varbinary , защото е по-представителен за това как SQL Server го съхранява в базата данни. Въпреки че това е частично вярно, резултатите не са точно същите като съхранената стойност (както ще видите по-късно).
Ето какво се случва, ако преобразуваме нашето отместване на датата и времето стойност до варбинарна :
DECLARE @d datetimeoffset(7);SET @d ='2025-05-21 10:15:30.1234567 +07:00';SELECT CONVERT(VARBINARY(16), @d) КАТО 'Стойност', DATALENGTH( CONVERT(VARBINARY(16), @d)) КАТО 'Дължина в байтове';
Резултат
+--------------------+------------------- +| Стойност | Дължина в байтове ||------------------------------+------------------ -|| 0x0787CBB24F1B3F480BA401 | 11 |+--------------------+------------------+
В този случай получаваме 11 байта.
Това е шестнадесетично представяне на datetimeoffset стойност. Действителната стойност на изместване на датата и часа (и нейната прецизност) е всичко след 0x
. Всяка двойка шестнадесетични знаци е байт. Има 11 двойки и следователно 11 байта. Това се потвърждава, когато използваме DATALENGTH()
за да върнете дължината в байтове.
В този пример можем да видим, че първият байт е 07
. Това представлява прецизността (използвах скала от 7 и това е, което се показва тук).
Ако променя мащаба, можем да видим, че първият байт се променя, за да съответства на мащаба:
DECLARE @d datetimeoffset(3);SET @d ='2025-05-21 10:15:30.1234567 +07:00';SELECT CONVERT(VARBINARY(16), @d) КАТО 'Стойност', DATALENGTH( CONVERT(VARBINARY(16), @d)) КАТО 'Дължина в байтове';
Резултат
+-------------------+-------------------+| Стойност | Дължина в байтове ||-----------------------+------------------| | 0x03CBFCB2003F480BA401 | 10 |+------------------+-------------------+предварително>Също така можем да видим, че дължината е съответно намалена.
Данни, съхранявани в база данни
В този пример създавам база данни с различни datetimeoffset(n) колони и след това използвайте
COL_LENGTH()
за да върне дължината на всяка колона в байтове. След това вмъквам стойности в колоните, преди да използвамDBCC PAGE
за да проверите размера на хранилището, което всеки datetimeoffset стойността заема файла на страницата.Създайте база данни:
СЪЗДАВАНЕ НА БАЗА ДАННИ Тест;Създайте таблица:
ИЗПОЛЗВАЙТЕ тест; СЪЗДАЙТЕ ТАБЛИЦА 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) );В този случай създавам осем колони – по една за всеки дефиниран от потребителя мащаб, който можем да използваме с datetimeoffset(n) .
Сега можем да проверим размера на съхранение на всяка колона.
Дължина в байтове с помощта на COL_LENGTH()
Използвайте
COL_LENGTH()
за да проверите дължината (в байтове) на всяка колона:SELECT COL_LENGTH ( 'DatetimeoffsetTest' , 'd0' ) КАТО 'd0', COL_LENGTH ( 'DatetimeoffsetTest' , 'd1' ) КАТО 'd1', COL_LENGTH ( 'DatetimeoffsetTest' , 'd2'd2' ) КАТО C ( 'DatetimeoffsetTest' , 'd3' ) КАТО 'd3', COL_LENGTH ( 'DatetimeoffsetTest' , 'd4' ) КАТО 'd4', COL_LENGTH ( 'DatetimeoffsetTest' , 'd5' ) КАТО 'd5', COLT'LENG 'd6' ) AS 'd6', COL_LENGTH ( 'DatetimeoffsetTest' , 'd7' ) AS 'd7';Резултат:
+------+------+------+------+------+-----+---- --+------+| d0 | d1 | d2 | d3 | d4 | d5 | d6 | d7 ||------+------+------+------+------+------+----- -+------|| 8 | 8 | 8 | 9 | 9 | 10 | 10 | 10 |+------+------+------+------+------+------+----- -+------+Така че отново получаваме същия резултат, както е посочено в документацията, че ще получим. Това може да се очаква, тъй като в документацията изрично е посочено „Дължина на колоната (байтове)“, което е точно това, което измерваме тук.
Използвайте DBCC PAGE, за да проверите съхранените данни
Сега нека използваме
DBCC PAGE
за да намерите действителния размер за съхранение на данните, които съхраняваме в тази таблица.Първо, нека вмъкнем някои данни:
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;Сега изберете данните (само за да ги проверите):
SELECT * FROM DatetimeoffsetTest;Резултат (с помощта на вертикален изход):
d0 | 2025-05-21 10:15:30.0000000 +07:00d1 | 21.05.2025 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Както се очаква, стойностите използват прецизността, която е била предварително посочена на ниво колона.
Имайте предвид, че моята система показва крайни нули. Вашият може или не може да го направи. Независимо от това, това не засяга действителната прецизност или точност.
Сега, преди да използваме
DBCC PAGE()
, трябва да знаем кой PagePID да му предадем. Можем да използвамеDBCC IND()
за да го намерите.Намерете PagePID:
DBCC IND('Test', 'dbo.DatetimeoffsetTest', 0);Резултат (с помощта на вертикален изход):
-[ ЗАПИС 1 ]------------------------------PageFID | 1PagePID | 307IAMFID | NULLIAMPID | NULLObjectID | 1525580473 Индекс ИД | 0Номер на дял | 1PartitionID | 72057594043170816iam_chain_type | В ред dataPageType | 10 Ниво на индекс | NULLNextPageFID | 0NextPagePID | 0PrevPageFID | 0PrevPagePID | 0-[ ЗАПИС 2 ]------------------------------PageFID | 1PagePID | 376IAMFID | 1IAMPID | 307ObjectID | 1525580473 Индекс ИД | 0Номер на дял | 1PartitionID | 72057594043170816iam_chain_type | В ред dataPageType | 1Ниво на индекс | 0Следваща страницаFID | 0NextPagePID | 0PrevPageFID | 0PrevPagePID | 0Това връща два записа. Интересуваме се от PageType от 1 (2-ри запис). Искаме PagePID от този запис. В този случай PagePID е 376 .
Сега можем да вземем този PagePID и да го използваме в следното:
DBCC TRACEON(3604, -1);DBCC PAGE(Тест, 1, 376, 3);В момента се интересуваме основно от следната част:
Слот 0 Колона 1 Изместване 0x4 Дължина 8 Дължина (физическа) 8d0 =2025-05-21 10:15:30 +07:00 Слот 0 Колона 2 Отместване 0xc Дължина 8 Дължина (физическа) 2025-05-21 2025 10:15:30.1 +07:00 Слот 0 Колона 3 Отместване 0x14 Дължина 8 Дължина (физическа) 8d2 =2025-05-21 10:15:30.12 +07:00 Слот 0 Колона 4 Отместване 0x3 Дължина на дължина 9 (Физически отместване) =2025-05-21 10:15:30.123 +07:00 Слот 0 Колона 5 Отместване 0x25 Дължина 9 Дължина (физическа) 9d4 =2025-05-21 10:15:30.1235 Length lotx 07:0 Cole Offset 07:0 Дължина (физическа) 10d5 =2025-05-21 10:15:30.12346 +07:00 Слот 0 Колона 7 Отместване 0x38 Дължина 10 Дължина (физическа) 10d6 =2025-05-25:05 Slot 0:15:07 + Колона 8 Отместване 0x42 Дължина 10 Дължина (физическа) 10d7 =2025-05-21 10:15:30.1234567 +07:00Така че отново получаваме същия резултат. Точно както е посочено в документацията.
Докато сме тук, нека да разгледаме данните – действителните стойности за дата/час, както се съхраняват в SQL Server.
Действителните стойности се съхраняват в тази част на файла на страницата:
изхвърляне на памет @0x000000041951a0600000000000000000:10004C00 D22D003F 480BA401 35CA013F 480BA401 ..L.ò-.? H.¤.óßý000000000000000028:063F480B A4017ABF EA45003F 480BA401 C17A2BBB. ..Това все още включва няколко допълнителни бита. Нека изтрием няколко неща, така че да останат само нашите стойности за дата и час:
D22D003F 480BA401 35CA013F 480BA40114E6113F 480BA401 CBFCB200 3F480BA4 01F3DFFD063F480B A4017ABF EA45003F 480BA401 C177AОстаналите шестнадесетични цифри съдържат всички наши данни за дата и час, но не и точността . Те обаче са подредени в 4 байтови парчета, така че трябва да пренаредим интервалите, за да получим отделните стойности.
Ето крайния резултат. Поставих всяка стойност за дата/час на нов ред за по-добра четливост.
d22d003f480ba401 35ca013f480ba40114e6113f480ba401 cbfcb2003f480ba401f3dffd063f480ba4017abfea45003f480b2f40bf400b40b400b40b40b40b40b40b40b40b40b40b480b480b480b480b480b480bf480bf480bf480bf480bf480bf480bf480b40b40b40b40b0b480b480b480b400cТова са действителните шестнадесетични стойности (минус точността ), които бихме получили, ако преобразуваме datetimeoffset стойност до варбинарна . Като това:
ИЗБЕРЕТЕ CONVERT(VARBINARY(16), d0) КАТО 'd0', CONVERT(VARBINARY(16), d1) КАТО 'd1', CONVERT(VARBINARY(16), d2) КАТО 'd2', CONVERT(VARBINARY( 16), d3) КАТО 'd3', CONVERT(VARBINARY(16), d4) КАТО 'd4', CONVERT(VARBINARY(16), d5) КАТО 'd5', CONVERT(VARBINARY(16), d6) КАТО 'd6 ', CONVERT(VARBINARY(16), d7) КАТО 'd7'FROM DatetimeoffsetTest;Резултат (с помощта на вертикален изход):
d0 | 0x00D22D003F480BA401d1 | 0x0135CA013F480BA401d2 | 0x0214E6113F480BA401d3 | 0x03CBFCB2003F480BA401d4 | 0x04F3DFFD063F480BA401d5 | 0x057ABFEA45003F480BA401d6 | 0x06C17A2BBB023F480BA401d7 | 0x0787CBB24F1B3F480BA401Така че получаваме същия резултат – с изключение на това, че е добавен с точност.
Ето таблица, която сравнява действителните данни от файла на страницата с резултатите от
CONVERT()
операция.
Данни за файл на страницата | CONVERT() Данни |
---|---|
d22d003f480ba401 | 00D22D003F480BA401 |
35ca013f480ba401 | 0135CA013F480BA401 |
14e6113f480ba401 | 0214E6113F480BA401 |
cbfcb2003f480ba401 | 03CBFCB2003F480BA401 |
f3dffd063f480ba401 | 04F3DFFD063F480BA401 |
7abfea45003f480ba401 | 057ABFEA45003F480BA401 |
c17a2bbb023f480ba401 | 06C17A2BBB023F480BA401 |
87cbb24f1b3f480ba401 | 0787CBB24F1B3F480BA401 |
Така че можем да видим, че файлът на страницата не съхранява точността, но конвертираният резултат го прави.
Осветих действителните части за дата и час в червено. Премахнах и 0x
префикс от преобразуваните резултати, така че да се показват само действителните данни за дата/час (заедно с точността).
Също така имайте предвид, че шестнадесетичният знак не е чувствителен към главни букви, така че фактът, че единият използва малки букви, а другият използва главни букви, не е проблем.
Заключение
При преобразуване на отместване на дата и час стойност до варбинарна , се нуждае от допълнителен байт за съхраняване на точността. Той се нуждае от точността, за да интерпретира частта от време (тъй като това се съхранява като интервал от време, чиято точна стойност ще зависи от точността).
Когато се съхранява в база данни, прецизността се посочва веднъж на ниво колона. Това изглежда логично, тъй като няма нужда да добавяте прецизност към всеки ред, когато всички редове така или иначе използват една и съща точност. Това ще изисква допълнителен байт за всеки ред, което би увеличило ненужно изискванията за съхранение.