¿Por qué GETUTCDATE es anterior a SYSDATETIMEOFFSET?


8

Alternativamente, ¿cómo hizo Microsoft posible viajar en el tiempo?

Considera este código:

DECLARE @Offset datetimeoffset = sysdatetimeoffset();
DECLARE @UTC datetime = getUTCdate();
DECLARE @UTCFromOffset datetime = CONVERT(datetime,SWITCHOFFSET(@Offset,0));
SELECT
    Offset = @Offset,
    UTC = @UTC,
    UTCFromOffset = @UTCFromOffset,
    TimeTravelPossible = CASE WHEN @UTC < @UTCFromOffset THEN 1 ELSE 0 END;

@Offsetse establece antes @UTC , pero a veces tiene un valor posterior . (He intentado esto en SQL Server 2008 R2 y SQL Server 2016. Debe ejecutarlo varias veces para detectar los casos sospechosos).

Esto no parece ser simplemente una cuestión de redondeo o falta de precisión. (De hecho, creo que el redondeo es lo que "soluciona" el problema ocasionalmente). Los valores para una ejecución de muestra son los siguientes:

  • Compensar
    • 2017-06-07 12: 01: 58.8801139 -05: 00
  • UTC
    • 2017-06-07 17: 01: 58.877
  • UTC desde compensación:
    • 2017-06-07 17: 01: 58.880

Entonces, la precisión de fecha y hora permite que el .880 sea un valor válido.

Incluso los ejemplos GETUTCDATE de Microsoft muestran que los valores SYS * son posteriores a los métodos anteriores, a pesar de haber sido SELECCIONADOS anteriormente :

SELECT 'SYSDATETIME()      ', SYSDATETIME();  
SELECT 'SYSDATETIMEOFFSET()', SYSDATETIMEOFFSET();  
SELECT 'SYSUTCDATETIME()   ', SYSUTCDATETIME();  
SELECT 'CURRENT_TIMESTAMP  ', CURRENT_TIMESTAMP;  
SELECT 'GETDATE()          ', GETDATE();  
SELECT 'GETUTCDATE()       ', GETUTCDATE();  
/* Returned:  
SYSDATETIME()            2007-05-03 18:34:11.9351421  
SYSDATETIMEOFFSET()      2007-05-03 18:34:11.9351421 -07:00  
SYSUTCDATETIME()         2007-05-04 01:34:11.9351421  
CURRENT_TIMESTAMP        2007-05-03 18:34:11.933  
GETDATE()                2007-05-03 18:34:11.933  
GETUTCDATE()             2007-05-04 01:34:11.933  
*/

Supongo que esto se debe a que provienen de información del sistema subyacente diferente. ¿Alguien puede confirmar y proporcionar detalles?

La documentación SYSDATETIMEOFFSET de Microsoft dice que "SQL Server obtiene los valores de fecha y hora utilizando la API de Windows GetSystemTimeAsFileTime ()" (gracias srutzky), pero su documentación GETUTCDATE es mucho menos específica, diciendo solo que el "valor se deriva del sistema operativo del equipo en el que se ejecuta la instancia de SQL Server ".

(Esto no es completamente académico. Me encontré con un problema menor causado por esto. Estaba actualizando algunos procedimientos para usar SYSDATETIMEOFFSET en lugar de GETUTCDATE, con la esperanza de una mayor precisión en el futuro, pero comencé a obtener pedidos extraños porque otros procedimientos eran sigo usando GETUTCDATE y ocasionalmente "saltando" de mis procedimientos convertidos en los registros).


1
No sé si hay otra explicación que no sea redondear hacia abajo o redondear hacia arriba. Cuando tiene que redondear cualquier valor a uno de menor precisión, y cuando en algunos casos tiene que redondear hacia abajo, y otros tiene que redondear (reglas matemáticas simples en juego aquí), puede esperar que en algunos casos el valor redondeado será más bajo o más alto que el valor original y más preciso. Si desea asegurarse de que el valor menos preciso sea siempre antes (o siempre después) del más preciso, siempre puede manipularlo en 3 milisegundos con DATEADD ().
Aaron Bertrand

(Además, ¿por qué no simplemente cambiar TODOS los procedimientos al mismo tiempo para usar el valor más nuevo y preciso?)
Aaron Bertrand

No creo que pueda ser una respuesta tan simple como el redondeo porque si convierte el valor de compensación de fecha y hora de 2017-06-07 12: 01: 58.8801139 -05: 00 directamente a fecha y hora, selecciona 2017-06-07 12:01 : 58.880, no 2017-06-07 12: 01: 58.877. Entonces, GETUTCDATE se redondea internamente de manera diferente o son fuentes diferentes.
Riley Major

