Sqlserver
 sql >> Base de données >  >> RDS >> Sqlserver

Comprendre la taille de stockage 'datetime2' dans SQL Server

Dans cet article, je partage quelques observations que j'ai eues concernant le datetime2 la taille de stockage du type de données dans SQL Server. Je vais peut-être clarifier certains points concernant la taille de stockage réelle utilisée par ce type de données lorsqu'il est stocké dans une base 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
  • Données stockées dans une base de données
    • Longueur en octets en utilisant COL_LENGTH()
    • Longueur en octets en utilisant DBCC PAGE()

Certaines d'entre elles semblent se contredire, et vous verrez deux tailles de stockage différentes pour la même valeur, selon l'endroit où vous regardez.

Un datetime2 la valeur peut afficher une taille de stockage différente, selon qu'elle est stockée dans une base de données, en tant que datetime2 variable ou convertie en varbinary .

Mais il y a une explication plausible à cela - cela dépend de l'endroit où la précision est en cours de stockage.

Au cours de mes recherches sur ce problème, j'ai trouvé l'article approfondi de Ronen Ariely sur la façon dont datetime2 est stocké dans le fichier de données très informatif, et cela m'a incité à exécuter des tests similaires dans mon propre environnement de développement et à les présenter ici.

Documentation Microsoft

Voyons d'abord ce que dit la documentation officielle.

Documentation de Microsoft sur le datetime2 type de données indique que sa taille de stockage est la suivante :

6 octets pour une précision inférieure à 3.
7 octets pour une précision 3 ou 4.
Toute autre précision nécessite 8 octets.

Mais il qualifie le tableau ci-dessus avec la déclaration suivante :

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.

Donc, compte tenu des informations ci-dessus, la conclusion évidente à tirer serait que le tableau pourrait/(devrait ?) être écrit comme suit :

7 octets pour une précision inférieure à 3.
8 octets pour une précision 3 ou 4.
Toute autre précision nécessite 9 octets.

De cette façon, ils n'auraient pas besoin de le qualifier avec les informations supplémentaires sur la précision.

Mais ce n'est pas aussi simple que ça.

Données stockées dans une variable

Tout d'abord, stockons un datetime2 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 datetime2(7) valeur :

DECLARE @d datetime2(7);SET @d ='2025-05-21 10:15:30.1234567';SELECT @d AS 'Valeur', DATALENGTH(@d) AS 'Longueur en octets'; 

Résultat

+-------------------------------------------+---------------- ---+| Valeur | Longueur en octets ||----------------------------+--------------- ----|| 2025-05-21 10:15:30.1234567 | 8 |+----------------------------+----------------- --+

La valeur dans cet exemple a l'échelle maximale de 7 (parce que je déclare la variable comme datetime2(7) ), et il renvoie une longueur de 8 octets.

Cela semble contredire ce que déclare Microsoft sur le besoin d'un octet supplémentaire pour stocker la précision. Pour citer Microsoft, 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. .

S'il est vrai que nous semblons obtenir 8 octets pour le stockage des données , il semble qu'il nous manque 1 octet utilisé pour stocker la précision.

Cependant, si nous convertissons la valeur en varbinary nous obtenons une histoire différente.

Longueur en octets après conversion en 'varbinary'

Voici ce qui se passe si nous convertissons notre datetime2 valeur en varbinary :

DECLARE @d datetime2(7);SET @d ='2025-05-21 10:15:30.1234567';SELECT CONVERT(VARBINARY(10), @d) AS 'Value', DATALENGTH(CONVERT(VARBINARY( 10), @d)) AS 'Longueur en octets' ;

Résultat

+----------------------+-----------------------+| Valeur | Longueur en octets ||-----------------------+---------------------------|| 0x0787A311FC553F480B | 9 |+----------------------+--------------+

Dans ce cas, nous obtenons 9 octets.

Il s'agit d'une représentation hexadécimale de la datetime2 valeur. La valeur réelle de la date et de l'heure (et sa précision) est tout ce qui suit le 0x . Chaque paire de caractères hexadécimaux est un octet. Il y a 9 paires, donc 9 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 datetime2(3);SET @d ='2025-05-21 10:15:30.1234567';SELECT CONVERT(VARBINARY(10), @d) AS 'Value', DATALENGTH(CONVERT(VARBINARY( 10), @d)) AS 'Longueur en octets' ;

Résultat

+--------------------+----------------------------+| Valeur | Longueur en octets ||--------------------+------------------------------|| 0x034B8233023F480B | 8 |+--------------------+------------------+

Nous pouvons également voir que la longueur est réduite en conséquence.

