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

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

En este artículo, analizo cómo datetimeoffset el tipo de datos se almacena en SQL Server y cómo puede obtener diferentes resultados de tamaño de almacenamiento informado, según lo que esté haciendo con él.

Esto es similar a lo que hice con datetime2 tipo 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()

Documentación de Microsoft

Documentación oficial de Microsoft sobre datetimeoffset tipo de datos indica que su tamaño de almacenamiento está entre 8 y 10 bytes, dependiendo de la precisión que se utilice.

Similar a datetime2(n) , puede usar datetimeoffset(n) para especificar la precisión, donde n es una escala entre 0 y 7.

Estos son los datos que presenta Microsoft para este tipo de datos:

Escala especificada Resultado (precisión, escala) Longitud de columna (bytes) Precisión de fracciones de segundo
desplazamiento de fecha y hora (34,7) 10 7
desplazamiento de fecha y hora (0) (26,0) 8 0-2
desplazamiento de fecha y hora (1) (28,1) 8 0-2
desplazamiento de fecha y hora (2) (29,2) 8 0-2
desplazamiento de fecha y hora (3) (30,3) 9 3-4
desplazamiento de fecha y hora (4) (31,4) 9 3-4
desplazamiento de fecha y hora (5) (32,5) 10 5-7
desplazamiento de fecha y hora (6) (33,6) 10 5-7
desplazamiento de fecha y hora (7) (34,7) 10 5-7

A los efectos de este artículo, estoy principalmente interesado en la Longitud de columna (bytes) columna. Esto nos dice cuántos bytes se utilizan para almacenar este tipo de datos en una base de datos.

La razón principal por la que quería escribir este artículo (y ejecutar los experimentos a continuación) es que la documentación de Microsoft no explica que se usa un byte adicional para la precisión (como lo hace en su documentación para datetime2 tipo de datos). En su documentación para datetime2 , dice:

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.

Pero la documentación para datetimeoffset no incluye este texto, y tampoco la hora documentación.

Esto me hizo preguntarme si hay una diferencia entre cómo estos tipos de datos almacenan sus valores. La lógica me dijo que deberían funcionar igual, ya que todos tienen una precisión definida por el usuario, pero quería averiguarlo.

La respuesta corta es sí, datetimeoffset parece funcionar igual que datetime2 (con respecto al byte extra), aunque no esté documentado como tal.

El resto del artículo incluye varios ejemplos en los que devuelvo el tamaño de almacenamiento de datetimeoffset valores en diferentes contextos.

Datos almacenados en una variable

Guardemos un datetimeoffset 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 datetimeoffset(7) valor:

DECLARAR @d datetimeoffset(7);SET @d ='2025-05-21 10:15:30.1234567 +07:00';SELECT @d AS 'Valor', DATALENGTH(@d) AS 'Longitud en bytes ';

Resultado

+-------------------------------------+--------- ----------+| Valor | Longitud en bytes ||-----------------------------------------------------+-------- -----------|| 2025-05-21 10:15:30.1234567 +07:00 | 10 |+-------------------------------------+---------- ---------+

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

No hay sorpresas aquí, este es el tamaño de almacenamiento exacto que la documentación de Microsoft indica que debería tener.

Sin embargo, si convertimos el valor a varbinary obtenemos un resultado diferente.

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

A algunos desarrolladores les gusta convertir datetimeoffset y fechahora2 variables a varbinary , porque es más representativo de cómo SQL Server lo almacena en la base de datos. Si bien esto es parcialmente cierto, los resultados no son exactamente iguales al valor almacenado (como verá más adelante).

Esto es lo que sucede si convertimos nuestro datetimeoffset valor a varbinary :

DECLARAR @d datetimeoffset(7);SET @d ='2025-05-21 10:15:30.1234567 +07:00';SELECCIONAR CONVERTIR(VARBINARIO(16), @d) COMO 'Valor', DATALENGTH( CONVERT(VARBINARY(16), @d)) AS 'Longitud en Bytes';

Resultado

+--------------------------+------------------- +| Valor | Longitud en bytes ||------------------------------------------+--------------------------------- -|| 0x0787CBB24F1B3F480BA401 | 11 |+--------------------------+-------------------+ 

En este caso obtenemos 11 bytes.

