Mendeteksi perubahan dalam tabel SQL Server

13

Dalam aplikasi saya, dengan DB yang berjalan pada SQL Server 2012, saya mendapatkan pekerjaan (tugas terjadwal) yang secara berkala menjalankan kueri yang mahal dan menulis hasilnya ke tabel yang nantinya dapat ditanyakan oleh aplikasi.

Idealnya, saya ingin menjalankan kueri yang mahal itu hanya jika sesuatu berubah sejak kueri itu terakhir kali dieksekusi. Karena tabel sumber sangat besar, saya tidak bisa hanya memilih checksum dari semua kolom kandidat atau sesuatu seperti itu.

Saya punya ide-ide berikut:

  • Tulis secara eksplisit cap waktu terakhir yang diubah, bendera "harus kueri", atau sesuatu seperti ini ke tabel pelacakan setiap kali saya mengubah sesuatu di tabel sumber.
  • Gunakan pemicu untuk melakukan hal yang sama.

Namun, saya benar-benar ingin tahu apakah ada cara yang ringan untuk mendeteksi perubahan pada tabel tanpa saya secara eksplisit melacak penulisan. Bisakah saya, misalnya, mendapatkan "saat ini" ROWVERSIONdari tabel atau sesuatu seperti itu?

Fabian Schmied
sumber

Jawaban:

14

Tidak, tidak ada. Pelacakan 'terakhir diperbarui pada' apa pun akan mengalami masalah kinerja yang parah karena semua pembaruan, dari semua transaksi, akan berupaya memperbarui satu catatan yang melacak pelacakan 'terakhir diperbarui pada'. Ini secara efektif berarti hanya satu transaksi yang dapat memperbarui tabel setiap saat, dan semua transaksi lainnya harus menunggu yang pertama untuk berkomitmen . Serialisasi lengkap. Jumlah admin / pengembang yang bersedia menerima penalti kinerja seperti itu hanya untuk mengetahui kapan pembaruan terakhir terjadi mungkin kecil.

Jadi, Anda terdampar untuk menanganinya melalui kode khusus. Itu berarti pemicu karena alternatif (mendeteksi dari catatan log) adalah hak prerogatif yang disediakan hanya untuk replikasi transaksional (atau alter-ego CDC ). Ketahuilah bahwa jika Anda mencoba melacaknya melalui kolom 'terakhir diperbarui di' maka Anda akan menghadapi masalah serialisasi yang disebutkan di atas. Jika pembaruan konkurensi penting maka Anda harus menggunakan mekanisme antrian (pemicu menggunakan INSERT dan kemudian proses mengumpulkan nilai yang dimasukkan untuk merumuskan 'terakhir diperbarui di'). Jangan mencoba menipu dengan solusi 'pintar' seperti menyelinap ke identitas saat ini atau mencari sys.dm_db_index_usage_stats . Dan juga kolom 'updated_at' per-rekam, seperti cap waktu yang dimiliki Rails,

Apakah ada alternatif 'ringan'? Sebenarnya ada satu, tetapi sulit untuk mengatakan apakah itu akan berhasil untuk Anda dan sulit untuk memperbaikinya: Pemberitahuan Permintaan . Query Notification melakukan hal itu, itu akan mengatur pemberitahuan jika ada data yang berubah dan Anda perlu menyegarkan kueri Anda. Meskipun sebagian besar pengembang hanya mengenal inkarnasi .Net sebagai SqlDependency, Query Notification dapat digunakan sebagai mekanisme yang bertahan lama untuk mendeteksi perubahan data. Dibandingkan dengan pelacakan perubahan sejati, ini akan menjadi sangat ringan, dan semantiknya lebih dekat dengan kebutuhan Anda (sesuatu, apa saja , berubah, jadi Anda perlu menjalankan kembali kueri).

Tetapi pada akhirnya, di tempat Anda, saya akan benar-benar mempertimbangkan kembali asumsi saya dan kembali ke papan gambar. Mungkin Anda dapat menggunakan pengiriman log atau replikasi untuk mengatur database pelaporan, di server yang berbeda. Apa yang saya baca di sela-sela adalah bahwa Anda membutuhkan jalur pipa ETL yang tepat dan gudang data analitik ...

