Tambah penghitung untuk setiap baris yang diubah

8

Saya menggunakan SQL Server 2008 Standard, yang tidak memiliki SEQUENCEfitur.

Sistem eksternal membaca data dari beberapa tabel khusus pada basis data utama. Sistem eksternal menyimpan salinan data dan secara berkala memeriksa perubahan dalam data dan menyegarkan salinannya.

Untuk membuat sinkronisasi efisien, saya ingin mentransfer hanya baris yang diperbarui atau disisipkan sejak sinkronisasi sebelumnya. (Baris tidak pernah dihapus). Untuk mengetahui baris mana yang diperbarui atau disisipkan sejak sinkronisasi terakhir ada bigintkolom RowUpdateCounterdi setiap tabel.

Idenya adalah bahwa setiap kali baris dimasukkan atau diperbarui, nomor di RowUpdateCounterkolomnya akan berubah. Nilai-nilai yang masuk ke RowUpdateCounterkolom harus diambil dari urutan angka yang semakin meningkat. Nilai dalam RowUpdateCounterkolom harus unik dan setiap nilai baru yang disimpan dalam tabel harus lebih besar dari nilai sebelumnya.

Silakan lihat skrip yang menunjukkan perilaku yang diinginkan.

Skema

CREATE TABLE [dbo].[Test](
    [ID] [int] NOT NULL,
    [Value] [varchar](50) NOT NULL,
    [RowUpdateCounter] [bigint] NOT NULL,
CONSTRAINT [PK_Test] PRIMARY KEY CLUSTERED
(
    [ID] ASC
))
GO

CREATE UNIQUE NONCLUSTERED INDEX [IX_RowUpdateCounter] ON [dbo].[Test]
(
    [RowUpdateCounter] ASC
)
GO

Masukkan beberapa baris

INSERT INTO [dbo].[Test]
    ([ID]
    ,[Value]
    ,[RowUpdateCounter])
VALUES
(1, 'A', ???),
(2, 'B', ???),
(3, 'C', ???),
(4, 'D', ???);

Hasil yang diharapkan

+----+-------+------------------+
| ID | Value | RowUpdateCounter |
+----+-------+------------------+
|  1 | A     |                1 |
|  2 | B     |                2 |
|  3 | C     |                3 |
|  4 | D     |                4 |
+----+-------+------------------+

Nilai yang dihasilkan di RowUpdateCounterdapat berbeda, katakanlah 5, 3, 7, 9,. Mereka harus unik dan mereka harus lebih besar dari 0, karena kita mulai dari tabel kosong.

Masukkan dan perbarui beberapa baris

DECLARE @NewValues TABLE (ID int NOT NULL, Value varchar(50));
INSERT INTO @NewValues (ID, Value) VALUES
(3, 'E'),
(4, 'F'),
(5, 'G'),
(6, 'H');

MERGE INTO dbo.Test WITH (HOLDLOCK) AS Dst
USING
(
    SELECT ID, Value
    FROM @NewValues
)
AS Src ON Dst.ID = Src.ID
WHEN MATCHED THEN
UPDATE SET
     Dst.Value            = Src.Value
    ,Dst.RowUpdateCounter = ???
WHEN NOT MATCHED BY TARGET THEN
INSERT
    (ID
    ,Value
    ,RowUpdateCounter)
VALUES
    (Src.ID
    ,Src.Value
    ,???)
;

Hasil yang diharapkan

+----+-------+------------------+
| ID | Value | RowUpdateCounter |
+----+-------+------------------+
|  1 | A     |                1 |
|  2 | B     |                2 |
|  3 | E     |                5 |
|  4 | F     |                6 |
|  5 | G     |                7 |
|  6 | H     |                8 |
+----+-------+------------------+
  • RowUpdateCounteruntuk baris dengan ID 1,2harus tetap apa adanya, karena baris ini tidak diubah.
  • RowUpdateCounteruntuk baris dengan ID 3,4harus berubah, karena mereka diperbarui.
  • RowUpdateCounteruntuk baris dengan ID 5,6harus diubah, karena mereka dimasukkan.
  • RowUpdateCounteruntuk semua baris yang diubah harus lebih besar dari 4 (yang terakhir RowUpdateCounterdari urutan).

