Mengapa GETUTCDATE lebih awal dari SYSDATETIMEOFFSET?

8

Atau, bagaimana Microsoft memungkinkan perjalanan waktu?

Pertimbangkan kode ini:

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;

@Offsetdiatur sebelumnya @UTC , namun terkadang memiliki nilai nanti . (Saya sudah mencoba ini di SQL Server 2008 R2 dan SQL Server 2016. Anda harus menjalankannya beberapa kali untuk menangkap kejadian yang mencurigakan.)

Ini tampaknya bukan hanya masalah pembulatan atau kurangnya presisi. (Sebenarnya, saya pikir pembulatan adalah apa yang "memperbaiki" masalah sesekali.) Nilai-nilai untuk menjalankan sampel adalah sebagai berikut:

  • Mengimbangi
    • 2017-06-07 12: 01: 58.8801139 -05: 00
  • UTC
    • 2017-06-07 17: 01: 58.877
  • UTC Dari Offset:
    • 2017-06-07 17: 01: 58.880

Jadi, ketelitian datetime memungkinkan .880 sebagai nilai yang valid.

Bahkan contoh Microsoft GETUTCDATE menunjukkan nilai SYS * lebih lambat dari metode yang lebih lama, meskipun telah SELECTed sebelumnya :

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  
*/

Saya kira ini karena mereka berasal dari sistem informasi dasar yang berbeda. Adakah yang bisa mengkonfirmasi dan memberikan rincian?

Dokumentasi SYSDATETIMEOFFSET Microsoft mengatakan "SQL Server memperoleh nilai tanggal dan waktu dengan menggunakan GetSystemTimeAsFileTime () Windows API" (terima kasih srutzky), tetapi dokumentasi GETUTCDATE mereka jauh lebih spesifik, hanya mengatakan bahwa nilai "berasal dari sistem operasi sistem komputer tempat instance dari SQL Server berjalan ".

(Ini bukan sepenuhnya akademis. Saya mengalami masalah kecil yang disebabkan oleh ini. Saya memperbarui beberapa prosedur untuk menggunakan SYSDATETIMEOFFSET alih-alih GETUTCDATE, dengan harapan untuk presisi yang lebih besar di masa mendatang, tetapi saya mulai mendapatkan pesanan aneh karena prosedur lain dilakukan masih menggunakan GETUTCDATE dan kadang-kadang "melompati" prosedur saya yang dikonversi dalam log.)

Riley Major
sumber
1
Saya tidak tahu bahwa ada penjelasan selain pembulatan ke bawah atau pembulatan ke atas. Ketika Anda harus membulatkan nilai apa pun ke salah satu presisi yang lebih rendah, dan ketika dalam beberapa kasus Anda harus membulatkan ke bawah, dan yang lain Anda harus mengumpulkan (aturan matematika sederhana dalam permainan di sini), Anda dapat mengharapkan bahwa dalam beberapa kasus nilai bulat akan lebih rendah atau lebih tinggi dari nilai aslinya, lebih tepat. Jika Anda ingin memastikan bahwa nilai yang kurang tepat selalu sebelum (atau selalu setelah) yang lebih tepat, Anda selalu dapat memanipulasinya dengan 3 milidetik menggunakan DATEADD ().
Aaron Bertrand
(Juga, mengapa tidak hanya mengubah SEMUA prosedur pada saat yang sama untuk menggunakan nilai yang lebih baru dan lebih presisi?)
Aaron Bertrand
Saya tidak berpikir itu bisa sesederhana jawaban pembulatan karena jika Anda mengonversi nilai setel datetimeoffset 2017-06-07 12: 01: 58.8801139 -05: 00 langsung ke datetime, ia memilih 2017-06-07 12:01 : 58.880, bukan 2017-06-07 12: 01: 58.877. Jadi, GETUTCDATE secara internal berputar secara berbeda atau mereka adalah sumber yang berbeda.
Riley Major
Saya lebih suka melakukannya secara bertahap untuk mengubah sesedikit mungkin hal sekaligus. scribnasium.com/2017/05/deploying-the-old-fashioned-way saya akhirnya akan melakukannya secara batch atau hidup dengan inkonsistensi dalam log.
Riley Major
2
Selain itu saya ingin menambahkan waktu perjalanan selalu mungkin di komputer . Anda tidak boleh kode berharap bahwa waktu selalu maju. Alasannya adalah penyimpangan dan koreksi waktu sistem, sebagian besar didorong oleh Layanan Waktu Windows , tetapi juga dari penyesuaian pengguna. Penyimpangan dan koreksi milidetik sebenarnya sangat sering ditemui.
Remus Rusanu