Donc, dans ce cas, nos résultats correspondent parfaitement à la documentation Microsoft - un octet supplémentaire a été ajouté pour la précision.

De nombreux développeurs supposent que c'est ainsi que SQL Server stocke son datetime2 valeurs dans la base de données. Cependant, cette hypothèse semble incorrecte.

Données stockées dans une base de données

Dans cet exemple, je crée une base de données qui contient une table avec différents datetime2(n) Colonnes. J'utilise ensuite COL_LENGTH() pour renvoyer la longueur de chaque colonne, en octets. Après cela, j'y insère des valeurs, avant d'utiliser DBCC PAGE pour vérifier la taille de stockage que chaque datetime2 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 Datetime2Test ( d0 datetime2(0), d1 datetime2(1), d2 datetime2(2), d3 datetime2(3), d4 datetime2(4), d5 datetime2(5), d6 datetime2(6 ), d7 datetime2(7) );

Dans ce cas, je crée huit colonnes - une pour chaque échelle définie par l'utilisateur que nous pouvons utiliser avec datetime2(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 ( 'DateHeure2Test' , 'd0' ) AS 'd0', COL_LENGTH ( 'DateHeure2Test' , 'd1' ) AS 'd1', COL_LENGTH ( 'DateHeure2Test' , 'd2' ) AS 'd2', COL_LENGTH ( 'DateHeure2Test' , 'd3' ) AS 'd3', COL_LENGTH ( 'DateHeure2Test' , 'd4' ) AS 'd4', COL_LENGTH ( 'DateHeure2Test' , 'd5' ) AS 'd5', COL_LENGTH ( 'DateHeure2Test' , 'd6' ) AS 'd6', COL_LENGTH ( 'DateHeure2Test' , 'd7' ) AS 'd7'; 

Résultat :

+------+------+------+------+------+------+---- --+------+| d0 | d1 | d2 | d3 | d4 | d5 | d6 | d7 ||------+------+------+------+------+------+----- -+------|| 6 | 6 | 6 | 7 | 7 | 8 | 8 | 8 |+------+------+------+------+------+------+----- -+------+