Esta es una representación hexadecimal del datetimeoffset valor. El valor de compensación de fecha y hora real (y su precisión) es todo lo que está después de 0x . Cada par de caracteres hexadecimales es un byte. Hay 11 pares y, por lo tanto, 11 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:

DECLARAR @d datetimeoffset(3);SET @d ='2025-05-21 10:15:30.1234567 +07:00';SELECCIONAR CONVERTIR(VARBINARIO(16), @d) COMO 'Valor', DATALENGTH( CONVERT(VARBINARY(16), @d)) AS 'Longitud en Bytes';

Resultado

+------------------------+-----------------------------------+| Valor | Longitud en bytes ||------------------------+-------------------| | 0x03CBFCB2003F480BA401 | 10 |+------------------------+-----------------------------------+

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

Datos almacenados en una base de datos

En este ejemplo, creo una base de datos con varios datetimeoffset(n) columnas, y luego use COL_LENGTH() para devolver la longitud de cada columna, en bytes. Luego inserto valores en las columnas, antes de usar DBCC PAGE para verificar el tamaño de almacenamiento que cada datetimeoffset el valor ocupa el archivo de la página.

Crear una base de datos:

Prueba CREAR BASE DE DATOS;

Crear una tabla:

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 desplazamiento de fecha y hora (7) );