Jawaban:

9

Masalahnya adalah kombinasi rincian data / akurasi dan sumber nilai.

Pertama, DATETIMEhanya akurat / terperinci untuk setiap 3 milidetik. Oleh karena itu, mengubah dari tipe data yang lebih tepat seperti DATETIMEOFFSETatau DATETIME2tidak hanya membulatkan ke atas atau ke bawah ke milidetik terdekat, bisa jadi berbeda 2 milidetik.

Kedua, dokumentasi tampaknya menyiratkan perbedaan dari mana nilai-nilai itu berasal. Fungsi SYS * menggunakan fungsi FileTime presisi tinggi.

Dokumentasi SYSDATETIMEOFFSET menyatakan:

SQL Server memperoleh nilai tanggal dan waktu dengan menggunakan API Windows GetSystemTimeAsFileTime ().

sedangkan dokumentasi GETUTCDATE menyatakan:

Nilai ini berasal dari sistem operasi komputer tempat instance SQL Server berjalan.

Kemudian, dalam dokumentasi About Time , bagan menunjukkan dua (dari beberapa) jenis berikut:

  • System Time = "Tahun, bulan, hari, jam, detik, dan milidetik, diambil dari jam perangkat keras internal."
  • File Time = "Jumlah interval 100 nanodetik sejak 1 Januari 1601."

Petunjuk tambahan ada dalam dokumentasi .NET untuk StopWatchkelas (penekanan pada huruf tebal miring):

  • Kelas Stopwatch

    Stopwatch mengukur waktu yang berlalu dengan menghitung kutu timer dalam mekanisme timer yang mendasarinya. Jika perangkat keras dan sistem operasi yang diinstal mendukung penghitung kinerja resolusi tinggi, maka kelas Stopwatch menggunakan penghitung itu untuk mengukur waktu yang berlalu. Jika tidak, kelas Stopwatch menggunakan timer sistem untuk mengukur waktu yang berlalu.

  • Stopwatch.IsHighResolution Field

    Timer yang digunakan oleh kelas Stopwatch tergantung pada perangkat keras sistem dan sistem operasi. IsHighResolution benar jika timer Stopwatch didasarkan pada penghitung kinerja resolusi tinggi. Jika tidak, IsHighResolution salah , yang menunjukkan bahwa penghitung waktu Stopwatch didasarkan pada pengatur waktu sistem .

Oleh karena itu, ada "jenis" waktu yang berbeda yang memiliki presisi dan sumber yang berbeda pula.

Tetapi, bahkan jika itu adalah logika yang sangat longgar, menguji kedua jenis fungsi sebagai sumber untuk DATETIMEnilai membuktikannya. Adaptasi kueri dari pertanyaan berikut menunjukkan perilaku ini:

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;

Pengembalian:

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

Seperti yang Anda lihat dalam hasil di atas, UTC dan UTC2 keduanya DATETIMEtipe data. @UTC2diatur melalui SYSUTCDATETIME()dan diatur setelah @Offset(juga diambil dari fungsi SYS *), tetapi sebelum @UTCyang diatur melalui GETUTCDATE(). Namun, @UTC2tampaknya datang sebelumnya @UTC. Bagian OFFSET ini sama sekali tidak terkait dengan apa pun.

NAMUN, untuk bersikap adil, ini masih bukan bukti dalam arti yang ketat. @MartinSmith melacak GETUTCDATE()panggilan dan menemukan yang berikut:

masukkan deskripsi gambar di sini

