sql >> Base de Datos >  >> RDS >> Sqlserver

Comprender el tamaño de almacenamiento 'datetime2' en SQL Server

En este artículo comparto algunas observaciones que he tenido sobre el datetime2 el tamaño de almacenamiento del tipo de datos en SQL Server. Quizás aclare algunos puntos sobre el tamaño de almacenamiento real utilizado por este tipo de datos cuando se almacena en una base de datos.

En particular, miro lo siguiente:

  • Documentación de Microsoft
  • Datos almacenados en una variable
    • Longitud en bytes usando DATALENGTH()
    • Longitud en bytes usando DATALENGTH() después de convertir a varbinary
  • Datos almacenados en una base de datos
    • Longitud en bytes usando COL_LENGTH()
    • Longitud en bytes usando DBCC PAGE()

Algunos de ellos parecen contradecirse entre sí, y verá dos cantidades de tamaño de almacenamiento diferentes por el mismo valor, dependiendo de dónde mire.

Una fechahora2 El valor puede mostrar un tamaño de almacenamiento diferente, dependiendo de si está almacenado en una base de datos, como datetime2 variable, o convertido a varbinary .

Pero hay una explicación plausible para esto:depende de dónde esté la precisión. se está almacenando.

Durante mi investigación sobre este tema, encontré el artículo detallado de Ronen Ariely sobre cómo datetime2 se almacena en el archivo de datos muy informativo y me impulsó a ejecutar algunas pruebas similares en mi propio entorno de desarrollo y presentarlas aquí.

Documentación de Microsoft

Primero, veamos lo que dice la documentación oficial.

Documentación de Microsoft sobre el datetime2 el tipo de datos indica que su tamaño de almacenamiento es el siguiente:

6 bytes para una precisión inferior a 3.
7 bytes para una precisión de 3 o 4.
Todas las demás precisiones requieren 8 bytes.

Pero califica la tabla anterior con la siguiente declaración:

El primer byte de un datetime2 value almacena la precisión del valor, lo que significa el almacenamiento real requerido para un datetime2 El valor es el tamaño de almacenamiento indicado en la tabla anterior más 1 byte adicional para almacenar la precisión. Esto hace que el tamaño máximo de un datetime2 valor 9 bytes:1 byte almacena precisión más 8 bytes para el almacenamiento de datos con la máxima precisión.

Entonces, dada la información anterior, la conclusión obvia a sacar sería que la tabla podría/(¿debería?) escribirse de la siguiente manera:

7 bytes para precisión inferior a 3.
8 bytes para precisión 3 o 4.
Todas las demás precisiones requieren 9 bytes.

De esa forma, no tendrían que calificarlo con la información adicional sobre la precisión.

Pero no es tan simple como eso.

Datos almacenados en una variable

Primero, almacenemos un datetime2 valor en una variable y comprobar su tamaño de almacenamiento. Luego convertiré ese valor a varbinary y compruébalo de nuevo.

Longitud en Bytes usando DATALENGTH

Esto es lo que sucede si usamos DATALENGTH() función para devolver el número de bytes utilizados para un datetime2(7) valor:

DECLARE @d datetime2(7);
SET @d = '2025-05-21 10:15:30.1234567';
SELECT 
  @d AS 'Value',
  DATALENGTH(@d) AS 'Length in Bytes';

Resultado

+-----------------------------+-------------------+
| Value                       | Length in Bytes   |
|-----------------------------+-------------------|
| 2025-05-21 10:15:30.1234567 | 8                 |
+-----------------------------+-------------------+

El valor en este ejemplo tiene la escala máxima de 7 (porque declaro la variable como datetime2(7) ), y devuelve una longitud de 8 bytes.

Esto parece contradecir lo que afirma Microsoft sobre la necesidad de un byte adicional para almacenar la precisión. Para citar a Microsoft, Esto hace que el tamaño máximo de un datetime2 valor 9 bytes:1 byte almacena precisión más 8 bytes para el almacenamiento de datos con la máxima precisión. .

Si bien es cierto que parece que obtenemos 8 bytes para el almacenamiento de datos , parece que nos falta el 1 byte utilizado para almacenar la precisión.

Sin embargo, si convertimos el valor a varbinary tenemos una historia diferente.

Longitud en bytes después de convertir a 'varbinary'

Esto es lo que sucede si convertimos nuestro datetime2 valor a 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 'Length in Bytes';

Resultado

+----------------------+-------------------+
| Value                | Length in Bytes   |
|----------------------+-------------------|
| 0x0787A311FC553F480B | 9                 |
+----------------------+-------------------+

En este caso obtenemos 9 bytes.

Esta es una representación hexadecimal de datetime2 valor. El valor de fecha y hora real (y su precisión) es todo después de 0x . Cada par de caracteres hexadecimales es un byte. Hay 9 pares y, por lo tanto, 9 bytes. Esto se confirma cuando usamos DATALENGTH() para devolver la longitud en bytes.

En este ejemplo podemos ver que el primer byte es 07 . Esto representa la precisión (utilicé una escala de 7 y eso es lo que se muestra aquí).