Prefiero hacerlos de forma incremental para cambiar la menor cantidad de cosas posible a la vez. scribnasium.com/2017/05/deploying-the-old-fashioned-way Eventualmente los haré en un lote o viviré con la inconsistencia en los registros.
Riley Major

2
Además, me gustaría agregar que el viaje en el tiempo siempre es posible en las computadoras . Nunca debe codificar esperando que el tiempo siempre avance. La razón es la deriva y las correcciones a la hora del sistema, principalmente impulsadas por el Servicio de hora de Windows , pero también por el ajuste del usuario. La deriva y la corrección de milisegundos se encuentran con mucha frecuencia.
Remus Rusanu

Respuestas:


9

El problema es una combinación de granularidad / precisión de tipo de datos y fuente de los valores.

Primero, DATETIMEsolo es preciso / granular cada 3 milisegundos. Por lo tanto, la conversión de un tipo de datos más precisos tales como DATETIMEOFFSETo DATETIME2no a la vuelta de arriba o hacia abajo al milisegundo más cercano, que podría ser de 2 milisegundos diferente.

Segundo, la documentación parece implicar una diferencia en el origen de los valores. Las funciones SYS * utilizan las funciones FileTime de alta precisión.

La documentación de SYSDATETIMEOFFSET establece:

SQL Server obtiene los valores de fecha y hora utilizando la API de Windows GetSystemTimeAsFileTime ().

mientras que la documentación de GETUTCDATE establece:

Este valor se deriva del sistema operativo de la computadora en la que se ejecuta la instancia de SQL Server.

Luego, en la documentación Acerca del tiempo , un gráfico muestra los siguientes dos (de varios) tipos:

  • Hora del sistema = "Año, mes, día, hora, segundo y milisegundos, tomados del reloj interno del hardware".
  • File Time = "El número de intervalos de 100 nanosegundos desde el 1 de enero de 1601."

Hay pistas adicionales en la documentación de .NET para la StopWatchclase (énfasis en negrita cursiva mía):

  • Clase de cronómetro

    El cronómetro mide el tiempo transcurrido contando los tics del temporizador en el mecanismo del temporizador subyacente. Si el hardware y el sistema operativo instalados admiten un contador de rendimiento de alta resolución, la clase de cronómetro usa ese contador para medir el tiempo transcurrido. De lo contrario, la clase Cronómetro usa el temporizador del sistema para medir el tiempo transcurrido.

  • Stopwatch.IsHighResolution Field

    El temporizador utilizado por la clase Cronómetro depende del hardware del sistema y del sistema operativo. IsHighResolution es verdadero si el cronómetro se basa en un contador de rendimiento de alta resolución. De lo contrario, IsHighResolution es falso , lo que indica que el temporizador del cronómetro se basa en el temporizador del sistema .

Por lo tanto, hay diferentes "tipos" de veces que tienen tanto precisiones diferentes como diferentes fuentes.

Pero, incluso si esa es una lógica muy flexible, probar ambos tipos de funciones como fuentes para el DATETIMEvalor lo demuestra. La siguiente adaptación de la consulta de la pregunta muestra este comportamiento:

DECLARE @Offset DATETIMEOFFSET = SYSDATETIMEOFFSET(),
        @UTC2 DATETIME = SYSUTCDATETIME(),
        @UTC DATETIME = GETUTCDATE();
DECLARE @UTCFromOffset DATETIME = CONVERT(DATETIME, SWITCHOFFSET(@Offset, 0));
SELECT
    Offset = @Offset,
    UTC2 = @UTC2,
    UTC = @UTC,
    UTCFromOffset = @UTCFromOffset,
    TimeTravelPossible = CASE WHEN @UTC < @UTCFromOffset THEN 1 ELSE 0 END,
    TimeTravelPossible2 = CASE WHEN @UTC2 < @UTCFromOffset THEN 1 ELSE 0 END;

Devoluciones:

Offset                 2017-06-07 17:50:49.6729691 -04:00
UTC2                   2017-06-07 21:50:49.673
UTC                    2017-06-07 21:50:49.670
UTCFromOffset          2017-06-07 21:50:49.673
TimeTravelPossible     1
TimeTravelPossible2    0

Como puede ver en los resultados anteriores, UTC y UTC2 son ambos DATETIMEtipos de datos. @UTC2se establece a través de SYSUTCDATETIME()y se establece después @Offset(también tomado de una función SYS *), pero antes de lo @UTCcual se establece a través de GETUTCDATE(). Sin embargo, @UTC2parece venir antes @UTC. La parte OFFSET de esto no tiene nada que ver con nada.

