Freigeben über


Konfigurieren der Konflikterkennung und -lösung durch letzten Writer

Ab SQL Server 2019 (15.x) CU 13 können Sie die Peer-zu-Peer-Replikation so konfigurieren, dass Konflikte automatisch gelöst werden, indem die letzte Einfügung oder Aktualisierung den Konflikt gewinnt. Wenn ein Schreibvorgang die Zeile löscht, lässt SQL Server zu, dass der Löschvorgang den Konflikt gewinnt. Diese Methode wird als „last write wins“ (letzter Schreibvorgang gewinnt) bezeichnet.

Verwenden Sie gespeicherte Prozeduren, um den letzten Schreibvorgang zu konfigurieren. Verwenden Sie den Peer-to-Peer-Topologie-Assistenten nicht, um Knoten hinzuzufügen oder zu entfernen, wenn Sie den letzten Writer verwenden.

Wichtige Überlegungen zur Konfiguration

Hinweis

Wenn Sie nach dem Aktualisieren auf SQL Server 2019 (15.x) CU13 oder höher eine Veröffentlichung konfigurieren, bei der die Konfliktlösung auf den letzten Schreibvorgang festgelegt ist, werden zusätzliche Metadaten in die Veröffentlichung eingeschlossen. Wenn Sie SQL Server 2019 (15.x) CU13 später deinstallieren oder auf ein früheres Release downgraden, verursachen diese zusätzlichen Metadaten Probleme. Es wird empfohlen, solche Veröffentlichungen vor dem Downgrade zu trennen und sie dann in der früheren Version neu zu erstellen.

Wenn Sie die Peer-zu-Peer-Replikation mit automatischer Konflikterkennung und -lösung so konfigurieren, dass Konflikte durch den letzten Schreibvorgang gelöst werden, wenden Sie die folgenden Konfigurationen und Einstellungen an:

  • Erstellen Sie die Veröffentlichung mit den folgenden Parametern:

    • Legen Sie @p2p_conflictdetection_policy = 'lastwriter' so fest, dass der letzte Schreibvorgang den Konflikt löst. Weitere Informationen finden Sie in sp_addpublication (Transact-SQL). Dieser Parameter wird in SQL Server 2019 (15.x) CU 13 eingeführt. Der Standardwert originatorid löst Konflikte basierend auf der Absender-ID und entspricht der Konfliktlösung vor SQL Server 2019 (15.x) CU 13.
    • Legen Sie diese Einstellung @p2p_continue_onconflict = 'true' fest, damit der Verteilungs-Agent den Konflikt lösen kann.
  • Wenn Sie den Artikel (sp_addarticle) hinzufügen, bestätigen Sie das Verhalten des Befehlstyps für den Updatebefehl (@upd_cmd). Beispiele für Optionen:

    • CALL (Standard)
    • SCALL

    Weitere Details finden Sie in sp_addarticle (Transact-SQL).

  • Wenn Sie einen Artikel (sp_addarticle) einer Veröffentlichung mit einer Richtlinie für die Konflikterkennung mithilfe des letzten Schreibvorgangs, USE CALL oder SCALL als Befehlstyp für Parameter @upd_cmd hinzufügen, ist CALL der Standard.

    Hinweis

    SQL Server unterstützt SCALL für @upd_cmd. Wenn eine Transaktion einen Wert auf denselben Wert aktualisiert, wird er mit SCALL nicht als Änderung betrachtet, und das Format SCALL stellt den Wert nicht für Spalten bereit, die nicht aktualisiert oder geändert wurden. Ausführliche Informationen zum SCALL-Aufrufformat finden Sie unter Aufrufsyntax für gespeicherte Prozeduren.

  • Sie können die Peer-zu-Peer-Veröffentlichung mit Konflikterkennung und -lösung durch den letzten Schreibvorgang in einer Verfügbarkeitsgruppe nutzen. Thema

  • Sie können den Konflikt und die Lösung einsehen.

    • Klicken Sie in SQL Server Management Studio mit der rechten Maustaste auf die Publikation, und klicken Sie dann auf Konflikte anzeigen.

    Oder

  • Einfüge- und Updatekonflikte werden auf der Grundlage des letzten Schreibvorgangs gelöst, doch Löschen ist immer die höchste Instanz. Wenn Sie zum Beispiel einen Löschen-Update-Konflikt haben und das Update später ausgeführt wurde, wird dennoch gelöscht.

  • Konflikterkennung und -lösung mithilfe des letzten Schreibvorgangs wird basierend auf einer ausgeblendeten Spalte $sys_mw_cd_id geregelt. Der Datentyp dieser Spalte ist datetime2.

