Utilizar un sistema de seguimiento de cambios personalizado
Muchas aplicaciones requieren que se realice el seguimiento de los cambios en la base de datos servidor para que estos cambios se puedan entregar a los clientes durante una sesión de sincronización posterior. En este tema se describen los requisitos para un sistema de seguimiento de cambios, y se muestra la forma de crear un sistema personalizado que Sync Framework puede utilizar. El seguimiento de cambios personalizado es adecuado en algunos casos. Sin embargo, tenga en cuenta que introduce complejidad y puede influir en el rendimiento de la base de datos servidor. Si está utilizando SQL Server 2008, se recomienda utilizar la característica de seguimiento de cambios de SQL Server. Para obtener más información, vea Usar el seguimiento de cambios de SQL Server.
Requisitos del servidor para escenarios de sincronización
Sync Framework se ha diseñado para causar un efecto mínimo en la base de datos servidor. Por lo tanto, las modificaciones requeridas para el seguimiento de cambios en la base de datos servidor son proporcionales al nivel de funcionalidad que se desee para una aplicación: Tenga presente las consideraciones siguientes:
En un extremo de la escala se sitúa la instantánea de los datos de sólo descarga, que no requiere cambios.
En el otro extremo está la sincronización bidireccional que usa el seguimiento completo de los cambios y la detección de todo conflicto.
En la tabla siguiente se resumen los distintos usos de Sync Framework y se identifican los requisitos correspondientes de la base de datos servidor.
Escenario | Clave principal o columna única1 | Seguimiento de tiempo de actualización | Seguimiento de tiempo de inserción | Seguimiento de tiempo de eliminación | Seguimiento de id. del cliente para actualizaciones | Seguimiento de id. del cliente para inserciones | Seguimiento de id. del cliente para eliminaciones |
---|---|---|---|---|---|---|---|
Descargar en el cliente una instantánea de datos. |
No |
No |
No |
No |
No |
No |
No |
Descargar en el cliente inserciones y actualizaciones incrementales. |
Sí |
Sí |
Sí2 |
No |
No |
No |
No |
Descargar en el cliente inserciones, actualizaciones y eliminaciones incrementales. |
Sí |
Sí |
Sí2 |
Sí |
No |
No |
No |
Cargar inserciones en el servidor. |
Sí |
No |
No |
No |
No |
No3 |
No |
Cargar inserciones y actualizaciones en el servidor. |
Sí |
No |
No |
No |
No3 |
No3 |
No |
Cargar inserciones, actualizaciones y eliminaciones en el servidor. |
Sí |
No |
No |
No |
No3 |
No3 |
No3 |
Inserciones y actualizaciones bidireccionales con detección de conflictos. |
Sí |
Sí |
Sí2 |
No |
Sí4 |
Sí4 |
No |
Inserciones, actualizaciones y eliminaciones bidireccionales con detección de conflictos. |
Sí |
Sí |
Sí2 |
Sí |
Sí4 |
Sí4 |
Sí4 |
1 Las claves principales deben ser únicas en todos los nodos y no se tienen que volver a usar: si una fila se elimina, su clave principal no debe utilizarse para otra fila. Las columnas de identidad normalmente no son una opción adecuada para los entornos distribuidos. Para obtener más información acerca de las claves principales, vea Seleccionar una clave principal apropiada para un entorno distribuido.
2 Es necesario si desea distinguir entre inserciones y actualizaciones. Para obtener más información, vea más adelante el apartado "Determinar los cambios de datos que se van a descargar en un cliente".
3 Es necesario cuando varios clientes pueden cambiar una fila y desea identificar qué cliente efectúa la modificación. Para obtener más información, vea "Identificar el cliente que realiza el cambio de datos" en este tema.
4 Es necesario si no se desea mostrar los cambios al cliente que los efectuó. Para obtener más información, vea "Identificar el cliente que realiza el cambio de datos" en este tema.
Nota
Además de los cambios descritos anteriormente, es posible que le convenga crear procedimientos almacenados para tener acceso a los datos. En la mayoría de los ejemplos de esta documentación se usa SQL incorporado, ya que permite mostrar de una forma más sencilla lo que está ocurriendo en el código. A continuación se describen los motivos por los que es necesario usar procedimientos almacenados en las aplicaciones de producción: encapsulan el código, normalmente funcionan mejor y pueden proporcionar más seguridad de SQL insertado cuando se escriben correctamente.
Determinar los cambios de datos que se van a descargar en un cliente
En la sincronización bidireccional y de sólo descarga, es necesario realizar un seguimiento de los cambios que ocurran en el servidor, de forma que Sync Framework pueda determinar los cambios que deben descargarse en clientes. A pesar de que Sync Framework no define específicamente cómo admitir el seguimiento de los cambios, existe un método común para abordarlo. Puede usar el planteamiento siguiente para cada tabla que desee sincronizar:
Agregar una columna que realice un seguimiento cuando se inserte una fila en la base de datos servidor.
Agregar una columna y un desencadenador (éste último es menos frecuente) para realizar un seguimiento de la última actualización de una fila en la base de datos servidor.
Agregar una tabla de marcadores de exclusión y un desencadenador para realizar un seguimiento de la eliminación de filas de la base de datos servidor. Si no desea eliminar datos del servidor, pero tiene que enviar las eliminaciones al cliente, puede llevar un seguimiento de las eliminaciones lógicas en la tabla base: use una columna, normalmente de tipo bit, para indicar la eliminación de filas y otra columna para llevar un seguimiento del momento en el que se producen las eliminaciones.
Estas columnas y tablas de marcadores de exclusión se usan conjuntamente con los delimitadores para determinar las inserciones, actualizaciones y eliminaciones que se van a descargar. Un delimitador es sencillamente un instante de tiempo que permite definir un conjunto de cambios que se van a sincronizar. Considere las consultas siguientes:
La consulta que se especifica para la propiedad SelectIncrementalInsertsCommand. Esta consulta descarga inserciones incrementales de la tabla
Sales.Customer
en la base de datos de ejemplo Sync Framework, como se detalla a continuación:SELECT CustomerId, CustomerName, SalesPerson, CustomerType FROM Sales.Customer WHERE InsertTimestamp > @sync_last_received_anchor AND InsertTimestamp <= @sync_new_received_anchor
Para obtener más información acerca de esta propiedad y otras propiedades relacionadas con los comandos de sincronización, vea Especificar una sincronización de instantáneas, de descarga, de carga y bidireccional.
La consulta que se especifica para la propiedad SelectNewAnchorCommand. Esta consulta recupera un valor de instante de tiempo. La columna
InsertTimestamp
almacena valores de marca de tiempo. Por tanto, la consulta usa la funciónMIN_ACTIVE_ROWVERSION
de Transact-SQL, que apareció por primera vez en SQL Server 2005 Service Pack 2, para recuperar un valor de marca de tiempo de la base de datos servidor, tal como se explica a continuación:SELECT @sync_new_received_anchor = MIN_ACTIVE_ROWVERSION - 1
MIN_ACTIVE_ROWVERSION devuelve el valor activo más bajo de timestamp (conocido también como rowversion) en la base de datos actual. Un valor de timestamp está activo si se usa en una transacción que aún no se ha confirmado. Si no hay valores activos en la base de datos, MIN_ACTIVE_ROWVERSION devuelve el mismo valor como @@DBTS + 1. MIN_ACTIVE_ROWVERSION resulta útil para determinados escenarios, como la sincronización de datos, que usan valores timestamp para agrupar conjuntos de cambios. Si una aplicación usa @@DBTS en sus comandos de delimitador en lugar de MIN_ACTIVE_ROWVERSION, es posible que se pierdan cambios que están activos cuando se produce la sincronización.
La primera vez que se sincroniza la tabla Sales.Customer
, se realizan los procesos siguientes:
Se ejecuta el nuevo comando de delimitador. El comando devuelve el valor
0x0000000000000D49
. Este valor se almacena en la base de datos cliente. La tabla nunca se ha sincronizado. Por tanto, la base de datos cliente no tiene almacenado un valor de delimitador de una sincronización anterior. En este caso, Sync Framework usa el valor más bajo disponible para el tipo de datos timestampde SQL Server:0x0000000000000000
. La consulta que ejecuta Sync Framework es la siguiente. Esta consulta descarga el esquema y todas las filas de la tabla.exec sp_executesql N'SELECT CustomerId, CustomerName, SalesPerson, CustomerType FROM Sales.Customer WHERE (InsertTimestamp > @sync_last_received_anchor AND InsertTimestamp <= @sync_new_received_anchor)',N'@sync_last_received_anchor timestamp, @sync_new_received_anchor timestamp', @sync_last_received_anchor=0x0000000000000000, @sync_new_received_anchor=0x0000000000000D49
Durante la segunda sincronización, se ejecuta el comando del nuevo delimitador. Se han insertado filas desde la última sincronización. Por consiguiente, el comando devuelve el valor
0x0000000000000D4C
. La tabla se ha sincronizado antes. Por consiguiente, Sync Framework puede recuperar el valor de delimitador0x0000000000000D49
. Este valor se almacenó en la base de datos cliente en la sincronización anterior. La consulta que se ejecuta es la siguiente: La consulta solamente descarga las filas de la tabla que se insertaron entre los dos valores del delimitador.exec sp_executesql N'SELECT CustomerId, CustomerName, SalesPerson, CustomerType FROM Sales.Customer WHERE (InsertTimestamp > @sync_last_received_anchor AND InsertTimestamp <= @sync_new_received_anchor)', N'@sync_last_received_anchor timestamp, @sync_new_received_anchor timestamp', @sync_last_received_anchor=0x0000000000000D49, @sync_new_received_anchor=0x0000000000000D4C
Para ver ejemplos sobre comandos de actualización y eliminación, vea Descargar cambios de datos incrementales a un cliente y Intercambiar cambios de datos incrementales bidireccionales entre un cliente y un servidor.
Tal como se indicó anteriormente, el comando que recupera los valores del delimitador dependen del tipo de datos de las columnas de seguimiento en la base de datos servidor. En los ejemplos de esta documentación se usa timestamp de SQL Server, también conocido como rowversion. Para usar una columna datetime de SQL Server, la consulta del nuevo comando de delimitador debe ser similar a la siguiente:
SELECT @sync_new_received_anchor = GETUTCDATE()
Para determinar qué tipo de datos se deben usar para un delimitador, debe considerar los requisitos de la aplicación y tener en cuenta la flexibilidad disponible para cambiar el esquema de base de datos servidor. Si la base de datos está en desarrollo, puede especificar exactamente qué columnas y desencadenadores hay que agregar. Si la base de datos está en producción, puede que sus opciones estén más limitadas. Considere las instrucciones siguientes:
Todas las tablas de un grupo de sincronización deben usar el mismo tipo de datos y el comando de delimitador nuevo. Si puede, use el mismo comando y tipo de datos para todos los grupos.
El tipo de datos datetime se entiende con facilidad y las tablas tienen normalmente una columna para el seguimiento de las modificaciones de las filas. Sin embargo, este tipo de datos puede causar problemas si los clientes se encuentran en zonas horarias diferentes. Si usa este tipo de datos, podrían perderse transacciones si se seleccionan cambios incrementales.
El tipo de datos timestamp es preciso y no depende de la hora. No obstante, cada tabla de una base de datos de SQL Server puede contener únicamente una columna de este tipo de datos. Por tanto, si necesita distinguir las inserciones de las actualizaciones, puede agregar una columna de otro tipo de datos, como binary(8), y almacenar en ella los valores de marca de tiempo. Para obtener un ejemplo, vea Scripts de configuración para los temas de procedimientos del proveedor de base de datos. El tipo de datos timestamp puede causar problemas cuando se restablezca la base de datos servidor desde una copia de seguridad. Para obtener más información, vea Objetos de bases de compatibles con Sync Framework. Como se indicó anteriormente, se recomienda usar MIN_ACTIVE_ROWVERSION en el comando que selecciona un delimitador nuevo.
Identificar el cliente que realiza el cambio de datos
Existen dos motivos principales para la identificación del cliente que realiza cambios de datos:
Admitir la detección y resolución de conflictos en una sincronización bidireccional y de sólo carga.
Cuando el servidor y uno o más clientes pueden cambiar una determinada fila, es posible que desee identificar quién efectúa el cambio. Esta información permite, por ejemplo, escribir un código que dé prioridad a un cambio sobre otro. Sin esta información, persistiría el último cambio efectuado en la fila.
Impedir mostrar los cambios al cliente durante la sincronización bidireccional.
Sync Framework transmite en primer lugar los cambios al servidor y, a continuación, los descarga en el cliente. Si no lleva un seguimiento de la identidad del cliente que efectúa un cambio, el cambio se cargará en el servidor y seguidamente en el cliente durante la misma sesión de sincronización. En algunas situaciones puede ser adecuado mostrar los cambios, pero en otras no.
Al igual que con el seguimiento de los cambios, Sync Framework no define específicamente como se admite el seguimiento de la identidad; no obstante, existe un método habitual para abordarlo. Use el planteamiento siguiente para cada tabla que desee sincronizar:
Agregar una columna a la tabla base que lleve un seguimiento de quién efectúa cada inserción.
Agregar una columna a la tabla base que lleve un seguimiento de quién efectúa cada actualización.
Agregar una columna a la tabla de marcadores de exclusión que lleve un seguimiento de quién efectúa cada eliminación.
Estas columnas y tablas se usan con la propiedad ClientId para determinar qué cliente efectúa cada inserción, actualización o eliminación. La primera vez que se sincroniza una tabla con un método distinto a la sincronización de instantáneas, Sync Framework almacena un valor GUID en el cliente con el que identifica a dicho cliente. Este identificador se pasa a DbServerSyncProvider para que puedan usarlo las consultas de selección y actualización en cada SyncAdapter. El valor del identificador está disponible mediante la propiedad ClientId. Considere la consulta Transact-SQL siguiente:
SELECT CustomerId, CustomerName, SalesPerson, CustomerType FROM
Sales.Customer WHERE InsertTimestamp > @sync_last_received_anchor AND
InsertTimestamp <= @sync_new_received_anchor AND InsertId <>
@sync_client_id
Esta consulta es parecida a la que realiza seguimientos de inserciones efectuadas en el servidor. La instrucción de la cláusula WHERE
asegura que las únicas inserciones que se descargan sean aquellas no efectuadas por el cliente que realiza en ese momento la sincronización.
Sync Framework también permite a las aplicaciones identificar clientes en el servidor usando un entero en lugar de un valor GUID. Para obtener más información, vea Usar variables de sesión.
Ejemplos de preparación del servidor
Los ejemplos siguientes muestran cómo configurar la tabla Sales.Customer
de la base de datos de ejemplo Sync Framework con la infraestructura de seguimiento necesaria para controlar el escenario de aplicación más complejo: operaciones de inserción, actualización y eliminación bidireccionales con detección de conflictos. Otros escenarios menos complicados no requieren la infraestructura completa. Para obtener más información, vea "Requisitos del servidor para escenarios de sincronización" en este tema. Para obtener un script completo que crea los objetos de este ejemplo, así como objetos adicionales, vea Scripts de configuración para los temas de procedimientos del proveedor de base de datos. Para obtener más información sobre el uso de estos objetos, vea Especificar una sincronización de instantáneas, de descarga, de carga y bidireccional.
Los ejemplos de esta sección realizan los pasos siguientes para preparar un servidor:
Verificar el esquema de
Sales.Customer
. Determinar si la tabla tiene una clave principal y columnas que puedan usarse para el seguimiento de cambios.Agregar columnas para el seguimiento de dónde y cuándo se efectúan inserciones y actualizaciones.
Crear una tabla de marcadores de exclusión y agregar un desencadenador a la tabla
Sales.Customer
para rellenar la tabla de marcadores de exclusión.
Comprobar el esquema de Sales.Customer
En el ejemplo de código siguiente se muestra el esquema de la tabla Sales.Customer
. La tabla tiene una clave principal en la columna CustomerId
y no tiene columnas que puedan utilizarse para el seguimiento de los cambios.
CREATE TABLE SyncSamplesDb.Sales.Customer(
CustomerId uniqueidentifier NOT NULL PRIMARY KEY DEFAULT NEWID(),
CustomerName nvarchar(100) NOT NULL,
SalesPerson nvarchar(100) NOT NULL,
CustomerType nvarchar(100) NOT NULL)
Agregar columnas para realizar el seguimiento de operaciones de inserción y actualización
En el ejemplo de código siguiente se agregan cuatro columnas: UpdateTimestamp
, InsertTimestamp
, UpdateId
e InsertId
. La columna UpdateTimestamp
es una columna timestamp
de SQL Server. Esta columna se actualiza automáticamente cuando se actualiza la fila. Como se indicó anteriormente, una tabla sólo puede tener una columna timestamp
. Por lo tanto, la columna InsertTimestamp
es una columna binary(8)
cuyo valor predeterminado es @@DBTS + 1
. El ejemplo agrega al valor devuelto por @@DBTS
para que las columnas UpdateTimestamp
y InsertTimestamp
tengan el mismo valor después de una operación de inserción. En caso contrario, parecería que cada fila se hubiese actualizado después de su inserción.
El identificador que crea Sync Framework para cada cliente es un GUID; por lo tanto, las dos columnas de identificador son columnas uniqueidentifier
. Las columnas tienen un valor predeterminado de 00000000-0000-0000-0000-000000000000
. Este valor indica que el servidor realizó la actualización o inserción. Un ejemplo posterior incluye una columna DeleteId
en la tabla de marcadores de exclusión.
ALTER TABLE SyncSamplesDb.Sales.Customer
ADD UpdateTimestamp timestamp
ALTER TABLE SyncSamplesDb.Sales.Customer
ADD InsertTimestamp binary(8) DEFAULT @@DBTS + 1
ALTER TABLE SyncSamplesDb.Sales.Customer
ADD UpdateId uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'
ALTER TABLE SyncSamplesDb.Sales.Customer
ADD InsertId uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000'
Una vez agregadas las columnas, el código de ejemplo siguiente agrega los índices. Estos y otros índices del ejemplo de código se crean en columnas en las que se realizan consultas durante la sincronización. Los índices se agregan para recalcar que se deben tener en cuenta los índices cuando se implemente el seguimiento de cambios en la base de datos servidor. Asegúrese de mantener un equilibrio entre el rendimiento del servidor y el rendimiento de la sincronización.
CREATE NONCLUSTERED INDEX IX_Customer_UpdateTimestamp
ON Sales.Customer(UpdateTimestamp)
CREATE NONCLUSTERED INDEX IX_Customer_InsertTimestamp
ON Sales.Customer(InsertTimestamp)
CREATE NONCLUSTERED INDEX IX_Customer_UpdateId
ON Sales.Customer(UpdateId)
CREATE NONCLUSTERED INDEX IX_Customer_InsertId
ON Sales.Customer(InsertId)
Agregar una tabla de marcadores de exclusión para el seguimiento de las operaciones de eliminación
El ejemplo de código siguiente crea una tabla de marcadores de exclusión que tiene un índice clúster y un desencadenador para rellenar la tabla. Cuando se produce una operación de eliminación en la tabla Sales.Customer
, el desencadenador inserta una fila en la tabla Sales.Customer_Tombstone
. Antes de que el desencadenador ejecute una operación de inserción, el desencadenador comprueba si la tabla Sales.Customer_Tombstone
ya contiene una fila con la clave principal de una fila eliminada. Esto sucede si se elimina una fila de Sales.Customer
, se vuelve a insertar y se elimina de nuevo. Si se detecta una fila de este tipo en Sales.Customer_Tombstone
, el desencadenador la elimina y la vuelve a insertar. Es posible que la columna DeleteTimestamp
de Sales.Customer_Tombstone
también se actualice.
CREATE TABLE SyncSamplesDb.Sales.Customer_Tombstone(
CustomerId uniqueidentifier NOT NULL PRIMARY KEY NONCLUSTERED,
CustomerName nvarchar(100) NOT NULL,
SalesPerson nvarchar(100) NOT NULL,
CustomerType nvarchar(100) NOT NULL,
DeleteId uniqueidentifier NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000',
DeleteTimestamp timestamp)
CREATE TRIGGER Customer_DeleteTrigger
ON SyncSamplesDb.Sales.Customer FOR DELETE
AS
BEGIN
SET NOCOUNT ON
DELETE FROM SyncSamplesDb.Sales.Customer_Tombstone
WHERE CustomerId IN (SELECT CustomerId FROM deleted)
INSERT INTO SyncSamplesDb.Sales.Customer_Tombstone (CustomerId, CustomerName, SalesPerson, CustomerType)
SELECT CustomerId, CustomerName, SalesPerson, CustomerType FROM deleted
SET NOCOUNT OFF
END
CREATE CLUSTERED INDEX IX_Customer_Tombstone_DeleteTimestamp
ON Sales.Customer_Tombstone(DeleteTimestamp)
CREATE NONCLUSTERED INDEX IX_Customer_Tombstone_DeleteId
ON Sales.Customer_Tombstone(DeleteId)