Si cambio la escala, podemos ver que el primer byte cambia para coincidir con la escala:

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 'Length in Bytes';

Resultado

+--------------------+-------------------+
| Value              | Length in Bytes   |
|--------------------+-------------------|
| 0x034B8233023F480B | 8                 |
+--------------------+-------------------+

También podemos ver que la longitud se reduce en consecuencia.

Entonces, en este caso, nuestros resultados coinciden perfectamente con la documentación de Microsoft:se agregó un byte adicional para la precisión.

Muchos desarrolladores asumen que así es como SQL Server almacena su datetime2 valores en la base de datos. Sin embargo, esa suposición parece ser incorrecta.

Datos almacenados en una base de datos

En este ejemplo, creo una base de datos que contiene una tabla con varios datetime2(n) columnas Luego uso COL_LENGTH() para devolver la longitud de cada columna, en bytes. Después de eso, inserto valores en él, antes de usar DBCC PAGE para verificar el tamaño de almacenamiento que cada datetime2 el valor ocupa el archivo de la página.

Crear una base de datos:

CREATE DATABASE Test;

Crear una tabla:

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

En este caso, creo ocho columnas, una para cada escala definida por el usuario que podemos usar con datetime2(n) .

Ahora podemos verificar el tamaño de almacenamiento de cada columna.

Longitud en Bytes usando COL_LENGTH()

Utilice COL_LENGTH() para verificar la longitud (en bytes) de cada columna:

SELECT 
  COL_LENGTH ( 'Datetime2Test' , 'd0' ) AS 'd0',
  COL_LENGTH ( 'Datetime2Test' , 'd1' ) AS 'd1',
  COL_LENGTH ( 'Datetime2Test' , 'd2' ) AS 'd2',
  COL_LENGTH ( 'Datetime2Test' , 'd3' ) AS 'd3',
  COL_LENGTH ( 'Datetime2Test' , 'd4' ) AS 'd4',
  COL_LENGTH ( 'Datetime2Test' , 'd5' ) AS 'd5',
  COL_LENGTH ( 'Datetime2Test' , 'd6' ) AS 'd6',
  COL_LENGTH ( 'Datetime2Test' , 'd7' ) AS 'd7';  

Resultado:

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

Entonces, una vez más, parece que no usamos el byte adicional para almacenar la precisión.

Utilice la PÁGINA DBCC para comprobar los datos almacenados

Ahora usemos DBCC PAGE para encontrar el tamaño de almacenamiento real de los datos que almacenamos en esta tabla.

Primero, insertemos algunos datos:

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;

Ahora selecciona los datos (solo para comprobarlo):

SELECT * FROM Datetime2Test;

Resultado (usando salida vertical):

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

Como era de esperar, los valores usan la precisión que se especificó previamente en el nivel de columna.

Ahora, antes de usar DBCC PAGE() , necesitamos saber qué PagePID pasarle. Podemos usar DBCC IND() para encontrar eso.

Encuentra el PID de la página:

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

Resultado (usando salida vertical):

-[ RECORD 1 ]-------------------------
PageFID         | 1
PagePID         | 306
IAMFID          | NULL
IAMPID          | NULL
ObjectID        | 1205579333
IndexID         | 0
PartitionNumber | 1
PartitionID     | 72057594043039744
iam_chain_type  | In-row data
PageType        | 10
IndexLevel      | NULL
NextPageFID     | 0
NextPagePID     | 0
PrevPageFID     | 0
PrevPagePID     | 0
-[ RECORD 2 ]-------------------------
PageFID         | 1
PagePID         | 360
IAMFID          | 1
IAMPID          | 306
ObjectID        | 1205579333
IndexID         | 0
PartitionNumber | 1
PartitionID     | 72057594043039744
iam_chain_type  | In-row data
PageType        | 1
IndexLevel      | 0
NextPageFID     | 0
NextPagePID     | 0
PrevPageFID     | 0
PrevPagePID     | 0

Esto devuelve dos registros. Estamos interesados ​​en el PageType de 1 (el segundo registro). Queremos el PagePID de ese registro. En este caso, el PagePID es 360 .

Ahora podemos tomar ese PagePID y usarlo en lo siguiente:

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

Esto produce una gran cantidad de datos, pero estamos interesados ​​principalmente en la siguiente parte:

Slot 0 Column 1 Offset 0x4 Length 6 Length (physical) 6

d0 = 2025-05-21 10:15:30            

Slot 0 Column 2 Offset 0xa Length 6 Length (physical) 6

d1 = 2025-05-21 10:15:30.1          

Slot 0 Column 3 Offset 0x10 Length 6 Length (physical) 6

d2 = 2025-05-21 10:15:30.12         

Slot 0 Column 4 Offset 0x16 Length 7 Length (physical) 7

d3 = 2025-05-21 10:15:30.123   

Slot 0 Column 5 Offset 0x1d Length 7 Length (physical) 7

d4 = 2025-05-21 10:15:30.1235       

Slot 0 Column 6 Offset 0x24 Length 8 Length (physical) 8

