Ketika memperbarui baris dalam tabel temporal nilai lama untuk baris disimpan dalam tabel sejarah dengan transaksi dimulai waktu sebagai SysEndTime
. Nilai-nilai baru dalam tabel saat ini akan memiliki waktu mulai transaksi sebagai SysStartTime
.
SysStartTime
dan SysEndTime
yang datetime2
kolom yang digunakan oleh tabel sementara untuk merekam ketika berturut-turut adalah versi saat ini. Waktu mulai transaksi adalah waktu transaksi yang memuat pembaruan dimulai.
BOL mengatakan:
Waktu yang dicatat dalam kolom datetime2 sistem didasarkan pada waktu mulai transaksi itu sendiri. Misalnya, semua baris yang disisipkan dalam satu transaksi akan memiliki waktu UTC yang sama dicatat di kolom yang sesuai dengan awal periode SYSTEM_TIME.
Contoh: Saya mulai memperbarui semua baris dalam tabel Pesanan saya di 20160707 11:00:00
dan transaksi berlangsung 5 menit. Ini membuat baris dalam tabel sejarah untuk setiap baris dengan SysEndTime
as 20160707 11:00:00
. Semua baris dalam tabel saat ini akan memiliki SysStartTime
dari 20160707 11:00:00
.
Jika seseorang mengeksekusi kueri di 20160707 11:01:00
(saat pembaruan sedang berjalan) mereka akan melihat nilai-nilai lama (dengan asumsi tingkat isolasi yang dilakukan read default).
Tetapi jika seseorang kemudian menggunakan AS OF
sintaks untuk query tabel temporal karena pada saat itu 20160707 11:01:00
mereka akan melihat nilai-nilai baru karena mereka SysStartTime
akan 20160707 11:00:00
.
Bagi saya ini berarti tidak menunjukkan baris-baris itu seperti pada waktu itu. Jika menggunakan akhir waktu transaksi masalahnya tidak akan ada.
Pertanyaan: Apakah ini sesuai desain? Apakah saya melewatkan sesuatu?
Satu-satunya alasan saya dapat berpikir itu menggunakan waktu mulai transaksi adalah bahwa itu adalah satu-satunya 'diketahui' ketika transaksi dimulai. Ia tidak tahu kapan transaksi akan berakhir ketika itu dimulai dan akan butuh waktu untuk menerapkan waktu akhir di akhir yang akan membatalkan waktu akhir itu berlaku. Apakah ini masuk akal?
Ini akan memungkinkan Anda untuk membuat ulang masalah.
sumber
20160707 11:04:58
dan sekarang Anda memperbarui semua baris dengan stempel waktu itu. Tetapi pembaruan ini juga berjalan selama beberapa detik dan selesai pada20160707 11:05:02
, sekarang, stempel waktu mana yang merupakan akhir transaksi yang benar? Atau menganggap Anda digunakanRead Uncommited
di20160707 11:05:00
, dan mendapat baris yang dikembalikan, tetapi kemudianAS OF
tidak menunjukkan mereka.Jawaban:
Idenya adalah untuk melacak waktu logis vs waktu fisik. Logika hanya merujuk pada apa yang diharapkan pengguna / aplikasi saat memasukkan / memperbarui / menghapus. Fakta bahwa operasi DML dapat memakan waktu cukup lama karena alasan apa pun, tidak berarti atau bahkan mudah ditentukan dan dipahami oleh pengguna. Jika Anda pernah harus menjelaskan pertentangan kunci vs kait ke seorang akuntan (saya punya), itu adalah situasi yang sebanding.
Misalnya, ketika Bob "memberi tahu" aplikasi bahwa semua karyawan di departemen Bob akan mulai menghasilkan $ 42 / mnt di
20160707 11:00:00
, Bob (dan karyawannya) mengharapkan gaji semua orang sekarang dihitung pada $ 42 / mnt dari waktu itu. Bob tidak peduli bahwa ini akan diberlakukan, aplikasi harus membuat 2 membaca dan 6 menulis di seluruh database per karyawan dan data mereka + file log duduk di sekelompok drive RAID-5 SATA II sehingga dibutuhkan sekitar 7 menit untuk menyelesaikan tugas untuk semua 256 karyawan Bob. Bob, akuntannya, dan manajer penggajian memperhatikan bahwa semua pegawainya dibayar $ 42 / mnt mulai20160707 11:00:00
. Selain itu, karyawan yang dimutakhirkan pada20160707 11:00:01
akan sedikit terganggu sementara mereka yang catatannya diperbarui20160707 11:00:07
akan berkumpul di luar departemen penggajian.Ada kasus penggunaan yang valid untuk melacak waktu fisik seperti debugging dan forensik tetapi bagi pengguna akhir, umumnya tidak ada artinya. Tlog menyimpan informasi pemesanan dan waktu untuk setiap operasi penulisan (antara lain) sehingga ada di sana jika Anda tahu cara melihatnya.
sumber
Saya percaya bahwa ini memang cacat desain, meskipun yang tidak spesifik untuk SQL Server 2016, karena semua implementasi yang ada dari tabel temporal lainnya (sejauh yang saya tahu) memiliki kelemahan yang sama. Masalah yang dapat timbul dengan tabel temporal karena ini cukup parah; skenario dalam contoh Anda ringan dibandingkan dengan apa yang bisa salah secara umum:
Referensi kunci asing yang rusak : Misalkan kita memiliki dua tabel temporal, dengan tabel A memiliki referensi kunci asing ke tabel B. Sekarang katakanlah kita memiliki dua transaksi, keduanya berjalan pada tingkat isolasi BACA KOMITMEN: transaksi 1 dimulai sebelum transaksi 2, transaksi 2 menyisipkan baris ke dalam tabel B dan melakukan, lalu transaksi 1 menyisipkan baris di tabel A dengan referensi ke baris yang baru ditambahkan dari B. Karena penambahan baris baru ke B sudah dilakukan, batasan kunci asing puas dan transaksi 1 berhasil melakukan komitmen. Namun, jika kita melihat database "SEBAGAIMANA ADANYA" di beberapa waktu antara ketika transaksi 1 dimulai dan ketika transaksi 2 dimulai, maka kita akan melihat tabel A dengan referensi ke deretan B yang tidak ada. Jadi dalam hal ini,tabel temporal memberikan pandangan yang tidak konsisten dari database . Ini tentu saja bukan maksud dari standar SQL: 2011, yang menyatakan,
Kunci primer non-unik : Misalkan kita memiliki tabel dengan kunci utama dan dua transaksi, keduanya pada tingkat isolasi BACA KOMITMEN, di mana hal berikut terjadi: Setelah transaksi 1 dimulai tetapi sebelum menyentuh tabel ini, transaksi 2 menghapus tertentu baris meja dan melakukan. Kemudian, transaksi 1 menyisipkan baris baru dengan kunci utama yang sama dengan yang telah dihapus. Ini berjalan dengan baik, tetapi ketika Anda melihat tabel SEBAGAI waktu di antara ketika transaksi 1 dimulai dan ketika transaksi 2 dimulai, kita akan melihat dua baris dengan kunci utama yang sama.
Kesalahan pada pembaruan bersamaan : Katakanlah kita memiliki tabel dan dua transaksi yang keduanya memperbarui baris yang sama di dalamnya, sekali lagi pada tingkat isolasi BACA KOMITMEN. Transaksi 1 dimulai terlebih dahulu, tetapi transaksi 2 adalah yang pertama untuk memperbarui baris. Transaksi 2 kemudian melakukan, dan transaksi 1 kemudian melakukan pembaruan yang berbeda pada baris dan melakukan. Ini semua baik-baik saja, kecuali bahwa jika ini adalah tabel temporal, setelah pelaksanaan pembaruan dalam transaksi 1 ketika sistem pergi untuk memasukkan baris yang diperlukan ke dalam tabel riwayat, SysStartTime yang dihasilkan akan menjadi waktu mulai transaksi 2, sedangkan SysEndTime akan menjadi waktu mulai transaksi 1, yang bukan interval waktu yang valid karena SysEndTime akan sebelum SysStartTime. Dalam hal ini SQL Server melempar kesalahan dan memutar kembali transaksi (misalnya, lihatdiskusi ini ). Ini sangat tidak menyenangkan, karena pada tingkat isolasi READ COMMITTED, tidak diharapkan bahwa masalah konkurensi akan menyebabkan kegagalan langsung, yang berarti bahwa aplikasi tidak perlu dipersiapkan untuk melakukan upaya coba lagi. Secara khusus, ini bertentangan dengan "jaminan" dalam dokumentasi Microsoft:
Implementasi lain dari tabel temporal telah berurusan dengan skenario ini (dua transaksi bersamaan memperbarui baris yang sama) dengan menawarkan opsi untuk secara otomatis "menyesuaikan" cap waktu jika mereka tidak valid (lihat di sini dan di sini ). Ini adalah solusi yang buruk, karena memiliki konsekuensi yang disayangkan dari mematahkan atomicity transaksi, karena pernyataan lain dalam transaksi yang sama umumnya tidak akan memiliki stempel waktu mereka disesuaikan dengan cara yang sama; yaitu, dengan solusi ini, jika kita melihat basis data "SEBAGAIMANA Waktu" tertentu maka kita dapat melihat transaksi yang sebagian dieksekusi.
Larutan: Anda sudah menyarankan solusi yang jelas, yaitu agar implementasi menggunakan waktu akhir transaksi (yaitu waktu komit) alih-alih waktu mulai. Ya memang benar bahwa ketika kita mengeksekusi pernyataan di tengah-tengah transaksi, tidak mungkin untuk mengetahui berapa waktu komit akan terjadi (seperti di masa depan, atau bahkan mungkin tidak ada jika transaksi akan digulirkan kembali). Tetapi ini tidak berarti solusinya tidak dapat diterapkan; itu hanya harus dilakukan dengan cara yang berbeda. Misalnya, ketika melakukan pernyataan UPDATE atau DELETE, dalam membuat baris sejarah sistem hanya bisa memasukkan ID transaksi saat ini daripada waktu mulai, dan kemudian ID dapat dikonversi ke stempel waktu kemudian oleh sistem setelah transaksi dilakukan. .
Dalam konteks implementasi semacam ini, saya akan menyarankan bahwa sebelum transaksi dilakukan, setiap baris yang ditambahkan ke tabel riwayat tidak boleh terlihat oleh pengguna. Dari perspektif pengguna, seharusnya hanya muncul bahwa baris ini ditambahkan (dengan stempel waktu komit) pada saat komit. Khususnya, jika transaksi tidak pernah berhasil dilakukan maka itu tidak akan pernah muncul dalam sejarah. Tentu saja, ini tidak konsisten dengan standar SQL: 2011 yang menggambarkan penyisipan ke riwayat (termasuk cap waktu) sebagai yang terjadi pada saat pernyataan UPDATE dan DELETE (sebagai lawan dari waktu komit). Tetapi saya tidak berpikir ini benar-benar penting, mengingat bahwa standar tidak pernah diimplementasikan dengan baik (dan bisa dibilang tidak pernah bisa) karena masalah yang dijelaskan di atas,
Dari sudut pandang kinerja, mungkin tampak tidak diinginkan bagi sistem untuk kembali dan meninjau kembali baris sejarah untuk mengisi stempel waktu komit. Tetapi tergantung pada bagaimana hal ini dilakukan, biayanya bisa sangat rendah. Saya tidak begitu akrab dengan bagaimana SQL Server bekerja secara internal, tetapi PostgreSQL misalnya menggunakan write-ahead-log, yang membuatnya sehingga jika beberapa pembaruan dilakukan pada bagian tabel yang sama, pembaruan tersebut dikonsolidasikan sehingga data hanya perlu ditulis satu kali ke halaman tabel fisik - dan itu biasanya berlaku dalam skenario ini. Bagaimanapun,
Tentu saja, karena (sejauh yang saya tahu) sistem semacam ini belum pernah diterapkan, saya tidak dapat mengatakan dengan pasti bahwa itu akan berhasil - mungkin ada sesuatu yang saya lewatkan - tetapi saya tidak melihat alasan apa pun mengapa itu tidak berhasil?
sumber
Pada saat Anda melakukan transaksi Anda, semua data harus ditulis di dalam halaman data (dalam memori dan pada disk dalam file log). Termasuk
SysStartTime
danSysEndTime
kolom. Bagaimana Anda bisa tahu waktu akhir transaksi sebelum benar-benar selesai?Kecuali Anda dapat memprediksi masa depan, menggunakan waktu mulai transaksi adalah satu-satunya pilihan, meskipun itu mungkin kurang intuitif.
sumber