Remus Rusanu
sumber
Jadi mengapa Microsoft repot-repot membuat sys.dm_db_index_usage_stats, jika informasi yang diberikannya tidak dapat diandalkan?
Craig Efrein
Ini bukan DMV yang dirancang untuk pelacakan perubahan . Sangat dapat diandalkan untuk tujuan yang dimaksud, yaitu penyempurnaan kinerja.
Remus Rusanu
8

Sepertinya saya terlambat dua tahun ke permainan, di sini, tetapi memang ada cara yang cukup ringan untuk melakukan apa yang Anda minta.

Ada dua mekanisme SQL Server yang dapat membantu Anda. Solusi utama Anda mungkin merupakan gabungan dari keduanya.

Ubah Pelacakan . SQL Server memiliki kemampuan untuk menempatkan tabel tertentu di bawah arloji, hanya merekam baris mana yang telah berubah (dengan nilai kunci utama mereka), dan jenis perubahannya (Sisipkan, Perbarui, atau Hapus). Setelah Anda mengatur deteksi perubahan pada satu set tabel, kueri ringan dapat memberi tahu Anda apakah ada perubahan pada tabel sejak terakhir kali Anda memeriksa. Overhead kira-kira sama dengan mempertahankan indeks sederhana tambahan.

Rowversion / timestamp . Ini adalah tipe kolom varbinary 8-byte (dapat dicetak ke BigInt) yang bertambah, lebar basis data, setiap kali baris yang berisi satu dimasukkan atau diperbarui (tidak membantu dengan penghapusan). Jika Anda mengindeks kolom ini, Anda dapat dengan mudah mengetahui apakah data baris telah berubah dengan membandingkan MAX (timestamp) dengan nilainya sejak terakhir kali dievaluasi. Karena nilainya meningkat secara monoton, ini akan memberi Anda indikasi yang dapat diandalkan bahwa data telah berubah jika nilai baru lebih besar daripada yang terakhir kali Anda memeriksanya.

Singkat
sumber
7

Jika sumbernya hanya sisipkan, beri IDENTITYkolom saja. Ketika Anda melakukan transfer data, Anda mencatat nilai tertinggi yang dituliskan. Selama transfer berikutnya Anda hanya perlu meminta nilai yang lebih besar dari yang dicatat selama transfer sebelumnya. Kami melakukan ini untuk mentransfer catatan log ke data warehouse.

Untuk baris yang dapat diupdate tambahkan bendera "kotor". Ini akan memiliki tiga nilai - bersih, kotor dan dihapus. Kueri sehari-hari harus menghilangkan baris dengan bendera diatur ke "dihapus". Ini akan mahal dalam perawatan, pengujian, dan waktu pengoperasian. Setelah kueri besar yang Anda sebutkan, semua baris yang ditandai untuk dihapus harus dihapus dan flag direset untuk yang lainnya. Ini tidak akan skala dengan baik.

Alternatif yang lebih ringan untuk Ubah Pengambilan Data adalah Ubah Pelacakan . Ini tidak akan memberi tahu Anda nilai apa yang berubah, hanya saja baris telah berubah sejak terakhir kali ditanya. Fungsi bawaan memfasilitasi pengambilan nilai yang berubah dan pengelolaan pelacakan. Kami telah berhasil menggunakan CT untuk memproses sekitar 100.000 perubahan per hari dalam tabel 100.000.000 baris.

Notifikasi Permintaan bertindak pada tuas yang lebih tinggi lagi - pada tingkat set hasil. Secara konseptual, ini seperti mendefinisikan pandangan. Jika SQL Server mendeteksi bahwa setiap baris yang dikembalikan melalui tampilan itu telah berubah, itu akan mengirimkan pesan ke aplikasi. Tidak ada indikasi berapa banyak baris berubah, atau kolom mana. Hanya ada pesan sederhana yang mengatakan "sesuatu yang terjadi." Terserah aplikasi untuk bertanya dan bereaksi. Praktis itu jauh lebih kompleks dari itu, seperti yang Anda bayangkan. Ada batasan tentang bagaimana kueri dapat didefinisikan dan pemberitahuan dapat diaktifkan untuk kondisi selain data yang diubah. Ketika pemberitahuan menyala itu dihapus. Jika aktivitas menarik selanjutnya terjadi maka tidak ada pesan lebih lanjut yang akan dikirim.

