分享方式:


設定最後寫入器衝突偵測與解決

從 SQL Server 2019 (15.x) CU 13 開始,您可以設定在點對點複寫時,允許最新的插入或更新贏得衝突,以自動解決衝突。 如果任一寫入刪除資料列,SQL Server 則允許該刪除贏得衝突。 此方法稱為最後寫入勝出。

使用預存程序來設定最後寫入勝出。 使用最後一個寫入器時,請勿使用點對點拓撲精靈來新增或移除節點。

重要設定考量

注意

更新至 2019 SQL Server 2019 (15.x) CU13 或更新版本之後,當您將發行集的衝突解決方法設定為最後寫入勝出時,會將額外中繼資料包含在發行集中。 如果您之後解除安裝或降級為低於 SQL Server 2019 (15.x) CU13 的版本,這些額外中繼資料會造成問題。 建議您在降級之前,先卸除任何這類發行集,然後在較低版本上重新建立。

當您為點對點複寫設定自動衝突探索和解決方法,以最後寫入勝出來解決時,請包含下列組態和設定:

  • 使用下列參數建立發行集,

    • 設定 @p2p_conflictdetection_policy = 'lastwriter' 以指定最後寫入勝出。 請參閱 sp_addpublication (Transact-SQL)。 此參數在 SQL Server 2019 (15.x) CU 13 中引進。 預設值 originatorid 會根據來源識別碼解決衝突,而且與 2019 SQL Server (15.x) CU 13 之前的衝突解決方法相同。
    • 設定 @p2p_continue_onconflict = 'true' 以允許散發代理程式解決衝突。
  • 當您新增發行項 (sp_addarticle) 時,請確認更新命令 (@upd_cmd) 的命令類型行為。 這些選項包括:

    • CALL (預設值)
    • SCALL

    請參閱 sp_addarticle (Transact-SQL) 中的詳細資料。

  • 當您在具有最後寫入器衝突偵測原則的發行集中新增發行項 (sp_addarticle) 時,請使用 CALLSCALL 作為 @upd_cmd 參數的命令類型,預設為 CALL

    注意

    SQL Server 對 @upd_cmd 支援 SCALL。 使用 SCALL 時,當交易將值更新為相同的值時,不會被視為變更,而且 SCALL 格式不會提供未更新或修改之資料行的值。 如需 SCALL 呼叫格式的詳細資料,請檢閱下列內容 - 預存程序的呼叫語法

  • 您可以在可用性群組中搭配最後寫入器衝突偵測和解決使用點對點發行集。 請參閱:

  • 您可以看到衝突及其解決方法。

    • 在 SQL Server Management Studio 中,以滑鼠右鍵按一下發行集,然後選取 [檢視衝突]。

    Or

  • 插入和更新衝突會根據最後寫入器解決,但刪除一律勝出。 例如,如果您有刪除對更新的衝突,且更新較晚進行,則刪除仍會勝出。

  • 最後寫入器衝突偵測和解決會根據隱藏的資料行 $sys_mw_cd_id 決定。 此資料行的資料類型為 datetime2

衝突偵測比較

下表比較傳統點對點複寫以及啟用最後寫入器衝突解決方法時,分別如何偵測及解決衝突:

衝突類型 衝突詳細資料面板 對等 最後寫入器
插入對插入 參與點對點複寫之每份資料表中的所有資料列都會使用主索引鍵值進行唯一識別。 在多個節點上插入具有相同索引鍵值的資料列時,就會發生插入對插入的衝突。 如果傳入的資料列勝出,我們會更新目的地資料列。 不論是哪一種情況,我們都會記錄資訊。 如果傳入的資料列勝出,我們會更新目的地資料列。 不論是哪一種情況,我們都會記錄資訊。
更新對更新 在多個節點上更新相同的資料列時發生。 如果傳入的資料列勝出,我們「只」會修改變更的資料行。 如果傳入的資料列勝出,我們會修改目的地的所有資料行 (如果 @upd_cmd 設為 default - CALL)。
更新對插入 在某個節點上更新一個資料列,但是在另一個節點上刪除並重新插入同一個資料列時,就會發生。 如果傳入的資料列勝出,我們「只」會修改變更的資料行。 在 peer1 上更新資料列,而在 peer2 上刪除並重新插入同一個資料列時,就會發生。 同步處理時,由於刪除一律會勝出,因此會刪除 peer1 上的資料列,然後插入同一個資料列;由於更新較晚發生,因此會更新 peer2 上的資料列。 這會導致未聚合。
插入對更新 在某個節點上刪除並重新插入一個資料列,但是在另一個節點上更新同一個資料列時,就會發生。 如果傳入的資料列勝出,我們會更新所有資料行。 在 peer1 上刪除並重新插入一個資料列,而在 peer2 上更新同一個資料列時,就會發生。 同步處理時,由於刪除一律會勝出,因此會刪除 peer2 上的資料列,然後重新插入。 在 peer1 上,會略過更新。
刪除-插入

插入對刪除
在某個節點上刪除一個資料列,但是在另一個節點上刪除並重新插入相同的資料列時發生。 我們目前將此視為刪除對更新 (D-U) 衝突,如果傳入的資料列勝出,我們會從目的地刪除該資料列。 在 peer1 上刪除一個資料列,而在 peer2 上刪除並重新插入同一個資料列時,就會發生。 同步處理時,會刪除 peer2 上的資料列,並在 peer1 插入該資料列。 這樣做的原因是,我們不會儲存已刪除資料列的相關資訊,因此我們不知道資料列在同儕節點上是否已刪除或不存在。 這會導致未聚合。
刪除對更新 在某個節點上刪除一個資料列,但是在另一個節點上更新同一個資料列時,就會發生。 我們目前將此視為 D-U 衝突,如果傳入的資料列勝出,我們會從目的地刪除該資料列。 這是 D-U 衝突。 由於刪除一律優先,因此傳入的刪除會勝出,而我們會從目的地刪除資料列。
更新對刪除 在某個節點上插入一個資料列,但是在另一個節點上刪除相同的資料列時發生。 在點對點更新預存程序中,如果有更新對刪除 (U-D) 衝突,我們會列印下列訊息,且不會予以解決。

An update-delete conflict was detected and unresolved. The row could not be updated since the row does not exist.
這是 U-D 衝突。 由於刪除一律會勝出,因此會略過傳入的更新。
刪除對刪除 在多個節點上刪除資料列時發生。 在點對點刪除預存程序中,如果有刪除對刪除 (D-D) 衝突,我們就不會處理任何變更,只加以記錄。 如果發生 D-D 衝突,我們就不會處理任何變更,只加以記錄。

注意

在最後寫入器衝突偵測原則的目前實作中,刪除一律會在發生插入對刪除、刪除對插入或更新對刪除衝突中勝出。

範例

在第一個同儕節點 (Node1) 上建立發行集

在此範例中,指令碼是:

  • 發行名為 MWPubDB 的資料庫。
  • 將發行集命名為 PublMW
  • 將衝突偵測和解決原則設定為最後寫入成功:
    , @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

在第二個同儕節點 (Node2) 上建立發行集

下方指令碼會在第二個同儕節點 (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

建立從 Node1 到 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

建立從 Node2 到 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