d5 = 2025-05-21 10:15:30.12346      

Slot 0 Column 7 Offset 0x2c Length 8 Length (physical) 8

d6 = 2025-05-21 10:15:30.123457     

Slot 0 Column 8 Offset 0x34 Length 8 Length (physical) 8

d7 = 2025-05-21 10:15:30.1234567                                   

Entonces parece que no usa el byte extra para precisión.

Pero examinemos los datos reales antes de llegar a ninguna conclusión.

Los datos reales se almacenan en esta parte del archivo de la página:

Memory Dump @0x000000041883A060

0000000000000000:   10003c00 4290003f 480b95a2 053f480b d459383f  ..<.B..?H.•¢.?H.ÔY8?
0000000000000014:   480b4b82 33023f48 0bf31603 163f480b 7ae51edc  H.K‚3.?H.ó...?H.zå.Ü
0000000000000028:   003f480b c1f63499 083f480b 87a311fc 553f480b  .?H.Áö4..?H.‡£.üU?H.
000000000000003C:   080000                                        ...                                           ...   

Como puede ver, nada de eso se parece a los resultados que obtendríamos al convertir el datetime2 valor a varbinary . Pero está bastante cerca.

Esto es lo que parece si elimino algunas cosas:

4290003f 480b95a2 053f480b d459383f
480b4b82 33023f48 0bf31603 163f480b 7ae51edc
003f480b c1f63499 083f480b 87a311fc 553f480b

Los dígitos hexadecimales restantes contienen todos nuestros datos de fecha y hora, pero no la precisión . Sin embargo, debemos reorganizar los espacios para obtener los valores reales de cada fila.

Aquí está el resultado final. Coloqué cada valor de fecha/hora en una nueva línea para una mejor legibilidad.

4290003f480b 
95a2053f480b 
d459383f480b 
4b8233023f480b
f31603163f480b 
7ae51edc003f480b 
c1f63499083f480b 
87a311fc553f480b

Esos son los valores hexadecimales reales (menos la precisión ) que obtendríamos si convertimos el datetime2 valor a varbinary . Para estar seguros, sigamos adelante y hagamos precisamente eso:

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;

Resultado (usando salida vertical):

d0 | 0x004290003F480B
d1 | 0x0195A2053F480B
d2 | 0x02D459383F480B
d3 | 0x034B8233023F480B
d4 | 0x04F31603163F480B
d5 | 0x057AE51EDC003F480B
d6 | 0x06C1F63499083F480B
d7 | 0x0787A311FC553F480B

Entonces obtenemos el mismo resultado, excepto que se ha antepuesto con la precisión.

Pero para aclarar las cosas, aquí hay una tabla que compara los datos reales del archivo de página con los resultados de CONVERT() operación.

Datos del archivo de página CONVERTIR() datos
4290003f480b 004290003F480B
95a2053f480b 0195A2053F480B
d459383f480b 02D459383F480B
4b8233023f480b 034B8233023F480B
f31603163f480b 04F31603163F480B
7ae51edc003f480b 057AE51EDC003F480B
c1f63499083f480b 06C1F63499083F480B
87a311fc553f480b 0787A311FC553F480B

Entonces podemos ver claramente que el archivo de la página no almacena la precisión, pero el resultado convertido sí lo hace.

Resalté las partes de fecha y hora reales en rojo. También eliminé el 0x prefijo de los resultados convertidos, de modo que solo se muestren los datos de fecha/hora reales (junto con la precisión).

También tenga en cuenta que el hexadecimal no distingue entre mayúsculas y minúsculas, por lo que el hecho de que uno use minúsculas y el otro use mayúsculas no es un problema.

Conclusión

Al convertir un datetime2 valor a varbinary , necesita un byte adicional para almacenar la precisión. Necesita la precisión para interpretar la porción de tiempo (porque esto se almacena como un intervalo de tiempo, cuyo valor exacto dependerá de la precisión).

Cuando se almacena en una base de datos, la precisión se especifica una vez en el nivel de columna. Esto parecería lógico, ya que no hay necesidad de almacenar un byte adicional con cada fila si se puede especificar a nivel de columna. Entonces, si especifica decir, datetime2(7) en el nivel de columna, cada fila será datetime2(7) . No es necesario reiterar esto en cada fila.

Ronen Ariely llegó a la misma conclusión en su artículo mencionado anteriormente.

Si tiene un millón de filas con datetime2(7) valores, almacenar la precisión con cada fila requeriría 9 000 000 bytes, en comparación con solo 8 000 001 si la precisión se almacena una vez para toda la columna.

Esto también fortalece el datetime2 de caso de compararlo con datetime . Incluso cuando se usa el mismo número de lugares decimales que datetime (es decir, 3), el datetime2 tipo de datos utiliza menos almacenamiento (al menos cuando se almacena en una tabla con más de una fila). Y lo hace con mayor precisión. Una fechahora el valor usa 8 bytes, mientras que datetime2(3) usa 7 bytes (más 1 byte de "precisión" que se comparte en todas las filas).