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
- Longitud en bytes usando
- Datos almacenados en una base de datos
- Longitud en bytes usando
COL_LENGTH()
- Longitud en bytes usando
DBCC PAGE()
- Longitud en bytes usando
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.K3.?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).