Konflikterkennungsvergleich

In der folgenden Tabelle wird verglichen, wie Konflikte mit herkömmlicher Peer-zu-Peer-Replikation erkannt und gelöst werden und wann die Konfliktlösung mithilfe des letzten Schreibvorgangs aktiviert ist:

Konflikttyp Konfliktdetails Peer-to-Peer Letzter Schreibvorgang
Insert-insert Alle Zeilen in einer Tabelle, die an der Peer-zu-Peer-Replikation beteiligt sind, werden mithilfe von Primärschlüsselwerten eindeutig identifiziert. Ein Konflikt vom Typ "insert-insert" tritt auf, wenn eine Zeile mit demselben Schlüsselwert an mehreren Knoten eingefügt wurde. Wenn die eingehende Zeile gewinnt, aktualisieren Sie die Zielzeile. In beiden Fällen werden die Informationen aufgezeichnet. Wenn die eingehende Zeile gewinnt, aktualisieren Sie die Zielzeile. In beiden Fällen werden die Informationen aufgezeichnet.
Update-update Tritt auf, wenn eine Zeile an mehreren Knoten aktualisiert wurde. Wenn die eingehende Zeile gewinnt, ändern Sie NUR die geänderten Spalten. Wenn die eingehende Zeile gewinnt, ändern Sie alle Spalten im Zielort (wenn @upd_cmd als default festgelegt ist – CALL).
Update-insert Dies tritt auf, wenn eine Zeile an einem Knoten aktualisiert wurde, diese Zeile jedoch gelöscht und dann an einem anderen Knoten wieder eingefügt wurde. Wenn die eingehende Zeile gewinnt, ändern Sie NUR die geänderten Spalten. Dies tritt auf, wenn eine Zeile auf Peer1 aktualisiert wird und dieselbe Zeile gelöscht und auf Peer2 erneut eingefügt wird. Wenn die Synchronisierung erfolgt, wird die Zeile auf Peer1 gelöscht, da Löschen immer gewinnt. Dann wird dieselbe Zeile wieder eingefügt. Die Zeile auf Peer2 hingegen wird aktualisiert, da die Aktualisierung zu einem späteren Zeitpunkt erfolgte. Dies führt zu Nichtübereinfluss.
Insert-update Dies tritt auf, wenn eine Zeile an einem Knoten gelöscht und dann wieder eingefügt wurde und dieselbe Zeile an einem anderen Knoten aktualisiert wurde. Wenn die eingehende Zeile gewinnt, aktualisieren Sie alle Spalten. Dies tritt auf, wenn eine Zeile auf Peer1 gelöscht und wieder eingefügt wird und dieselbe Zeile auf Peer2 aktualisiert wird. Wenn die Synchronisierung erfolgt, wird die Zeile auf Peer2 gelöscht, da Löschen immer gewinnt, und dann wieder eingefügt. Auf Peer1 wird die Aktualisierung übersprungen.
Delete-Insert

Insert-Delete
Tritt auf, wenn eine Zeile an einem Knoten gelöscht wurde, diese Zeile aber gelöscht und dann an einem anderen Knoten erneut eingefügt wurde. Dies wird derzeit als D-u-Konflikt betrachtet. Wenn die eingehende Zeile gewinnt, löschen Sie die Zeile aus dem Ziel. Dies tritt auf, wenn eine Zeile auf Peer1 gelöscht und dieselbe Zeile auf Peer2 gelöscht und wieder eingefügt wird. Wenn die Synchronisierung erfolgt, wird die Zeile auf Peer2 gelöscht. Auf Peer1 hingegen wird die Zeile eingefügt. Dies liegt daran, dass wir keine Informationen über die gelöschte Zeile speichern und so nicht wissen, ob die Zeile gelöscht wurde oder auf dem Peer nicht vorhanden war. Dies führt zu Nichtübereinfluss.
Delete-Update Dies tritt auf, wenn eine Zeile an einem Knoten gelöscht wurde, dieselbe Zeile jedoch an einem anderen Knoten aktualisiert wurde. Dies wird derzeit als D-u-Konflikt betrachtet. Wenn die eingehende Zeile gewinnt, löschen Sie die Zeile aus dem Ziel. Dies ist ein D-u-Konflikt. Da Löschen immer gewinnt, gewinnt das eingehende Löschen, und Sie löschen die Zeile aus dem Ziel.
Update-Delete Tritt auf, wenn eine Zeile an einem Knoten aktualisiert wurde, diese Zeile aber an einem anderen Knoten gelöscht wurde. Wenn in der gespeicherten Prozedur „Peer-to-Peer Update“ ein U-d-Konflikt vorliegt, geben Sie die folgende Meldung aus und lösen den Konflikt nicht.

