다음을 통해 공유


마지막 기록기 충돌 검색 및 해결 구성

SQL Server 2019 (15.x) CU 13부터 가장 최근의 삽입 또는 업데이트가 충돌에 우선하도록 허용하여 충돌을 자동으로 해결하도록 피어 투 피어 복제를 구성할 수 있습니다. 쓰기 중 하나가 행을 삭제하면 SQL Server에서 삭제가 충돌을 이길 수 있습니다. 이 방법을 마지막 쓰기 우선이라고 합니다.

저장 프로시저를 사용하여 마지막 쓰기 우선을 구성합니다. 마지막 기록기를 사용할 때는 피어 투 피어 토폴로지 마법사를 사용하여 노드를 추가하거나 제거하지 마세요.

중요한 구성 고려 사항

참고 항목

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은(는) 시작자 ID를 기반으로 충돌을 해결하며 SQL Server 2019(15.x) CU 13 이전의 충돌 해결과 동일합니다.
    • 배포 에이전트가 충돌을 해결할 수 있도록 @p2p_continue_onconflict = 'true'을(를) 설정합니다.
  • 아티클(sp_addarticle)을 추가할 때 업데이트 명령(@upd_cmd)에 대한 명령 유형 동작을 확인합니다. 표시되는 옵션은 다음과 같습니다.

    • CALL(기본값)
    • SCALL

    자세한 내용은 sp_addarticle(Transact-SQL)을 참조하세요.

  • 마지막 기록기 충돌 검색 정책을 사용하여 게시에 아티클(sp_addarticle)을 추가하고 @upd_cmd 매개 변수에 대한 명령 유형으로 CALL 또는 SCALL을 사용하는 경우 CALL이 기본값입니다.

    참고 항목

    SQL Server는 @upd_cmd에 대해 SCALL을(를) 지원합니다. SCALL의 경우 트랜잭션이 동일한 값으로 값을 업데이트하면 변경으로 간주되지 않으며 SCALL 형식은 업데이트되거나 수정되지 않은 열에 대한 값을 제공하지 않습니다. 저장 프로시저에 대한 호출 구문인 SCALL 호출 형식에 대한 자세한 내용은 다음을 검토하세요.

  • 가용성 그룹에서 마지막 기록기 충돌 검색 및 해결과 함께 피어 투 피어 게시를 사용할 수 있습니다. 참조

  • 충돌과 그 해결을 확인할 수 있습니다.

    • SQL Server Management Studio에서 게시를 마우스 오른쪽 단추로 클릭하고 충돌 보기를 선택합니다.

    또는

  • 삽입 및 업데이트 충돌은 마지막 기록기를 기반으로 해결되지만 삭제는 항상 우선합니다. 예를 들어 삭제-업데이트 충돌이 있고 나중에 업데이트가 수행된 경우에도 삭제가 우선합니다.

  • 마지막 기록기 충돌 검색 및 해결 방법은 숨겨진 $sys_mw_cd_id 열을 기반으로 결정됩니다. 이 열의 데이터 형식은 datetime2입니다.

충돌 검색 비교

다음 표에서는 기존 피어 투 피어 복제를 사용하여 충돌을 검색 및 해결하는 방법과 마지막 기록기 충돌 해결이 사용되는 시기를 비교합니다.

