2008-09-23 2 views
2

PayPal 인스턴트 결제 알림을받을 때 보험 상품 배송과 같은 제품을 만들 예정인 응용 프로그램을 만들고 있습니다. 유감스럽게도 PayPal에서는 중복 알림을 보내는 경우가 있습니다. 또한 PayPal로부터 업데이트를받을 때 동시에 웹 서비스 업데이트를 수행하는 제 3 자도 있습니다.중복 방지를 위해 IsolationLevel.RepeatableRead

다음은 관련된 데이터베이스 테이블의 기본 다이어그램입니다. 동시에이 일을 두 개 (또는 그 이상)의 스레드 (또는 프로세스 또는 응용 프로그램)이있는 경우

using (SqlConnection conn = new SqlConnection(...)) 
{ 
    sqlTransaction sqlTrans = conn.BeginTransaction(IsolationLevel.RepeatableRead); 

    // Calls a stored procedure that checks if the foreign key in the transaction table has a value. 
    if (PackageDB.HasInsurancePolicy(packageID, conn)) 
    { 
    sqlTrans.Commit(); 
    return false; 
    } 

    // Insert row in foreign table. 
    int policyID = InsurancePolicyDB.Insert(coverageAmount, conn); 
    if (policyID <= 0) 
    { 
    sqlTrans.Rollback(); 
    return false; 
    } 

    // Assign foreign key to parent table. If this fails, roll back everything. 
    bool assigned = PackageDB.AssignPolicyID(packageID, policyID, conn); 
    if (!assigned) 
    { 
    sqlTrans.Rollback(); 
    return false; 
    } 
} 

, 내가 원하는 : 여기

// table "package" 
// columns packageID, policyID, other data... 
// 
// table "insurancepolicy" 
// columns policyID, coverageAmount, other data... 

내가 뭘 원하는지의 기본이다 정책이 생성되고 policyID가 패키지 테이블에 할당 될 때까지 policyID가없는 동안 "패키지"행을 잠그는 첫 번째 스레드. 그런 다음 policyID가 패키지 테이블에 할당 된 후 잠금이 해제됩니다. 이 코드를 호출하는 다른 스레드가 package 행을 읽어 policyID가 없는지 확인하기 위해 일시 ​​중지됩니다. 첫 번째 트랜잭션의 잠금이 해제되면 두 번째 트랜잭션에서 policyID가 표시되어 정책 테이블에 행을 삽입하지 않고 반환되기를 바랍니다.

참고 : CRUD 데이터베이스 설계로 인해 각 저장 프로 시저에는 읽기 (선택), 만들기 (삽입) 또는 업데이트가 포함됩니다.

RepeatableRead 트랜잭션 격리를 올바르게 사용합니까?

감사합니다.

+0

패키지 란 무엇입니까? 정책 관계는 무엇입니까? 1시 1 분인가요? – Constantin

+0

n : 1입니다. 부모 테이블은 실제로 "패키지"를위한 테이블이 아니며 설명하기 위해 호출했습니다.실제로 이러한 "패키지"는 전체 소포에 대한 하나의 보험 정책과 함께 결합되어 배송 될 수있는 주문과 같습니다. – devlord

답변

1

insert into Policy이 중복 삽입 시도에서 일부 고유성 제약 조건을 맞춘다면 더 안전하고 깨끗합니다. 격리 수준을 높이면 동시성이 낮아지고 교착 상태와 같은 다른 불쾌한 문제가 발생할 수 있습니다. 충돌 사건 것으로 보인다 드문 경우

begin tran (read committed) 

/* tentatively insert new Policy */ 
insert Policy 

/* attach Package to Policy if it's still free */ 
update Package 
    set Package.policy_id = @policy_id 
    where Package.package_id = @package_id and Package.policy_id is null 

if @@rowcount > 0 
    commit 
else 
    rollback 

이가 가장 잘 작동합니다

또 다른 방법은 항상 정책 행을 삽입 패키지가 이미 정책에 부착되어있는 경우 다음을 롤백하는 것입니다.

+0

InsurancePolicy 테이블에 packageID 필드를 넣지 못하게하는 n : 1 관계에 관한 위의 내 의견을 참조하십시오. 감사! – devlord

+0

가능한 해결책을 추가했습니다. – Constantin

+0

감사! 왜 내가 "Package.policy_id가 null"이라고 생각하지 않는지 모르겠다. – devlord

1

저는 여러분이 실제로 Serializable 격리 수준을 원한다고 생각합니다. 문제는 두 개의 스레드가 HasInsurancePolicyCheck를 지나갈 수 있다는 것입니다. (InsurancePolicyDB.Insert가 어떤 일을하는지, 또는 0을 반환하는 이유는 모르겠지만)

이 외에도 여러 가지 옵션이 있습니다. 하나는 메시지 대기열을 사용하여 이러한 요청을 순차적으로 처리하는 것입니다. 다른 하나는 sp_getapplock을 사용하고 해당 패키지에 고유 한 키를 잠그는 것입니다. 그렇게하면 더 이상 행이나 테이블을 잠그지 않아야합니다.

+0

고마워요! 회사의 다른 누군가가 전용 대기열을 권장하고 있습니다. – devlord

0

나는 아론 젠센의 대답에서 "메시지 대기열"아이디어에 동의한다. 동일한 동시 데이터 행을 동시에 업데이트하려고 시도하는 여러 개의 동시 스레드가 염려되는 경우 스레드가 데이터를 작업 큐에 삽입 한 다음 단일 스레드에서 순차적으로 처리하도록해야합니다. 대상 테이블은 "N"대신 하나의 스레드 만 업데이트되고 작업 큐 작업은 메시징 스레드에 의한 삽입 및 데이터 처리 스레드에 의한 읽기/업데이트로 제한되므로 데이터베이스의 경합이 크게 줄어 듭니다.