An update-delete conflict was detected and unresolved. The row could not be updated since the row does not exist.
Dies ist ein U-d-Konflikt. Da das Löschen immer gewinnt, wird die eingehende Aktualisierung übersprungen.
Delete-Delete Tritt auf, wenn eine Zeile an mehreren Knoten gelöscht wurde. Wenn in der gespeicherten Prozedur „Peer-to-Peer Delete“ ein D-d-Konflikt vorliegt, wenden Sie keine Änderungen an und zeichnen sie nur auf. Wenn ein D-d-Konflikt vorliegt, verarbeiten Sie keine Änderungen und zeichnen sie nur auf.

Hinweis

Aufgrund der aktuellen Implementierung der Konflikterkennungsrichtlinie mithilfe des letzten Schreibvorgangs gewinnt das Löschen immer dann, wenn ein Insert-delete-, Delete-insert- oder ein Update-delete-Konflikt vorliegt.

Beispiele

Erstellen einer Veröffentlichung auf dem ersten Peer (Node1)

Zum Beispiel das Skript:

  • Veröffentlicht eine Datenbank mit dem Namen MWPubDB.
  • Benennt die Veröffentlichung PublMW.
  • Konfiguriert die Konflikterkennungs- und -lösungsrichtlinie, sodass der letzte Schreibvorgang gewinnt:
    , @p2p_continue_onconflict= 'true'
    , @p2p_conflictdetection_policy = 'lastwriter'
USE [MWPubDB]
EXEC sp_replicationdboption @dbname = N'MWPubDB'
  , @optname = N'publish'
  , @value = N'true'
GO
-- Adding the transactional publication
USE [MWPubDB]
EXEC sp_addpublication @publication = N'PublMW'
  , @description = N'Peer-to-Peer publication of database ''MWPubDB'' from Publisher ''Node1''.'
  , @sync_method = N'native'
  , @retention = 0
  , @allow_push = N'true'
  , @allow_pull = N'true'
  , @allow_anonymous = N'false'
  , @enabled_for_internet = N'false'
  , @snapshot_in_defaultfolder = N'true'
  , @compress_snapshot = N'false'
  , @ftp_port = 21
  , @allow_subscription_copy = N'false'
  , @add_to_active_directory = N'false'
  , @repl_freq = N'continuous'
  , @status = N'active'
  , @independent_agent = N'true'
  , @immediate_sync = N'true'
  , @allow_sync_tran = N'false'
  , @allow_queued_tran = N'false'
  , @allow_dts = N'false'
  , @replicate_ddl = 1, @allow_initialize_from_backup = N'true'
  , @enabled_for_p2p = N'true'
  , @enabled_for_het_sub = N'false'
  , @p2p_conflictdetection = N'true'
  , @p2p_originator_id = 100
  , @p2p_continue_onconflict= 'true'
  , @p2p_conflictdetection_policy = 'lastwriter'
GO

USE [MWPubDB]
EXEC sp_addarticle @publication = N'PublMW'
  , @article = N'tab1'
  , @source_owner = N'dbo'
  , @source_object = N'tab1'
  , @type = N'logbased'
  , @description = null
  , @creation_script = null
  , @pre_creation_cmd = N'drop'
  , @schema_option = 0x0000000008035DDB
  , @identityrangemanagementoption = N'manual'
  , @destination_table = N'tab1'
  , @destination_owner = N'dbo'
  , @status = 16
  , @vertical_partition = N'false'
  , @ins_cmd = N'CALL sp_MSins_dbotab1'
  , @del_cmd = N'CALL sp_MSdel_dbotab1'
  , @upd_cmd = N'CALL sp_MSupd_dbotab1'
GO

Erstellen einer Veröffentlichung auf dem zweiten Peer (Node2)

Das folgende Skript erstellt die Veröffentlichung auf dem zweiten Peer (Node2).