En este caso, creo ocho columnas, una para cada escala definida por el usuario que podemos usar con datetimeoffset(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 ( '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' ) COMO 'd6', COL_LENGTH ( 'DatetimeoffsetTest' , 'd7' ) COMO 'd7'; 

Resultado:

+------+------+------+------+------+------+---- --+------+| d0 | d1 | d2 | d3 | d4 | d5 | d6 | d7 ||------+------+------+------+------+------+----- -+------|| 8 | 8 | 8 | 9 | 9 | 10 | 10 | 10 |+------+------+------+------+------+------+----- -+------+

Entonces, una vez más, obtenemos el mismo resultado que los estados de documentación que obtendremos. Esto es de esperar, porque la documentación establece explícitamente "Longitud de columna (bytes)", que es exactamente lo que estamos midiendo aquí.

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:

DECLARAR @d datetimeoffset(7) ='2025-05-21 10:15:30.1234567 +07:00';INSERT INTO DatetimeoffsetTest (d0, d1, d2, d3, d4, d5, d6, d7) SELECCIONAR @ d, @d, @d, @d, @d, @d, @d, @d;

Ahora selecciona los datos (solo para comprobarlo):

SELECCIONE * DESDE Prueba de compensación de fecha y hora;

Resultado (usando salida vertical):

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:00

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

Tenga en cuenta que mi sistema muestra ceros finales. El tuyo puede o no hacerlo. Independientemente, esto no afecta la precisión o exactitud real.

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('Prueba', 'dbo.DatetimeoffsetTest', 0);

Resultado (usando salida vertical):

-[ REGISTRO 1 ]-------------------------PageFID | 1PáginaPID | 307IAMFID | NULIAMPIDO | ID de objeto NULL | 1525580473ID de índice | 0Número de partición | 1ID de partición | 72057594043170816iam_chain_type | Tipo de página de datos en fila | 10Nivel de índice | NULLPágina siguienteFID | 0PáginaSiguientePID | 0PáginaAnteriorFID | 0PáginaAnteriorPID | 0-[ REGISTRO 2 ]-------------------------PageFID | 1PáginaPID | 376IAMFID | 1IAMPID | 307IdObjeto | 1525580473ID de índice | 0Número de partición | 1ID de partición | 72057594043170816iam_chain_type | Tipo de página de datos en fila | 1nivel de índice | 0PáginaSiguienteFID | 0PáginaSiguientePID | 0PáginaAnteriorFID | 0PáginaAnteriorPID | 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 376 .

Ahora podemos tomar ese PagePID y usarlo en lo siguiente:

TRACEON DBCC(3604, -1);PAGINA DBCC(Prueba, 1, 376, 3);

Ahora mismo estamos interesados ​​principalmente en la siguiente parte:

Ranura 0 Columna 1 Desplazamiento 0x4 Longitud 8 Longitud (física) 8d0 =2025-05-21 10:15:30 +07:00 Ranura 0 Columna 2 Desplazamiento 0xc Longitud 8 Longitud (física) 8d1 =2025-05-21 10:15:30.1 +07:00 Ranura 0 Columna 3 Desplazamiento 0x14 Longitud 8 Longitud (física) 8d2 =2025-05-21 10:15:30.12 +07:00 Ranura 0 Columna 4 Desplazamiento 0x1c Longitud 9 Longitud (física) 9d3 =2025-05-21 10:15:30.123 +07:00 Ranura 0 Columna 5 Desplazamiento 0x25 Longitud 9 Longitud (física) 9d4 =2025-05-21 10:15:30.1235 +07:00 Ranura 0 Columna 6 Desplazamiento 0x2e Longitud 10 Longitud (física) 10d5 =2025-05-21 10:15:30.12346 +07:00 Ranura 0 Columna 7 Desplazamiento 0x38 Longitud 10 Longitud (física) 10d6 =2025-05-21 10:15:30.123457 +07:00 Ranura 0 Columna 8 Desplazamiento 0x42 Longitud 10 Longitud (física) 10d7 =2025-05-21 10:15:30.1234567 +07:00 

Así que obtenemos el mismo resultado de nuevo. Exactamente como dice la documentación.

Mientras estamos aquí, examinemos los datos:los valores reales de fecha/hora tal como están almacenados en SQL Server.

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

 Volcado de memoria @0x000000041951A0600000000000000000000:10004C00 D22D003F 480BA401 35CA013F 480BA401 ..L.ò-. H.¤.óßý0000000000000028:063F480B A4017ABF EA45003F 480BA401 C17A2BBB. ..

Eso todavía incluye algunos bits adicionales. Eliminemos algunas cosas, para que solo queden nuestros valores de fecha y hora:

480ba4014e6113f 480ba401

Los dígitos hexadecimales restantes contienen todos nuestros datos de fecha y hora, pero no la precisión . Sin embargo, están organizados en fragmentos de 4 bytes, por lo que debemos reorganizar los espacios para obtener los valores individuales.

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

35ca013f480ba40114e6113f480ba401 cbfcb2003f480ba401f3dffd063f480ba4017abfea45003f480ba401

Esos son los valores hexadecimales reales (menos la precisión ) que obtendríamos si convertimos el datetimeoffset valor a varbinary . Así:

SELECCIONE CONVERTIR(VARBINARIO(16), d0) COMO 'd0', CONVERTIR(VARBINARIO(16), d1) COMO 'd1', CONVERTIR(VARBINARIO(16), d2) COMO 'd2', CONVERTIR(VARBINARIO( 16), d3) COMO 'd3', CONVERTIR(VARBINARIO(16), d4) COMO 'd4', CONVERTIR(VARBINARIO(16), d5) COMO 'd5', CONVERTIR(VARBINARIO(16), d6) COMO 'd6 ', CONVERTIR(VARBINARIO(16), d7) COMO 'd7'FROM DatetimeoffsetTest;

Resultado (usando salida vertical):

d0 | 0x00D22D003F480BA401d1 | 0x0135CA013F480BA401d2 | 0x0214E6113F480BA401d3 | 0x03CBFCB2003F480BA401d4 | 0x04F3DFFD063F480BA401d5 | 0x057ABFEA45003F480BA401d6 | 0x06C17A2BBB023F480BA401d7 | 0x0787CBB24F1B3F480BA401

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

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
d22d003f480ba401 00D22D003F480BA401
35ca013f480ba401 0135CA013F480BA401
14e6113f480ba401 0214E6113F480BA401
cbfcb2003f480ba401 03CBFCB2003F480BA401
f3dffd063f480ba401 04F3DFFD063F480BA401
7abfea45003f480ba401 057ABFEA45003F480BA401
c17a2bbb023f480ba401 06C17A2BBB023F480BA401
87cbb24f1b3f480ba401 0787CBB24F1B3F480BA401

Entonces podemos ver 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 datetimeoffset 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 parece lógico, ya que no es necesario agregar la precisión a cada fila cuando todas las filas usan la misma precisión de todos modos. Eso requeriría un byte adicional para cada fila, lo que aumentaría innecesariamente los requisitos de almacenamiento.