Sto usando SQL Server 2008 Standard, che non ha una SEQUENCE
funzionalità.
Un sistema esterno legge i dati da diverse tabelle dedicate del database principale. Il sistema esterno conserva una copia dei dati e verifica periodicamente la presenza di modifiche ai dati e ne aggiorna la copia.
Per rendere efficiente la sincronizzazione, voglio trasferire solo le righe che sono state aggiornate o inserite dalla sincronizzazione precedente. (Le righe non vengono mai eliminate). Per sapere quali righe sono state aggiornate o inserite dall'ultima sincronizzazione, c'è una bigint
colonna RowUpdateCounter
in ogni tabella.
L'idea è che ogni volta che una riga viene inserita o aggiornata, il numero nella sua RowUpdateCounter
colonna cambierebbe. I valori che vanno nella RowUpdateCounter
colonna dovrebbero essere presi da una sequenza di numeri sempre crescente. I valori nella RowUpdateCounter
colonna dovrebbero essere univoci e ogni nuovo valore archiviato in una tabella dovrebbe essere maggiore di qualsiasi valore precedente.
Si prega di consultare gli script che mostrano il comportamento desiderato.
Schema
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
INSERIRE alcune righe
INSERT INTO [dbo].[Test]
([ID]
,[Value]
,[RowUpdateCounter])
VALUES
(1, 'A', ???),
(2, 'B', ???),
(3, 'C', ???),
(4, 'D', ???);
Risultato atteso
+----+-------+------------------+
| ID | Value | RowUpdateCounter |
+----+-------+------------------+
| 1 | A | 1 |
| 2 | B | 2 |
| 3 | C | 3 |
| 4 | D | 4 |
+----+-------+------------------+
I valori generati in RowUpdateCounter
possono essere diverse, dicono 5, 3, 7, 9
. Dovrebbero essere univoci e dovrebbero essere maggiori di 0, poiché siamo partiti da una tabella vuota.
INSERISCI e AGGIORNA alcune righe
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
,???)
;
Risultato atteso
+----+-------+------------------+
| ID | Value | RowUpdateCounter |
+----+-------+------------------+
| 1 | A | 1 |
| 2 | B | 2 |
| 3 | E | 5 |
| 4 | F | 6 |
| 5 | G | 7 |
| 6 | H | 8 |
+----+-------+------------------+
RowUpdateCounter
per le righe con ID1,2
dovrebbe rimanere così com'è, poiché queste righe non sono state modificate.RowUpdateCounter
per le righe con ID3,4
dovrebbe cambiare, perché sono state aggiornate.RowUpdateCounter
per le righe con ID5,6
dovrebbe cambiare, perché sono state inserite.RowUpdateCounter
per tutte le righe modificate dovrebbe essere maggiore di 4 (l'ultimoRowUpdateCounter
dalla sequenza).
L'ordine in cui i nuovi valori ( 5,6,7,8
) vengono assegnati alle righe modificate non ha importanza. I nuovi valori possono presentare lacune, ad esempio 15,26,47,58
, ma non dovrebbero mai ridursi.
Esistono diverse tabelle con tali contatori nel database. Non importa se tutti usano la singola sequenza globale per i loro numeri o ogni tabella ha la sua sequenza individuale.
Non voglio usare una colonna con un timbro datetime invece di un contatore intero, perché:
L'orologio sul server può saltare sia in avanti che all'indietro. Soprattutto quando si trova su una macchina virtuale.
I valori restituiti da funzioni di sistema come
SYSDATETIME
sono gli stessi per tutte le righe interessate. Il processo di sincronizzazione dovrebbe essere in grado di leggere le modifiche in batch. Ad esempio, se la dimensione del batch è di 3 righe, dopo ilMERGE
passaggio sopra il processo di sincronizzazione leggerà solo le righeE,F,G
. La prossima volta che il processo di sincronizzazione verrà eseguito dalla rigaH
.
Il modo in cui lo sto facendo ora è piuttosto brutto.
Dato che non è presente SEQUENCE
in SQL Server 2008, emulo il SEQUENCE
da una tabella dedicata con IDENTITY
come mostrato in questa risposta . Questo di per sé è piuttosto brutto ed esacerbato dal fatto che ho bisogno di generare non un singolo, ma un gruppo di numeri contemporaneamente.
Quindi, ho un INSTEAD OF UPDATE, INSERT
trigger su ogni tabella con RowUpdateCounter
e generare lì i set di numeri richiesti.
In INSERT
, UPDATE
e le MERGE
query ho impostato RowUpdateCounter
su 0, che viene sostituito dai valori corretti nel trigger. Le ???
domande sopra sono 0
.
Funziona, ma esiste una soluzione più semplice?
rowversion
non mi avrebbe dato questa possibilità, se ho capito bene quello che è ... E 'garantito per essere sempre più?
rowversion
. Sembra molto allettante. La mia unica preoccupazione è che tutti gli esempi del suo uso che ho visto finora ruotino attorno al rilevamento di una singola riga modificata. Ho bisogno di un modo efficace per sapere quale serie di righe è cambiata da un certo momento. Inoltre, è possibile perdere un aggiornamento?
A
aggiorna una riga, la suaversione di riga cambia in 123, A
non è ancora impegnata. time = 2: la transazione B
aggiorna un'altra riga, la sua rowversion cambia in 124. time = 3: B
commit. time = 4: il processo di sincronizzazione viene eseguito e recupera tutte le righe con rowversion> 122, il che significa che le righe vengono aggiornate solo da B
. time = 5: A
commit. Risultato: le modifiche di A
non verranno mai rilevate dal processo di sincronizzazione. Ho sbagliato? Forse un uso intelligente di MIN_ACTIVE_ROWVERSION
aiuterà?