Dans cet article, je regarde comment le datetimeoffset Le type de données est stocké dans SQL Server et comment vous pouvez obtenir différents résultats de taille de stockage signalés, en fonction de ce que vous en faites.
Ceci est similaire à ce que j'ai fait avec le datetime2 type de données.
En particulier, je regarde ce qui suit :
- Documentation Microsoft
- Données stockées dans une variable
- Longueur en octets en utilisant
DATALENGTH()
- Longueur en octets en utilisant
DATALENGTH()
après conversion en varbinary
- Longueur en octets en utilisant
- Données stockées dans une base de données
- Longueur en octets en utilisant
COL_LENGTH()
- Longueur en octets en utilisant
DBCC PAGE()
- Longueur en octets en utilisant
Documentation Microsoft
Documentation officielle de Microsoft sur le datetimeoffset Le type de données indique que sa taille de stockage est comprise entre 8 et 10 octets, selon la précision utilisée.
Semblable à datetime2(n) , vous pouvez utiliser datetimeoffset(n) pour spécifier la précision, où n est une échelle entre 0 et 7.
Voici les données présentées par Microsoft pour ce type de données :
Échelle spécifiée | Résultat (précision, échelle) | Longueur de la colonne (octets) | Précision fractionnaire de seconde |
---|---|---|---|
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 |
Pour les besoins de cet article, je m'intéresse principalement à la Longueur de la colonne (octets) colonne. Cela nous indique combien d'octets sont utilisés pour stocker ce type de données dans une base de données.
La principale raison pour laquelle j'ai voulu écrire cet article (et exécuter les expériences ci-dessous), est que la documentation Microsoft n'explique pas qu'un octet supplémentaire est utilisé pour la précision (comme c'est le cas dans sa documentation pour le datetime2 Type de données). Dans sa documentation pour datetime2 , il indique :
Le premier octet d'un datetime2 value stocke la précision de la valeur, c'est-à-dire le stockage réel requis pour un datetime2 value est la taille de stockage indiquée dans le tableau ci-dessus plus 1 octet supplémentaire pour stocker la précision. Cela rend la taille maximale d'un datetime2 valeur 9 octets - 1 octet stocke la précision plus 8 octets pour le stockage des données avec une précision maximale.
Mais la documentation pour datetimeoffset n'inclut pas ce texte, et l'heure non plus documents.
Cela m'a amené à me demander s'il y a une différence entre la façon dont ces types de données stockent leurs valeurs. Logic m'a dit qu'ils devraient fonctionner de la même manière, car ils ont tous une précision définie par l'utilisateur, mais je voulais le savoir.
La réponse courte est oui, datetimeoffset semble fonctionner de la même manière que datetime2 (en ce qui concerne l'octet supplémentaire), même s'il n'est pas documenté en tant que tel.
Le reste de l'article passe par divers exemples où je renvoie la taille de stockage de datetimeoffset valeurs dans différents contextes.
Données stockées dans une variable
Stockons un datetimeoffset valeur dans une variable et vérifier sa taille de stockage. Ensuite, je convertirai cette valeur en varbinary et revérifiez.
Longueur en octets en utilisant DATALENGTH
Voici ce qui se passe si nous utilisons le DATALENGTH()
fonction pour renvoyer le nombre d'octets utilisés pour un datetimeoffset(7) valeur :
DECLARE @d datetimeoffset(7);SET @d ='2025-05-21 10:15:30.1234567 +07:00';SELECT @d AS 'Valeur', DATALENGTH(@d) AS 'Longueur en octets ';
Résultat
+------------------------------------+--------- ----------+| Valeur | Longueur en octets ||-----------------------------------------+-------- -----------|| 2025-05-21 10:15:30.1234567 +07:00 | 10 |+--------------------------------------------------+---------- ---------+
La valeur dans cet exemple a l'échelle maximale de 7 (parce que je déclare la variable comme datetimeoffset(7) ), et il renvoie une longueur de 10 octets.
Pas de surprise ici, c'est la taille de stockage exacte que la documentation Microsoft indique qu'elle devrait être.
Cependant, si nous convertissons la valeur en varbinary nous obtenons un résultat différent.
Longueur en octets après conversion en 'varbinary'
Certains développeurs aiment convertir datetimeoffset et datetime2 variables en varbinary , car il est plus représentatif de la façon dont SQL Server le stocke dans la base de données. Bien que cela soit partiellement vrai, les résultats ne sont pas exactement les mêmes que la valeur stockée (comme vous le verrez plus tard).
Voici ce qui se passe si nous convertissons notre datetimeoffset valeur en 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 'Longueur en octets';
Résultat
+--------------------------+---------------------------- +| Valeur | Longueur en octets ||--------------------------+------------------------------ -|| 0x0787CBB24F1B3F480BA401 | 11 |+---------------------------+------------------+
Dans ce cas, nous obtenons 11 octets.
Il s'agit d'une représentation hexadécimale du datetimeoffset valeur. La valeur réelle du décalage horaire de la date (et sa précision) est tout ce qui suit le 0x
. Chaque paire de caractères hexadécimaux est un octet. Il y a 11 paires, donc 11 octets. Ceci est confirmé lorsque nous utilisons DATALENGTH()
pour renvoyer la longueur en octets.
Dans cet exemple, nous pouvons voir que le premier octet est 07
. Cela représente la précision (j'ai utilisé une échelle de 7 et c'est donc ce qui est affiché ici).
Si je change l'échelle, on peut voir que le premier octet change pour correspondre à l'échelle :
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 'Longueur en octets';
Résultat
+-----------------------+----------------------------+| Valeur | Longueur en octets ||-----------------------+-------------------------------| | 0x03CBFCB2003F480BA401 | 10 |+-----------------------+-----------------------+Nous pouvons également voir que la longueur est réduite en conséquence.
Données stockées dans une base de données
Dans cet exemple, je crée une base de données avec différents datetimeoffset(n) colonnes, puis utilisez
COL_LENGTH()
pour renvoyer la longueur de chaque colonne, en octets. J'insère ensuite des valeurs dans les colonnes, avant d'utiliserDBCC PAGE
pour vérifier la taille de stockage que chaque datetimeoffset la valeur occupe le fichier de la page.Créer une base de données :
Test CRÉER BASE DE DONNÉES ;Créer un tableau :
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) );Dans ce cas, je crée huit colonnes - une pour chaque échelle définie par l'utilisateur que nous pouvons utiliser avec datetimeoffset(n) .
Nous pouvons maintenant vérifier la taille de stockage de chaque colonne.
Longueur en octets en utilisant COL_LENGTH()
Utilisez
COL_LENGTH()
pour vérifier la longueur (en octets) de chaque colonne :SELECT COL_LENGTH ( 'DatetimeoffsetTest' , 'd0' ) AS 'd0', COL_LENGTH ( 'DatetimeoffsetTest' , 'd1' ) AS 'd1', COL_LENGTH ( 'DatetimeoffsetTest' , 'd2' ) AS 'd2', COL_LENGTH ( 'DatetimeoffsetTest' , 'd3' ) AS 'd3', COL_LENGTH ( 'DatetimeoffsetTest' , 'd4' ) AS 'd4', COL_LENGTH ( 'DatetimeoffsetTest' , 'd5' ) AS 'd5', COL_LENGTH ( 'DatetimeoffsetTest' , 'd6' ) AS 'd6', COL_LENGTH ( 'DatetimeoffsetTest' , 'd7' ) AS 'd7';Résultat :
+------+------+------+------+------+------+---- --+------+| d0 | d1 | d2 | d3 | d4 | d5 | d6 | d7 ||------+------+------+------+------+------+----- -+------|| 8 | 8 | 8 | 9 | 9 | 10 | 10 | 10 |+------+------+------+------+------+------+----- -+------+Donc, encore une fois, nous obtenons le même résultat que la documentation indique que nous obtiendrons. C'est normal, car la documentation indique explicitement "Longueur de colonne (octets)", ce qui correspond exactement à ce que nous mesurons ici.
Utilisez DBCC PAGE pour vérifier les données stockées
Utilisons maintenant
DBCC PAGE
pour trouver la taille de stockage réelle des données que nous stockons dans cette table.Tout d'abord, insérons quelques données :
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;Sélectionnez maintenant les données (juste pour les vérifier) :
SELECT * FROM DatetimeoffsetTest ;Résultat (en utilisant la sortie verticale) :
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:00Comme prévu, les valeurs utilisent la précision précédemment spécifiée au niveau de la colonne.
Notez que mon système affiche des zéros à la fin. Le vôtre peut le faire ou non. Quoi qu'il en soit, cela n'affecte pas la précision ou l'exactitude réelle.
Maintenant, avant d'utiliser
DBCC PAGE()
, nous devons savoir quel PagePID lui transmettre. Nous pouvons utiliserDBCC IND()
pour le trouver.Trouver le PagePID :
DBCC IND('Test', 'dbo.DatetimeoffsetTest', 0);Résultat (en utilisant la sortie verticale) :
-[ ENREGISTREMENT 1 ]-------------------------PageFID | 1PagePID | 307IAMFID | NULLIAMPIDE | NULLObjectID | 1525580473IndexID | 0NuméroPartition | 1ID de partition | 72057594043170816iam_chain_type | DataPageType dans la ligne | 10NiveauIndex | NULLPageSuivanteFID | 0PIDPageSuivante | 0FIDPagePrécédente | 0PIDPagePrécédente | 0-[ ENREGISTREMENT 2 ]-------------------------PageFID | 1PagePID | 376IAMFID | 1IAMPID | 307ID d'objet | 1525580473IndexID | 0NuméroPartition | 1ID de partition | 72057594043170816iam_chain_type | DataPageType dans la ligne | 1NiveauIndex | 0FIDPageSuivante | 0PIDPageSuivante | 0FIDPagePrécédente | 0PIDPagePrécédente | 0Cela renvoie deux enregistrements. Nous sommes intéressés par le PageType de 1 (le 2e enregistrement). Nous voulons le PagePID de cet enregistrement. Dans ce cas, le PagePID est 376 .
Nous pouvons maintenant prendre ce PagePID et l'utiliser dans ce qui suit :
DBCC TRACEON(3604, -1);DBCC PAGE(Test, 1, 376, 3);Pour l'instant, nous nous intéressons principalement à la partie suivante :
Slot 0 Colonne 1 Décalage 0x4 Longueur 8 Longueur (physique) 8d0 =2025-05-21 10:15:30 +07:00 Slot 0 Colonne 2 Décalage 0xc Longueur 8 Longueur (physique) 8d1 =2025-05-21 10:15:30.1 +07:00 Emplacement 0 Colonne 3 Décalage 0x14 Longueur 8 Longueur (physique) 8d2 =2025-05-21 10:15:30.12 +07:00 Emplacement 0 Colonne 4 Décalage 0x1c Longueur 9 Longueur (physique) 9d3 =2025-05-21 10:15:30.123 +07:00 Slot 0 Colonne 5 Décalage 0x25 Longueur 9 Longueur (physique) 9d4 =2025-05-21 10:15:30.1235 +07:00Slot 0 Colonne 6 Décalage 0x2e Longueur 10 Longueur (physique) 10d5 =2025-05-21 10:15:30.12346 +07:00 Emplacement 0 Colonne 7 Décalage 0x38 Longueur 10 Longueur (physique) 10d6 =2025-05-21 10:15:30.123457 +07:00 Emplacement 0 Colonne 8 Décalage 0x42 Longueur 10 Longueur (physique) 10d7 =2025-05-21 10:15:30.1234567 +07:00Nous obtenons donc à nouveau le même résultat. Exactement comme l'indique la documentation.
Pendant que nous y sommes, examinons les données - les valeurs de date/heure réelles telles qu'elles sont stockées dans SQL Server.
Les valeurs réelles sont stockées dans cette partie du fichier de page :
Dump de mémoire @ 0x000000041951A06000000000000000:10004C00 D22D003F 480BA401 35CA013F 480BA401 ..l.ò -.? H.¤.5ê.? H.¤.0000000000000014:14e6113f 480ba401 H.¤.óßý0000000000000028:063F480B A4017ABF EA45003F 480BA401 C17A2BBB .? ..Cela inclut encore quelques bits supplémentaires. Supprimons quelques éléments pour ne conserver que nos valeurs de date et d'heure :
d22d003f 480ba401 35ca013f 480ba40114e6113f 480ba401 cbfcb200 3f480ba4 01f3dffd063f480b a4017abf ea45003f 480ba401 c17a2bbb023f480b a4018 b4018Les chiffres hexadécimaux restants contiennent toutes nos données de date et d'heure, mais pas la précision . Cependant, ils sont organisés en blocs de 4 octets, nous devons donc réorganiser les espaces pour obtenir les valeurs individuelles.
Voici le résultat final. J'ai placé chaque valeur de date/heure sur une nouvelle ligne pour une meilleure lisibilité.
d22d003f480ba401 35ca013f480ba40114e6113f480ba401 cbfcb2003f480ba401f3dffd063f480ba4017abfea45003f480ba401 c17a2bbb023f480ba40187cbb24f1b3f4Ce sont les valeurs hexadécimales réelles (moins la précision ) que nous obtiendrions si nous convertissions le datetimeoffset valeur en varbinary . Comme ceci :
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 ;Résultat (en utilisant la sortie verticale) :
d0 | 0x00D22D003F480BA401d1 | 0x0135CA013F480BA401d2 | 0x0214E6113F480BA401d3 | 0x03CBFCB2003F480BA401d4 | 0x04F3DFFD063F480BA401d5 | 0x057ABFEA45003F480BA401d6 | 0x06C17A2BBB023F480BA401d7 | 0x0787CBB24F1B3F480BA401Nous obtenons donc le même résultat - sauf qu'il a été ajouté avec précision.
Voici un tableau qui compare les données réelles du fichier de page aux résultats de la
CONVERT()
opération.
Données de fichier de page | CONVERT() Données |
---|---|
d22d003f480ba401 | 00D22D003F480BA401 |
35ca013f480ba401 | 0135CA013F480BA401 |
14e6113f480ba401 | 0214E6113F480BA401 |
cbfcb2003f480ba401 | 03CBFCB2003F480BA401 |
f3dffd063f480ba401 | 04F3DFFD063F480BA401 |
7abfea45003f480ba401 | 057ABFEA45003F480BA401 |
c17a2bbb023f480ba401 | 06C17A2BBB023F480BA401 |
87cbb24f1b3f480ba401 | 0787CBB24F1B3F480BA401 |
Nous pouvons donc voir que le fichier de page ne stocke pas la précision, mais le résultat converti le fait.
J'ai mis en surbrillance la date et l'heure réelles en rouge. J'ai également supprimé le 0x
préfixe des résultats convertis, de sorte que seules les données de date/heure réelles soient affichées (avec la précision).
Notez également que l'hexadécimal n'est pas sensible à la casse. Ainsi, le fait que l'un utilise des minuscules et l'autre des majuscules n'est pas un problème.
Conclusion
Lors de la conversion d'un datetimeoffset valeur en varbinary , il a besoin d'un octet supplémentaire pour stocker la précision. Il a besoin de la précision pour interpréter la partie temporelle (car celle-ci est stockée sous la forme d'un intervalle de temps, dont la valeur exacte dépendra de la précision).
Lorsqu'elle est stockée dans une base de données, la précision est spécifiée une fois au niveau de la colonne. Cela semble logique, car il n'est pas nécessaire d'ajouter la précision à chaque ligne lorsque toutes les lignes utilisent de toute façon la même précision. Cela nécessiterait un octet supplémentaire pour chaque ligne, ce qui augmenterait inutilement les besoins en stockage.