USE [MWPubDB]
EXEC sp_replicationdboption @dbname = N'MWPubDB'
 , @optname = N'publish'
 , @value = N'true'
GO
-- Adding the transactional publication
USE [MWPubDB]
EXEC sp_addpublication @publication = N'PublMW'
 , @description = N'Peer-to-Peer publication of database ''MWPubDB'' from Publisher ''Node2''.'
 ,@sync_method = N'native'
 , @retention = 0, @allow_push = N'true'
 , @allow_pull = N'true'
 , @allow_anonymous = N'false'
 , @enabled_for_internet = N'false'
 , @snapshot_in_defaultfolder = N'true'
 , @compress_snapshot = N'false'
 , @ftp_port = 21, @allow_subscription_copy = N'false'
 , @add_to_active_directory = N'false'
 , @repl_freq = N'continuous'
 , @status = N'active'
 , @independent_agent = N'true'
 , @immediate_sync = N'true'
 , @allow_sync_tran = N'false'
 , @allow_queued_tran = N'false'
 , @allow_dts = N'false'
 , @replicate_ddl = 1, @allow_initialize_from_backup = N'true'
 , @enabled_for_p2p = N'true'
 , @enabled_for_het_sub = N'false'
 , @p2p_conflictdetection = N'true'
 , @p2p_originator_id = 1
 , @p2p_continue_onconflict= 'true'
,  @p2p_conflictdetection_policy = 'lastwriter'
GO

USE [MWPubDB]
EXEC sp_addarticle @publication = N'PublMW'
 , @article = N'tab1'
 , @source_owner = N'dbo'
 , @source_object = N'tab1'
 , @type = N'logbased'
 , @description = null
 , @creation_script = null
 , @pre_creation_cmd = N'drop'
 , @schema_option = 0x0000000008035DDB
 , @identityrangemanagementoption = N'manual'
 , @destination_table = N'tab1'
 , @destination_owner = N'dbo'
 , @status = 16, @vertical_partition = N'false'
 , @ins_cmd = N'CALL sp_MSins_dbotab1'
 , @del_cmd = N'CALL sp_MSdel_dbotab1'
 , @upd_cmd = N'CALL sp_MSupd_dbotab1'
GO

Erstellen eines Abonnements von Node1 nach Node2

USE [MWPubDB]
EXEC sp_addsubscription @publication = N'PublMW'
 , @subscriber = N'Node2'
 , @destination_db = N'MWPubDB'
 , @subscription_type = N'Push'
 , @sync_type = N'replication support only'
 , @article = N'all'
 , @update_mode = N'read only'
 , @subscriber_type = 0
GO
EXEC sp_addpushsubscription_agent @publication = N'PublMW'
 , @subscriber = N'Node2'
 , @subscriber_db = N'MWPubDB'
 , @job_login = null
 , @job_password = null
 , @subscriber_security_mode = 1
 , @frequency_type = 64
 , @frequency_interval = 1
 , @frequency_relative_interval = 1
 , @frequency_recurrence_factor = 0
 , @frequency_subday = 4
 , @frequency_subday_interval = 5
 , @active_start_time_of_day = 0
 , @active_end_time_of_day = 235959
 , @active_start_date = 0 
 , @active_end_date = 0
 , @dts_package_location = N'Distributor'
GO

Erstellen eines Abonnements von Node2 nach Node1

USE [MWPubDB]
EXEC sp_addsubscription @publication = N'PublMW'
 , @subscriber = N'Node1'
 , @destination_db = N'MWPubDB'
 , @subscription_type = N'Push',
@sync_type = N'replication support only'
 , @article = N'all'
 , @update_mode = N'read only'
 , @subscriber_type = 0
go
EXEC sp_addpushsubscription_agent @publication = N'PublMW'
 , @subscriber = N'Node1'
 , @subscriber_db = N'MWPubDB'
 , @job_login = null
 , @job_password = null
 , @subscriber_security_mode = 1
 , @frequency_type = 64
 , @frequency_interval = 1
 , @frequency_relative_interval = 1
 , @frequency_recurrence_factor = 0
 , @frequency_subday = 4
 , @frequency_subday_interval = 5
 , @active_start_time_of_day = 0
 , @active_end_time_of_day = 235959
 , @active_start_date = 0
 , @active_end_date = 0
 , @dts_package_location = N'Distributor'
GO