Estoy usando SQL Server 2008 Standard, que no tiene una SEQUENCE
función.
Un sistema externo lee datos de varias tablas dedicadas de la base de datos principal. El sistema externo mantiene una copia de los datos y verifica periódicamente los cambios en los datos y actualiza su copia.
Para que la sincronización sea eficiente, quiero transferir solo las filas que se actualizaron o insertaron desde la sincronización anterior. (Las filas nunca se eliminan). Para saber qué filas se actualizaron o insertaron desde la última sincronización, hay una bigint
columna RowUpdateCounter
en cada tabla.
La idea es que cada vez que se inserte o actualice una fila, el número en su RowUpdateCounter
columna cambiaría. Los valores que entran en la RowUpdateCounter
columna deben tomarse de una secuencia de números cada vez mayor. Los valores en la RowUpdateCounter
columna deben ser únicos y cada nuevo valor almacenado en una tabla debe ser mayor que cualquier valor anterior.
Consulte los scripts que muestran el comportamiento deseado.
Esquema
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
INSERTAR algunas filas
INSERT INTO [dbo].[Test]
([ID]
,[Value]
,[RowUpdateCounter])
VALUES
(1, 'A', ???),
(2, 'B', ???),
(3, 'C', ???),
(4, 'D', ???);
Resultado Esperado
+----+-------+------------------+
| ID | Value | RowUpdateCounter |
+----+-------+------------------+
| 1 | A | 1 |
| 2 | B | 2 |
| 3 | C | 3 |
| 4 | D | 4 |
+----+-------+------------------+
Los valores generados en RowUpdateCounter
pueden ser diferentes, por ejemplo, 5, 3, 7, 9
. Deben ser únicos y deben ser mayores que 0, ya que comenzamos desde una tabla vacía.
INSERTAR y ACTUALIZAR algunas filas
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
,???)
;
Resultado Esperado
+----+-------+------------------+
| ID | Value | RowUpdateCounter |
+----+-------+------------------+
| 1 | A | 1 |
| 2 | B | 2 |
| 3 | E | 5 |
| 4 | F | 6 |
| 5 | G | 7 |
| 6 | H | 8 |
+----+-------+------------------+
RowUpdateCounter
para las filas con ID1,2
debe permanecer como está, porque estas filas no se modificaron.RowUpdateCounter
para las filas con ID3,4
deberían cambiar, porque se actualizaron.RowUpdateCounter
para las filas con ID5,6
deberían cambiar, porque se insertaron.RowUpdateCounter
para todas las filas modificadas debe ser mayor que 4 (el últimoRowUpdateCounter
de la secuencia).
El orden en que 5,6,7,8
se asignan los nuevos valores ( ) a las filas modificadas realmente no importa. Los nuevos valores pueden tener huecos, por ejemplo 15,26,47,58
, pero nunca deberían disminuir.
Hay varias tablas con dichos contadores en la base de datos. No importa si todos usan la secuencia global única para sus números, o si cada tabla tiene su propia secuencia individual.
No quiero usar una columna con un sello de fecha y hora en lugar de un contador entero, porque:
El reloj en el servidor puede saltar hacia adelante y hacia atrás. Especialmente cuando está en una máquina virtual.
Los valores devueltos por las funciones del sistema
SYSDATETIME
son iguales para todas las filas afectadas. El proceso de sincronización debería poder leer los cambios en lotes. Por ejemplo, si el tamaño del lote es de 3 filas, luego delMERGE
paso anterior, el proceso de sincronización solo leería filasE,F,G
. Cuando se ejecute el proceso de sincronización la próxima vez, continuará desde la filaH
.
La forma en que lo hago ahora es bastante fea.
Como no existe SEQUENCE
en SQL Server 2008, emulo SEQUENCE
una tabla dedicada con la IDENTITY
que se muestra en esta respuesta . Esto en sí mismo es bastante feo y se ve exacerbado por el hecho de que necesito generar no uno solo, sino un lote de números a la vez.
Luego, tengo un INSTEAD OF UPDATE, INSERT
activador en cada tabla con RowUpdateCounter
y genero los conjuntos de números necesarios allí.
En las consultas INSERT
, UPDATE
y MERGE
configuro RowUpdateCounter
a 0, que se reemplaza por los valores correctos en el disparador. El ???
en las consultas anteriores son 0
.
Funciona, pero ¿hay una solución más fácil?
fuente
rowversion
no me daría esta posibilidad, si entiendo correctamente lo que es ... ¿Se garantiza que aumentará?rowversion
. Se ve muy tentador. Mi única preocupación es que todos los ejemplos de su uso que he visto hasta ahora giran en torno a detectar si una sola fila cambió. Necesito una forma eficiente de saber qué conjunto de filas cambió desde un momento determinado. Además, ¿es posible perder una actualización?A
actualiza una fila, su versión de la fila cambia a 123,A
aún no se ha confirmado. time = 2: la transacciónB
actualiza otra fila, su versión de fila cambia a 124. time = 3:B
commits. time = 4: el proceso de sincronización se ejecuta y recupera todas las filas con rowversion> 122, lo que significa que las filas se actualizan solo porB
. tiempo = 5:A
confirmaciones. Resultado: los cambios porA
nunca serán recogidos por el proceso de sincronización. ¿Me equivoco? Tal vez algún uso inteligente deMIN_ACTIVE_ROWVERSION
ayudará?Respuestas:
Puedes usar una
ROWVERSION
columna para esto.La documentación indica que
Los valores son
BINARY(8)
y debe considerarlos comoBINARY
algo más queBIGINT
después de0x7FFFFFFFFFFFFFFF
que continúe0x80...
y comience a funcionar-9223372036854775808
si se trata como un signobigint
.Un ejemplo completo trabajado está por debajo. Mantener el índice en la
ROWVERSION
columna será costoso si tiene muchas actualizaciones, por lo que es posible que desee probar su carga de trabajo con y sin ver si vale la pena el costo.fuente
@@DBTS
debería haberMIN_ACTIVE_ROWVERSION()
, y si se usa laMIN_ACTIVE_ROWVERSION()
comparación,<=
debería convertirse<
y>
convertirse>=
.@@DBTS
yMIN_ACTIVE_ROWVERSION()
si hay transacciones activas no comprometidas. Si una aplicación usa en@@DBTS
lugar deMIN_ACTIVE_ROWVERSION
, es posible perder los cambios que están activos cuando se produce la sincronización.¿Has intentado usar la
IDENTITY
opción?Por ejemplo:
dónde
Esto es similar a SECUENCIA en Oracle.
fuente
IDENTITY
no hace lo que se requiere con respecto al incremento automático tanto en las actualizaciones como en las inserciones .IDENTITY
puede ayudar.