SIN EMBARGO, para ser justos, esto todavía no es una prueba en sentido estricto. @MartinSmith rastreó la GETUTCDATE()llamada y encontró lo siguiente:

ingrese la descripción de la imagen aquí

Veo tres cosas interesantes en esta pila de llamadas:

  1. Se inicia con una llamada a la GetSystemTime()que devuelve un valor que solo es preciso hasta los milisegundos.
  2. Utiliza DateFromParts (es decir, no hay fórmula para convertir, solo use las piezas individuales).
  3. Hay una llamada a QueryPerformanceCounter hacia abajo. Este es un contador de diferencia de alta resolución que podría usarse como un medio de "corrección". He visto alguna sugerencia de usar un tipo para ajustar el otro con el tiempo a medida que los intervalos largos se desincronizan lentamente (consulte Cómo obtener la marca de tiempo de precisión de marca en .NET / C #? En SO). Esto no aparece al llamar SYSDATETIMEOFFSET.

Aquí hay mucha buena información sobre los diferentes tipos de tiempo, diferentes fuentes, deriva, etc.: Adquisición de marcas de tiempo de alta resolución .

Realmente, no es apropiado comparar valores de diferentes precisiones. Por ejemplo, si tiene 2017-06-07 12:01:58.8770011y 2017-06-07 12:01:58.877, entonces, ¿cómo sabe que el que tiene menos precisión es mayor, menor o igual que el valor con mayor precisión? Compararlos supone que el menos preciso es en realidad 2017-06-07 12:01:58.8770000, pero ¿quién sabe si eso es cierto o no? El tiempo real podría haber sido 2017-06-07 12:01:58.8770005o 2017-06-07 12:01:58.8770111.

Sin embargo, si tiene DATETIMEtipos de datos, debe usar las funciones SYS * como fuente, ya que son más precisas, incluso si pierde algo de precisión, ya que el valor se fuerza a un tipo menos preciso. Y en ese sentido, parece tener más sentido usar en SYSUTCDATETIME()lugar de llamar SYSDATETIMEOFFSET()solo para ajustarlo a través de SWITCHOFFSET(@Offset, 0).


En mi sistema GETUTCDATEparece llamar GetSystemTimey SYSDATETIMEOFFSETllamar, GetSystemTimeAsFileTimeaunque aparentemente ambos se reducen a lo mismo blogs.msdn.microsoft.com/oldnewthing/20131101-00/?p=2763
Martin Smith

1
@MartinSmith (1/2) He pasado algún tiempo en este anuncio hoy no tengo tiempo para hacer más. No tengo una manera fácil de probar la API de Win de C ++ que se llama y se menciona en el blog que mencionaste. Puedo convertir de ida y vuelta entre FileTime y DateTime en .NET, pero DateTime no es un SystemTime, ya que puede manejar la precisión completa, pero fue sin pérdidas. No puedo probar / refutar esas GetSystemTimellamadas GetSystemTimeAsFileTime , pero noté cosas interesantes en la pila de llamadas que publicaste en el comentario de la pregunta: 1) usa DateFromParts, por lo que probablemente se truncó en ms (cont)
Solomon Rutzky

@MartinSmith (2/2) en lugar de redondeado (ya que SystemTime solo se reduce a milisegundos), y 2) hay una llamada a QueryPerformanceCounter hacia la parte inferior. Este es un contador de diferencia de alta resolución que podría usarse como un medio de "corrección". He visto alguna sugerencia de usar un tipo para ajustar el otro con el tiempo, ya que los intervalos largos se desincronizan lentamente. Aquí hay mucha buena información: adquisición de marcas de tiempo de alta resolución . Si comienzan al mismo tiempo, la pérdida es de 1 de los 2 pasos anteriores.
Solomon Rutzky

Supuse que Offset era un arenque rojo, pero no creé una prueba más abstracta. Debería haberlo visto una vez que los documentos de Microsoft muestran la discrepancia. Gracias por confirmar eso.
Riley Major

Mi desafío es que tengo varios procs haciendo esto. Todos usan GETUTCDATE ahora. Si cambio algunas a cualquiera de las funciones SYS * (con Offset o UTC directamente), comienzan a desordenarse con las demás utilizando las funciones GET *. Pero puedo vivir con eso o cambiarlos todos a la vez. No es un gran problema. Simplemente despertó mi curiosidad.
Riley Major
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.