충돌 유형 충돌 세부 정보 피어 투 피어 마지막 작성자
Insert-Insert 피어 투 피어 복제에 참여하는 각 테이블의 모든 행은 기본 키 값을 사용하여 고유하게 식별됩니다. 키 값이 같은 행이 둘 이상의 노드에 삽입된 경우 insert-insert 충돌이 발생합니다. 들어오는 행이 우선인 경우 대상 행을 업데이트합니다. 두 경우 모두 정보를 기록합니다. 들어오는 행이 우선인 경우 대상 행을 업데이트합니다. 두 경우 모두 정보를 기록합니다.
Update-Update 둘 이상의 노드에서 동일한 행이 업데이트되었을 때 발생합니다. 들어오는 행이 승자이면 변경된 열만 수정합니다. 들어오는 행이 우선인 경우 대상의 모든 열을 수정합니다(@upd_cmddefault로 설정된 경우 – CALL).
Update-Insert 하나의 노드에서 행이 업데이트되었지만 동일한 행이 삭제되었다가 다른 하나의 노드에서 다시 삽입된 경우 발생합니다. 들어오는 행이 승자이면 변경된 열만 수정합니다. 이는 peer1에서 행이 업데이트되고 동일한 행이 삭제되고 peer2에 다시 삽입될 때 발생합니다. 동기화가 발생하면 삭제가 항상 우선하므로 peer1의 행이 삭제되고 동일한 행이 삽입되는 반면, 나중에 업데이트가 발생하므로 행이 peer2에서 업데이트됩니다. 이로 인해 비대화가 발생할 수 있습니다.
Insert-Update 하나의 노드에서 행이 삭제된 다음 다시 삽입되고 동일한 행이 다른 노드에서 업데이트된 경우 발생합니다. 들어오는 행이 우선인 경우 모든 행이 업데이트됩니다. 이는 peer1에서 행을 삭제하고 다시 삽입하고 peer2에서 동일한 행을 업데이트할 때 발생합니다. 동기화가 발생하면 delete가 항상 우선하므로 peer2에서 행이 삭제된 다음 다시 삽입됩니다. peer1에서 업데이트를 건너뜁니다.
Delete-Insert

Insert-Delete
하나의 노드에서 행이 삭제되었지만 동일한 행이 삭제되었다가 다른 하나의 노드에서 다시 삽입된 경우 발생합니다. 현재는 이를 D-U 충돌로 간주하고, 들어오는 행이 우선인 경우 대상에서 행을 삭제합니다. 이는 peer1에서 행이 삭제되고 동일한 행이 삭제되고 peer2에 다시 삽입될 때 발생합니다. 동기화가 발생하면 peer2의 행이 삭제되는 반면 행은 peer1에 삽입됩니다. 이는 삭제된 행에 대한 정보를 저장하지 않기 때문에 발생하므로, 행이 삭제되었는지 또는 피어에 없었는지 알 수 없습니다. 이로 인해 비대화가 발생할 수 있습니다.
Delete-Update 하나의 노드에서 행이 삭제되었지만 다른 노드에서 동일한 행이 업데이트된 경우 발생합니다. 현재는 이를 D-U 충돌로 간주하고, 들어오는 행이 우선인 경우 대상에서 행을 삭제합니다. 이는 D-U 충돌입니다. 삭제가 항상 우선하므로 들어오는 삭제가 승자가 되며 대상에서 행을 삭제합니다.
Update-Delete 하나의 노드에서 행이 업데이트되었지만 다른 노드에서 동일한 행이 삭제된 경우 발생합니다. 피어 투 피어 업데이트 저장 프로시저에서 U-D 충돌이 있는 경우 다음 메시지를 출력하고 해결하지 않습니다.

An update-delete conflict was detected and unresolved. The row could not be updated since the row does not exist.
이는 U-D 충돌입니다. 삭제가 항상 적용되므로 들어오는 업데이트는 건너뜁습니다.
Delete-Delete 둘 이상의 노드에서 행이 삭제되었을 때 발생합니다. 피어 투 피어 삭제 저장 프로시저에서 D-D 충돌이 있는 경우 변경 사항을 처리하지 않고 기록만 합니다. D-D 충돌이 있는 경우 변경 사항을 처리하지 않고 기록만 합니다.

참고 항목

마지막 기록기 충돌 검색 정책의 현재 구현에서 삭제는 insert-delete, delete-insert 또는 update-delete 충돌이 있을 때 항상 우선합니다.

예제

첫 번째 피어(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