Saya melihat tiga hal menarik dalam tumpukan panggilan ini:

  1. Dimulai dengan panggilan GetSystemTime()yang mengembalikan nilai yang hanya tepat hingga milidetik.
  2. Menggunakan DateFromParts (yaitu tidak ada rumus untuk dikonversi, cukup gunakan masing-masing bagian)
  3. Ada panggilan ke QueryPerformanceCounter ke bagian bawah. Ini adalah penghitung perbedaan resolusi tinggi yang dapat digunakan sebagai sarana "koreksi". Saya telah melihat beberapa saran untuk menggunakan satu jenis untuk menyesuaikan yang lain dari waktu ke waktu sebagai interval lama perlahan-lahan tidak sinkron (lihat Bagaimana cara mendapatkan cap waktu presisi kutu dalam. NET / C #? Pada SO). Ini tidak muncul saat menelepon SYSDATETIMEOFFSET.

Ada banyak info bagus di sini mengenai berbagai jenis waktu, sumber berbeda, pergeseran, dll: Memperoleh perangko waktu resolusi tinggi .

Sungguh, tidak tepat untuk membandingkan nilai-nilai dari berbagai ketentuan. Misalnya, jika Anda memiliki 2017-06-07 12:01:58.8770011dan 2017-06-07 12:01:58.877, lalu bagaimana Anda tahu bahwa yang memiliki presisi kurang lebih besar, kurang dari, atau sama dengan nilai dengan presisi lebih? Membandingkan mereka mengasumsikan bahwa yang kurang tepat sebenarnya 2017-06-07 12:01:58.8770000, tetapi siapa yang tahu apakah itu benar atau tidak? Waktu sebenarnya bisa berupa 2017-06-07 12:01:58.8770005atau 2017-06-07 12:01:58.8770111.

Namun, jika Anda memiliki DATETIMEtipe data, maka Anda harus menggunakan fungsi SYS * sebagai sumber karena lebih akurat, bahkan jika Anda kehilangan beberapa presisi karena nilainya dipaksa menjadi tipe yang kurang tepat. Dan sejalan dengan itu, tampaknya lebih masuk akal untuk menggunakan SYSUTCDATETIME()daripada SYSDATETIMEOFFSET()hanya menelepon untuk menyesuaikannya SWITCHOFFSET(@Offset, 0).

Solomon Rutzky
sumber
Pada sistem saya GETUTCDATEtampaknya memanggil GetSystemTimedan SYSDATETIMEOFFSETmemanggil GetSystemTimeAsFileTimemeskipun keduanya tampaknya mendidih ke hal yang sama blogs.msdn.microsoft.com/oldnewthing/20131101-00/?p=2763
Martin Smith
1
@ MartinSmith (1/2) Saya telah menghabiskan beberapa waktu untuk iklan ini, saya kehabisan waktu hari ini untuk melakukan lebih banyak lagi. Saya tidak memiliki cara mudah untuk menguji C ++ Win API yang sedang dipanggil dan direferensikan di blog yang Anda sebutkan. Saya dapat mengkonversi bolak-balik antara FileTime dan DateTime di .NET, tetapi DateTime bukan SystemTime karena dapat menangani presisi penuh, tapi itu tidak ada untungnya. Saya tidak dapat membuktikan / menolak GetSystemTimepanggilan itu GetSystemTimeAsFileTime , tetapi saya memang memperhatikan hal-hal menarik dalam tumpukan panggilan yang Anda posting di komentar pertanyaan: 1) ia menggunakan DateFromParts sehingga kemungkinan besar terpotong menjadi ms (lanjutan)
Solomon Rutzky
@ MartinSmith (2/2) bukannya bulat (karena SystemTime hanya turun ke milidetik), dan 2) ada panggilan ke QueryPerformanceCounter ke arah bawah. Ini adalah penghitung perbedaan resolusi tinggi yang dapat digunakan sebagai sarana "koreksi". Saya telah melihat beberapa saran untuk menggunakan satu jenis untuk menyesuaikan yang lain dari waktu ke waktu sebagai interval lama perlahan-lahan tidak sinkron. Ada banyak info bagus di sini: Memperoleh perangko waktu resolusi tinggi . Jika mereka memulai sebagai waktu yang sama, kerugiannya adalah dari 1 dari 2 langkah di atas.
Solomon Rutzky
Saya pikir Offset adalah herring merah tetapi tidak membuat tes yang lebih abstrak. Seharusnya saya pernah melihat dokumen Microsoft menunjukkan perbedaan. Terima kasih telah mengkonfirmasi itu.
Riley Major
Tantangan saya adalah saya memiliki beberapa procs untuk melakukan ini. Mereka semua menggunakan GETUTCDATE sekarang. Jika saya mengubah beberapa ke salah satu fungsi SYS * (dengan Offset atau UTC langsung), mereka mulai keluar dari urutan dengan yang lain menggunakan fungsi GET *. Tapi saya bisa hidup dengannya atau mengubahnya sekaligus. Bukan masalah besar. Itu hanya memicu keingintahuan saya.
Riley Major