Dalam konteks pertanyaan OP, QN akan memiliki keuntungan sebagai biaya overhead yang rendah untuk diatur dan sedikit biaya run time. Ini mungkin merupakan upaya yang signifikan untuk membangun dan mempertahankan rezim reaksi berlangganan pesan yang keras. Karena tabel data besar, kemungkinan akan ada perubahan yang sering terjadi, artinya notifikasi cenderung menyala di sebagian besar siklus pemrosesan. Karena tidak ada indikasi perubahan pemrosesan delta yang berubah tidak akan mungkin terjadi, seperti halnya dengan CT atau CDC. Overhead karena pemicu salah adalah hal yang melelahkan, tetapi bahkan dalam kasus terburuk kueri mahal tidak perlu dijalankan lebih sering daripada saat ini.

Michael Green
sumber
3

SqlTableDependency

SqlTableDependency adalah komponen implementasi tingkat tinggi untuk mengakses pemberitahuan yang berisi nilai catatan tabel pada database SQL Server.

SqlTableDependency adalah komponen C # generik yang digunakan untuk menerima notifikasi ketika konten tabel database tertentu berubah.

Apa perbedaannya dengan .NET SqlDepenency?

Pada dasarnya, perbedaan utama adalah bahwa SqlTableDependency mengirim peristiwa yang berisi nilai-nilai untuk catatan yang dimasukkan, diubah atau dihapus, serta operasi DML (masukkan / hapus / perbarui) yang dijalankan pada tabel: SqlDepenency tidak memberi tahu data apa yang diubah pada tabel. tabel database, mereka hanya mengatakan bahwa ada sesuatu yang berubah.

Silahkan lihat pada proyek GitHub .

Christian Del Bianco
sumber
1

Jika pembaruan yang Anda harapkan memengaruhi indeks (dan hanya jika), Anda bisa menggunakan tabel sistem sys.dm_db_index_usage_statsuntuk mendeteksi pembaruan terakhir ke indeks pada tabel yang dimaksud. Anda akan menggunakan last_user_updatebidang tersebut.

Misalnya, untuk mendapatkan tabel yang terakhir diperbarui:

select
    object_name(object_id) as OBJ_NAME, *
from
    sys.dm_db_index_usage_stats
where
    database_id = db_id(db_name())
order by
    dm_db_index_usage_stats.last_user_update desc

Atau, untuk memeriksa apakah tabel tertentu diubah sejak tanggal tertentu:

select
    case when count(distinct object_id) > 0 then 1 else 0 end as IS_CHANGED
from
    sys.dm_db_index_usage_stats
where
    database_id = db_id(db_name())
    and object_id = object_id('MY_TABLE_NAME')
    and last_user_update > '2016-02-18'
Geoff
sumber
Apa pendapat Anda tentang komentar Remus di atas? "Jangan mencoba menipu dengan solusi 'pintar' seperti menyelinap pada identitas saat ini atau mencari sys.dm_db_index_usage_stats." (Lihat juga komentarnya di bawah jawabannya.)
Fabian Schmied
1
@FabianSchmied Menarik - Saya belum melihat bahwa ketika saya menambahkan jawaban saya, saya tidak dapat menemukan sesuatu yang otoritatif selain dari jawaban Remus lain untuk menunjukkan bahwa itu tidak dapat diandalkan untuk kasus penggunaan ini; halaman MS untuk dm_db_index_operational_statsmenunjukkan masalah (dibersihkan sebagai cache metadata dihapus), tetapi tidak untuk dm_db_index_usage_stats. Satu-satunya masalah yang saya temukan adalah dengan indeks membangun kembali, restart server dan pelepasan basis data membersihkan statistik penggunaan, dan sepertinya itu tidak diterapkan di sini. Akan tertarik untuk melihat info yang dibuktikan tentang ini.
Geoff