Por que GETUTCDATE é anterior a SYSDATETIMEOFFSET?

8

Como alternativa, como a Microsoft tornou possível a viagem no tempo?

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

@Offseté definido antes @UTC , mas às vezes possui um valor posterior . (Tentei isso no SQL Server 2008 R2 e no SQL Server 2016. Você deve executá-lo algumas vezes para detectar as ocorrências suspeitas.)

Isso não parece ser simplesmente uma questão de arredondamento ou falta de precisão. (Na verdade, acho que o arredondamento é o que "corrige" o problema ocasionalmente). Os valores para uma execução de amostra são os seguintes:

  • Deslocamento
    • 2017-06-07 12: 01: 58.8801139 -05: 00
  • UTC
    • 2017-06-07 17: 01: 58.877
  • UTC do deslocamento:
    • 2017-06-07 17: 01: 58.880

Portanto, a precisão de data e hora permite o .880 como um valor válido.

Até os exemplos GETUTCDATE da Microsoft mostram que os valores SYS * são posteriores aos métodos mais antigos, apesar de terem sido selecionados 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  
*/

Presumo que isso ocorre porque eles vêm de diferentes informações subjacentes do sistema. Alguém pode confirmar e fornecer detalhes?

A documentação SYSDATETIMEOFFSET da Microsoft diz "O SQL Server obtém os valores de data e hora usando a API do Windows GetSystemTimeAsFileTime ()" (obrigado srutzky), mas a documentação GETUTCDATE é muito menos específica, dizendo apenas que o "valor é derivado do sistema operacional do computador no qual a instância do SQL Server está sendo executada ".

(Isso não é totalmente acadêmico. Encontrei um problema menor causado por isso. Eu estava atualizando alguns procedimentos para usar SYSDATETIMEOFFSET em vez de GETUTCDATE, na esperança de obter maior precisão no futuro, mas comecei a receber pedidos estranhos porque outros procedimentos eram ainda usando GETUTCDATE e ocasionalmente "pulando à frente" dos meus procedimentos convertidos nos logs.)

Riley Major
fonte
11
Não sei se existe outra explicação além de arredondar para baixo ou arredondar para cima. Quando você precisa arredondar qualquer valor para um de menor precisão e, em alguns casos, precisar arredondar para baixo e outros para arredondar (regras simples de matemática em jogo aqui), você pode esperar que, em alguns casos, o valor arredondado será menor ou maior que o valor original e mais preciso. Se você deseja garantir que o valor menos preciso seja sempre antes (ou sempre depois) do valor mais preciso, você sempre poderá manipulá-lo em 3 milissegundos usando DATEADD ().
Aaron Bertrand
(Também, porque não basta mudar todos os procedimentos ao mesmo tempo para usar o valor mais recente, mais preciso?)
Aaron Bertrand
Acho que não pode ser uma resposta tão simples quanto o arredondamento, porque se você converter o valor datetimeoffset de 2017-06-07 12: 01: 58.8801139 -05: 00 diretamente para datetime, escolhe 2017-06-07 12:01 : 58.880, não 07-06-2017 12: 01: 58.877. Portanto, GETUTCDATE arredonda internamente de maneira diferente ou são fontes diferentes.
Riley Maior
Eu preferiria fazê-los incrementalmente para alterar o mínimo possível de coisas por vez. scribnasium.com/2017/05/deploying-the-old-fashioned-way Vou eventualmente fazê-los em um lote ou viver com a inconsistência nos logs.
Riley Maior
2
Além disso, gostaria de acrescentar que a viagem no tempo é sempre possível em computadores . Você nunca deve programar esperando que o tempo esteja sempre avançando. O motivo é a deriva e as correções na hora do sistema, principalmente orientadas pelo Windows Time Service , mas também pelo ajuste do usuário. Derivação e correção de milissegundos é realmente encontrada com muita frequência.
Remus Rusanu

Respostas:

9

O problema é uma combinação de granularidade / precisão do tipo de dados e origem dos valores.

Primeiro, DATETIMEé preciso / granular apenas a cada 3 milissegundos. Assim, a conversão de um tipo de dados mais precisos, como DATETIMEOFFSETou DATETIME2não vai apenas arredondar para cima ou para baixo para o milissegundo mais próximo, poderia ser 2 milissegundos diferente.

Segundo, a documentação parece implicar uma diferença na origem dos valores. As funções SYS * usam as funções FileTime de alta precisão.

A documentação do SYSDATETIMEOFFSET indica:

O SQL Server obtém os valores de data e hora usando a API do Windows GetSystemTimeAsFileTime ().

enquanto a documentação GETUTCDATE indica:

Este valor é derivado do sistema operacional do computador no qual a instância do SQL Server está sendo executada.

Em seguida, na documentação Sobre o tempo , um gráfico mostra os dois (de vários) tipos a seguir:

  • Hora do sistema = "Ano, mês, dia, hora, segundo e milissegundo, obtidos do relógio interno do hardware."
  • Hora do arquivo = "O número de intervalos de 100 nanossegundos desde 1 de janeiro de 1601."