Urutan di mana nilai-nilai baru ( 5,6,7,8) ditugaskan ke baris yang diubah tidak terlalu penting. Nilai-nilai baru dapat memiliki kesenjangan, misalnya 15,26,47,58, tetapi tidak boleh menurun.

Ada beberapa tabel dengan penghitung seperti itu dalam database. Tidak masalah jika mereka semua menggunakan urutan global tunggal untuk nomor mereka, atau setiap tabel memiliki urutan masing-masing.


Saya tidak ingin menggunakan kolom dengan cap datetime alih-alih penghitung bilangan bulat, karena:

  • Jam di server dapat melompat maju dan mundur. Terutama ketika itu pada mesin virtual.

  • Nilai yang dikembalikan oleh fungsi sistem seperti SYSDATETIMEsama untuk semua baris yang terpengaruh. Proses sinkronisasi harus dapat membaca perubahan dalam batch. Misalnya, jika ukuran kumpulan adalah 3 baris, maka setelah MERGElangkah di atas proses sinkronisasi hanya akan membaca baris E,F,G. Ketika proses sinkronisasi dijalankan lain kali akan melanjutkan dari baris H.


Cara saya melakukannya sekarang agak jelek.

Karena tidak ada SEQUENCEdalam SQL Server 2008, saya meniru SEQUENCEoleh tabel khusus dengan IDENTITYseperti yang ditunjukkan pada jawaban ini . Ini sendiri cukup jelek dan diperburuk oleh kenyataan bahwa saya perlu menghasilkan tidak satu, tetapi batch angka sekaligus.

Kemudian, saya memiliki INSTEAD OF UPDATE, INSERTpemicu pada setiap tabel dengan RowUpdateCounterdan menghasilkan set angka yang diperlukan di sana.

Di INSERT, UPDATEdan MERGEkueri yang saya tetapkan RowUpdateCounterke 0, yang diganti dengan nilai yang benar di pelatuk. Dalam ???kueri di atas adalah 0.

Ini bekerja, tetapi apakah ada solusi yang lebih mudah?

Vladimir Baranov
sumber
4
Bisakah Anda menggunakan versi baris / cap waktu? Ini adalah bidang biner tetapi nilainya akan berubah setiap kali baris diperbarui
James Z
@ James, saya perlu tahu urutan baris diubah. Proses sinkronisasi membaca penghitung MAX dari salinan tabel usang dan kemudian tahu untuk mengambil hanya baris yang memiliki penghitung lebih dari nilai itu. Tidak rowversionakan memberi saya kemungkinan ini, jika saya benar memahami apa itu ... Apakah dijamin akan semakin meningkat?
Vladimir Baranov
Terima kasih @MartinSmith, saya benar-benar lupa rowversion. Terlihat sangat menggoda. Satu-satunya kekhawatiran saya adalah bahwa semua contoh penggunaannya yang telah saya lihat sejauh ini berputar di sekitar mendeteksi apakah satu baris berubah. Saya perlu cara yang efisien untuk mengetahui apa set baris berubah sejak saat tertentu. Selain itu, apakah mungkin untuk melewatkan pembaruan?
Vladimir Baranov
@MartinSmith time = 0: nilai konversi baris terakhir adalah, katakanlah, 122. waktu = 1: Transaksi Amemperbarui satu baris, perubahan barisnya berubah menjadi 123, Abelum dilakukan. waktu = 2: Transaksi Bmemperbarui baris lain, perubahan barisnya menjadi 124. waktu = 3: Bkomit. time = 4: proses sinkronisasi berjalan dan mengambil semua baris dengan rowversion> 122, yang berarti baris hanya diperbarui oleh B. waktu = 5: Akomit. Hasil: perubahan oleh Atidak akan pernah diambil oleh proses sinkronisasi. Apakah aku salah? Mungkin beberapa penggunaan pintar MIN_ACTIVE_ROWVERSIONakan membantu?
Vladimir Baranov