Donc, encore une fois, nous ne semblons pas obtenir l'octet supplémentaire utilisé pour stocker la précision.

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 datetime2(7) ='2025-05-21 10:15:30.1234567';INSERT INTO Datetime2Test ( 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 DateHeure2Test ;

Résultat (en utilisant la sortie verticale) :

d0 | 2025-05-21 10:15:30d1 | 2025-05-21 10:15:30.1d2 | 2025-05-21 10:15:30.12d3 | 2025-05-21 10:15:30.123d4 | 2025-05-21 10:15:30.1235d5 | 2025-05-21 10:15:30.12346d6 | 2025-05-21 10:15:30.123457d7 | 2025-05-21 10:15:30.1234567

Comme prévu, les valeurs utilisent la précision précédemment spécifiée au niveau de la colonne.

Maintenant, avant d'utiliser DBCC PAGE() , nous devons savoir quel PagePID lui transmettre. Nous pouvons utiliser DBCC IND() pour le trouver.

Trouver le PagePID :

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

Résultat (en utilisant la sortie verticale) :

-[ ENREGISTREMENT 1 ]-------------------------PageFID | 1PagePID | 306IAMFID | NULLIAMPIDE | NULLObjectID | 1205579333IndexID | 0NuméroPartition | 1ID de partition | 72057594043039744iam_chain_type | DataPageType dans la ligne | 10NiveauIndex | NULLPageSuivanteFID | 0PIDPageSuivante | 0FIDPagePrécédente | 0PIDPagePrécédente | 0-[ ENREGISTREMENT 2 ]-------------------------PageFID | 1PagePID | 360IAMFID | 1IAMPID | 306ID d'objet | 1205579333IndexID | 0NuméroPartition | 1ID de partition | 72057594043039744iam_chain_type | DataPageType dans la ligne | 1NiveauIndex | 0FIDPageSuivante | 0PIDPageSuivante | 0FIDPagePrécédente | 0PIDPagePrécédente | 0

Cela 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 360 .

Nous pouvons maintenant prendre ce PagePID et l'utiliser dans ce qui suit :

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

Cela produit beaucoup de données, mais nous nous intéressons principalement à la partie suivante :

Slot 0 Colonne 1 Décalage 0x4 Longueur 6 Longueur (physique) 6d0 =2025-05-21 10:15:30 Slot 0 Colonne 2 Décalage 0xa Longueur 6 Longueur (physique) 6d1 =2025-05-21 10:15 :30.1 Emplacement 0 Colonne 3 Décalage 0x10 Longueur 6 Longueur (physique) 6d2 =2025-05-21 10:15:30.12 Emplacement 0 Colonne 4 Décalage 0x16 Longueur 7 Longueur (physique) 7d3 =2025-05-21 10:15:30.123 Emplacement 0 Colonne 5 Décalage 0x1d Longueur 7 Longueur (physique) 7d4 =2025-05-21 10:15:30.1235 Emplacement 0 Colonne 6 Décalage 0x24 Longueur 8 Longueur (physique) 8d5 =2025-05-21 10:15:30.12346 Emplacement 0 Colonne 7 Décalage 0x2c Longueur 8 Longueur (physique) 8d6 =2025-05-21 10:15:30.123457 Emplacement 0 Colonne 8 Décalage 0x34 Longueur 8 Longueur (physique) 8d7 =2025-05-21 10:15:30.1234567 

Il semble donc qu'il n'utilise pas l'octet supplémentaire pour la précision.

Mais examinons les données réelles avant de tirer des conclusions.

Les données réelles sont stockées dans cette partie du fichier de page :

 Dump Memory @ 0x000000041883a06000000000000000:10003C00 4290003F 480B95A2 053F480B D459383F .. <. B ..? H.zå.Ü0000000000000028:003f480b c1f63499 083f480b 87a311fc 553f480b .?H.Áö4..?H.‡£.üU?H.000000000000003C:080000 ... ... 

Comme vous pouvez le voir, rien de tout cela ne ressemble aux résultats que nous obtiendrions en convertissant le datetime2 valeur en varbinary . Mais c'est assez proche.

Voici à quoi cela ressemble si je supprime quelques éléments :

4290003f 480b95a2 053f480b d459383f480b4b82 33023f48 0bf31603 163f480b 7ae51edc003f480b c1f63499 083f480b 87a311fc 553f480b

Les chiffres hexadécimaux restants contiennent toutes nos données de date et d'heure, mais pas la précision . Cependant, nous devons réorganiser les espaces pour obtenir les valeurs réelles de chaque ligne.

Voici le résultat final. J'ai placé chaque valeur de date/heure sur une nouvelle ligne pour une meilleure lisibilité.

4290003f480b 95a2053f480b d459383f480b 4b8233023f480bf31603163f480b 7ae51edc003f480b c1f63499083f480b 87a311fc553f480b

Ce sont les valeurs hexadécimales réelles (moins la précision ) que nous obtiendrions si nous convertissions le datetime2 valeur en varbinary . Pour être sûr, allons de l'avant et faisons exactement cela :

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

Résultat (en utilisant la sortie verticale) :

d0 | 0x004290003F480Bd1 | 0x0195A2053F480Bd2 | 0x02D459383F480Bd3 | 0x034B8233023F480Bd4 | 0x04F31603163F480Bd5 | 0x057AE51EDC003F480Bd6 | 0x06C1F63499083F480Bd7 | 0x0787A311FC553F480B

Nous obtenons donc le même résultat - sauf qu'il a été ajouté avec précision.

Mais pour clarifier les choses, 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
4290003f480b 004290003F480B
95a2053f480b 0195A2053F480B
d459383f480b 02D459383F480B
4b8233023f480b 034B8233023F480B
f31603163f480b 04F31603163F480B
7ae51edc003f480b 057AE51EDC003F480B
c1f63499083f480b 06C1F63499083F480B
87a311fc553f480b 0787A311FC553F480B

Nous pouvons donc clairement 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 datetime2 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 semblerait logique, car il n'est pas nécessaire de stocker un octet supplémentaire avec chaque ligne si cela peut être spécifié au niveau de la colonne. Donc, si vous spécifiez, dites datetime2(7) au niveau de la colonne, chaque ligne sera alors datetime2(7) . Pas besoin de le répéter à chaque ligne.

Ronen Ariely est parvenu à la même conclusion dans son article mentionné ci-dessus.

Si vous avez un million de lignes avec datetime2(7) valeurs, stocker la précision avec chaque ligne nécessiterait 9 000 000 octets, contre seulement 8 000 001 si la précision est stockée une fois pour toute la colonne.

Cela renforce également le datetime2 de 's cas en le comparant à datetime . Même en utilisant le même nombre de décimales que datetime (c'est-à-dire 3), la datetime2 Le type de données utilise moins de stockage (au moins lorsqu'il est stocké dans une table avec plus d'une ligne). Et il le fait avec une plus grande précision. Une date/heure la valeur utilise 8 octets, alors que datetime2(3) utilise 7 octets (plus 1 octet de "précision" partagé sur toutes les lignes).