Pistas adicionais estão na documentação do .NET para a StopWatchclasse (ênfase em negrito e itálico):

  • Classe do cronômetro

    O cronômetro mede o tempo decorrido contando os tiques do cronômetro no mecanismo subjacente do cronômetro. Se o hardware e o sistema operacional instalados suportarem um contador de desempenho de alta resolução, a classe Stopwatch usará esse contador para medir o tempo decorrido. Caso contrário, a classe Stopwatch usa o timer do sistema para medir o tempo decorrido.

  • Campo Stopwatch.IsHighResolution

    O timer usado pela classe Stopwatch depende do hardware e do sistema operacional do sistema. IsHighResolution será verdadeiro se o cronômetro do cronômetro for baseado em um contador de desempenho de alta resolução. Caso contrário, IsHighResolution é false , o que indica que o cronômetro do cronômetro é baseado no cronômetro do sistema .

Portanto, existem diferentes "tipos" de tempos que possuem precisão e fontes diferentes.

Mas, mesmo que essa seja uma lógica muito vaga, testar os dois tipos de funções como fontes para o DATETIMEvalor prova isso. A seguinte adaptação da consulta da pergunta mostra esse comportamento:

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;

Devoluções:

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 você pode ver nos resultados acima, UTC e UTC2 são ambos DATETIMEtipos de dados. @UTC2é definido via SYSUTCDATETIME()e é definido depois @Offset(também extraído de uma função SYS *), mas antes do @UTCqual é definido via GETUTCDATE(). No entanto, @UTC2parece vir antes @UTC. A parte OFFSET disso não tem nenhuma relação com nada.

No entanto, para ser justo, isso ainda não é prova em sentido estrito. O @MartinSmith rastreou a GETUTCDATE()chamada e encontrou o seguinte:

insira a descrição da imagem aqui

Vejo três coisas interessantes nessa pilha de chamadas:

  1. É começa com uma chamada na GetSystemTime()qual retorna um valor que é preciso apenas até os milissegundos.
  2. Ele usa DateFromParts (ou seja, nenhuma fórmula para converter, basta usar as peças individuais).
  3. Há uma chamada para QueryPerformanceCounter na parte inferior. Este é um contador de diferenças de alta resolução que pode estar sendo usado como um meio de "correção". Eu já vi alguma sugestão de usar um tipo para ajustar o outro ao longo do tempo, pois longos intervalos lentamente ficam fora de sincronia (consulte Como obter o carimbo de data e hora da precisão de ticks no .NET / C #? No SO). Isso não aparece ao ligar SYSDATETIMEOFFSET.

Há muitas informações boas aqui sobre os diferentes tipos de horário, fontes diferentes, desvio, etc.: Adquirir carimbos de data e hora de alta resolução .

Realmente, não é apropriado comparar valores de diferentes precisões. Por exemplo, se você possui 2017-06-07 12:01:58.8770011e 2017-06-07 12:01:58.877, como você sabe que aquele com menos precisão é maior que, menor que ou igual ao valor com mais precisão? Compará-los pressupõe que o menos preciso seja realmente 2017-06-07 12:01:58.8770000, mas quem sabe se isso é verdade ou não? O tempo real poderia ter sido um 2017-06-07 12:01:58.8770005ou outro 2017-06-07 12:01:58.8770111.

No entanto, se você tiver DATETIMEtipos de dados, use as funções SYS * como origem, pois elas são mais precisas, mesmo se você perder alguma precisão, pois o valor é forçado a um tipo menos preciso. E nesse sentido, parece fazer mais sentido usar SYSUTCDATETIME()do que ligar SYSDATETIMEOFFSET()apenas para ajustá-lo via SWITCHOFFSET(@Offset, 0).

Solomon Rutzky
fonte
No meu sistema GETUTCDATEparece chamar GetSystemTimee SYSDATETIMEOFFSETchamadas GetSystemTimeAsFileTimeembora ambos aparentemente resumem à mesma coisa blogs.msdn.microsoft.com/oldnewthing/20131101-00/?p=2763
Martin Smith
11
@ MartinSmith (1/2) Passei algum tempo neste anúncio hoje estou sem tempo para fazer mais. Não tenho uma maneira fácil de testar a API do C ++ Win que está sendo chamada e é referenciada no blog que você mencionou. Posso converter entre o FileTime e o DateTime no .NET, mas o DateTime não é um SystemTime, pois pode lidar com a precisão total, mas foi inútil. Eu não posso provar / refutar que GetSystemTimeas chamadas GetSystemTimeAsFileTime , mas eu notei que coisas interessantes na pilha de chamadas que você postou o comentário pergunta: 1) ele usa DateFromParts então provavelmente truncado em ms (cont)
Solomon Rutzky
@MartinSmith (2/2) em vez de arredondado (como o SystemTime apenas desce para milissegundos) e 2) existe uma chamada para QueryPerformanceCounter na parte inferior. Este é um contador de diferenças de alta resolução que pode estar sendo usado como um meio de "correção". Vi algumas sugestões de uso de um tipo para ajustar o outro ao longo do tempo, pois longos intervalos lentamente ficam fora de sincronia. Há muitas informações boas aqui: Adquirir carimbos de tempo de alta resolução . Se eles começarem ao mesmo tempo, a perda será de 1 das 2 etapas acima.
Solomon Rutzky
Imaginei que Offset era um arenque vermelho, mas não criei um teste mais abstrato. Eu deveria ter visto os documentos da Microsoft mostrarem discrepância. Obrigado por confirmar isso.
Riley major
Meu desafio é que eu tenho vários procs fazendo isso. Todos eles usam GETUTCDATE agora. Se eu mudar algumas para qualquer uma das funções SYS * (com Offset ou UTC diretamente), elas começarão a ficar fora de ordem com as outras usando as funções GET *. Mas posso viver com isso ou mudar todos de uma vez. Não é um grande negócio. Isso apenas despertou minha curiosidade.
Riley major