Jawaban:

5

Anda dapat menggunakan ROWVERSIONkolom untuk ini.

Dokumentasi menyatakan itu

Setiap basis data memiliki penghitung yang bertambah untuk setiap operasi penyisipan atau pembaruan yang dilakukan pada tabel yang berisi kolom versi baris dalam basis data.

Nilai-nilainya adalah BINARY(8)dan Anda harus menganggapnya sebagai BINARYbukan BIGINTsetelah setelah 0x7FFFFFFFFFFFFFFFitu 0x80...dan mulai bekerja dari -9223372036854775808jika diperlakukan sebagai ditandatangani bigint.

Contoh lengkap bekerja di bawah ini. Mempertahankan indeks pada ROWVERSIONkolom akan mahal jika Anda memiliki banyak pembaruan sehingga Anda mungkin ingin menguji beban kerja Anda dengan dan tanpa melihat apakah itu sepadan dengan biayanya.

CREATE TABLE [dbo].[Test]
  (
     [ID]               [INT] NOT NULL CONSTRAINT [PK_Test] PRIMARY KEY,
     [Value]            [VARCHAR](50) NOT NULL,
     [RowUpdateCounter] [ROWVERSION] NOT NULL UNIQUE NONCLUSTERED
  )

INSERT INTO [dbo].[Test]
            ([ID],
             [Value])
VALUES     (1,'Foo'),
            (2,'Bar'),
            (3,'Baz');

DECLARE @RowVersion_LastSynch ROWVERSION = MIN_ACTIVE_ROWVERSION();

UPDATE [dbo].[Test]
SET    [Value] = 'X'
WHERE  [ID] = 2;

DECLARE @RowVersion_ThisSynch ROWVERSION = MIN_ACTIVE_ROWVERSION();

SELECT *
FROM   [dbo].[Test]
WHERE  [RowUpdateCounter] >= @RowVersion_LastSynch
       AND RowUpdateCounter < @RowVersion_ThisSynch;

/*TODO: Store @RowVersion_ThisSynch somewhere*/

DROP TABLE [dbo].[Test] 
Martin Smith
sumber
Terima kasih. Setelah membaca dokumen saya berpikir bahwa alih-alih @@DBTSseharusnya ada MIN_ACTIVE_ROWVERSION(), dan jika menggunakan MIN_ACTIVE_ROWVERSION()perbandingan <=harus menjadi <dan >menjadi >=.
Vladimir Baranov
Menurut dokumen ada perbedaan material antara @@DBTSdan MIN_ACTIVE_ROWVERSION()jika ada transaksi aktif yang tidak berkomitmen. Jika aplikasi menggunakan @@DBTSdaripada MIN_ACTIVE_ROWVERSION, dimungkinkan untuk kehilangan perubahan yang aktif saat sinkronisasi terjadi.
Vladimir Baranov
@VladimirBaranov - ya, disetujui, diedit.
Martin Smith
-2

Sudahkah Anda mencoba menggunakan IDENTITYopsi ini?

Sebagai contoh:

[RowUpdateCounter] [bigint] NOT NULL IDENTITY(1,2)

dimana

  • 1 -> Nilai awal
  • 2 -> setiap baris baru bertambah dengan ini

Ini mirip dengan URUTAN di Oracle.

Bibhuti Bhusan Padhi
sumber
SQL Server tidak memiliki "opsi OTOMATIS"
Martin Smith
Iya. Ini didukung oleh Access. SQL server mendukung opsi IDENTITY. Saya telah memperbarui balasan saya di atas. Terima kasih !!
Bibhuti Bhusan Padhi
4
IDENTITYtidak melakukan apa yang diperlukan terkait penambahan otomatis pada pembaruan dan sisipan .
Martin Smith
@BibhutiBhusanPadhi, saya perlu tahu baris apa yang telah diperbarui. Saya tidak melihat bagaimana sederhana IDENTITYdapat membantu.
Vladimir Baranov