[C#] 어떤 상황에서 SqlConnection이 주변 TransactionScope 트랜잭션에 자동으로 참여합니까?

SqlConnection이 트랜잭션에 “등록”되었다는 것은 무엇을 의미합니까? 연결에서 내가 실행하는 명령이 트랜잭션에 참여한다는 의미입니까?

그렇다면 어떤 상황에서 SqlConnection 이 주변 TransactionScope 트랜잭션에 자동으로 참여합니까?

코드 주석에서 질문을보십시오. 각 질문에 대한 나의 추측은 각 질문에 괄호 안에 있습니다.

시나리오 1 : 트랜잭션 범위 내에서 연결 열기

using (TransactionScope scope = new TransactionScope())
using (SqlConnection conn = ConnectToDB())
{   
    // Q1: Is connection automatically enlisted in transaction? (Yes?)
    //
    // Q2: If I open (and run commands on) a second connection now,
    // with an identical connection string,
    // what, if any, is the relationship of this second connection to the first?
    //
    // Q3: Will this second connection's automatic enlistment
    // in the current transaction scope cause the transaction to be
    // escalated to a distributed transaction? (Yes?)
}

시나리오 2 : 외부에서 열린 트랜잭션 범위 내에서 연결 사용

//Assume no ambient transaction active now
SqlConnection new_or_existing_connection = ConnectToDB(); //or passed in as method parameter
using (TransactionScope scope = new TransactionScope())
{
    // Connection was opened before transaction scope was created
    // Q4: If I start executing commands on the connection now,
    // will it automatically become enlisted in the current transaction scope? (No?)
    //
    // Q5: If not enlisted, will commands I execute on the connection now
    // participate in the ambient transaction? (No?)
    //
    // Q6: If commands on this connection are
    // not participating in the current transaction, will they be committed
    // even if rollback the current transaction scope? (Yes?)
    //
    // If my thoughts are correct, all of the above is disturbing,
    // because it would look like I'm executing commands
    // in a transaction scope, when in fact I'm not at all, 
    // until I do the following...
    //
    // Now enlisting existing connection in current transaction
    conn.EnlistTransaction( Transaction.Current );
    //
    // Q7: Does the above method explicitly enlist the pre-existing connection
    // in the current ambient transaction, so that commands I
    // execute on the connection now participate in the
    // ambient transaction? (Yes?)
    //
    // Q8: If the existing connection was already enlisted in a transaction
    // when I called the above method, what would happen?  Might an error be thrown? (Probably?)
    //
    // Q9: If the existing connection was already enlisted in a transaction
    // and I did NOT call the above method to enlist it, would any commands
    // I execute on it participate in it's existing transaction rather than
    // the current transaction scope. (Yes?)
}



답변

나는이 질문을 한 이후에 몇 가지 테스트를 해왔으며 아무도 대답하지 않았기 때문에 모든 답변이 내 자신의 것이 아니라면 대부분을 찾았습니다. 내가 놓친 부분이 있으면 알려주십시오.

Q1. 연결 문자열에 “enlist = false”가 지정되어 있지 않으면 예입니다. 연결 풀이 사용 가능한 연결을 찾습니다. 사용 가능한 연결은 트랜잭션에 참여하지 않거나 동일한 트랜잭션에 참여하는 연결입니다.

Q2. 두 번째 연결은 동일한 연결에 참여하는 독립적 인 연결입니다. 그들은 같은 데이터베이스에 대해 실행하고 있기 때문에 나는이 두 연결에 대한 명령의 상호 작용에 대해 잘 모르겠지만, 나는 명령이 두 가지를 동시에 발행하는 경우 오류가 발생할 수 있다고 생각 : 같은 오류 사용 “트랜잭션 컨텍스트에 의해를 다른 세션 “

Q3. 예, 분산 트랜잭션으로 에스컬레이션되므로 동일한 연결 문자열을 사용하더라도 둘 이상의 연결을 등록하면 분산 트랜잭션이되므로 Transaction.Current.TransactionInformation에서 널이 아닌 GUID를 확인하여 확인할 수 있습니다. .DistributedIdentifier. * 업데이트 : SQL Server 2008에서 수정 된 부분을 읽었으므로 두 연결에 동일한 연결 문자열을 사용할 때 MSDTC가 사용되지 않습니다 (두 연결이 동시에 열리지 않는 한). 이를 통해 트랜잭션 내에서 연결을 열고 여러 번 닫을 수 있으므로 연결을 최대한 늦게 열고 가능한 빨리 닫아 연결 풀을 더 잘 활용할 수 있습니다.

Q4. 아니요. 거래 범위가 활성화되지 않았을 때 열린 연결은 새로 작성된 거래 범위에 자동으로 참여하지 않습니다.

Q5. 아니요. 트랜잭션 범위에서 연결을 열거 나 범위에 기존 연결을 등록하지 않으면 기본적으로 트랜잭션이 없습니다. 명령이 트랜잭션에 참여하려면 트랜잭션 범위에 연결이 자동 또는 수동으로 참여해야합니다.

Q6. 예, 롤백 된 트랜잭션 범위 블록에서 코드가 실행 되더라도 트랜잭션에 참여하지 않는 연결의 명령은 발행 된 것으로 커밋됩니다. 연결이 현재 트랜잭션 범위에 입대하지 않을 경우,이 정도로 … 커밋 또는 트랜잭션 범위에 입대하지 연결에서 실행 한 명령에 영향을주지 않습니다 트랜잭션을 롤백 트랜잭션에 참여하지 않는 것 이 사람이 발견 . 자동 참여 프로세스를 이해하지 않으면 찾아 내기가 매우 어렵습니다 . 활성 트랜잭션 범위 에서 연결이 열린 경우에만 발생합니다 .

Q7. 예. EnlistTransaction (Transaction.Current)를 호출하여 현재 연결 범위에 기존 연결을 명시 적으로 참여시킬 수 있습니다. DependentTransaction을 사용하여 트랜잭션에서 별도의 스레드에 연결을 등록 할 수도 있지만 이전과 마찬가지로 동일한 데이터베이스에 대해 동일한 트랜잭션에 관련된 두 개의 연결이 어떻게 상호 작용하고 오류가 발생할 수 있는지 잘 모르겠습니다. 물론 두 번째 참여 연결로 인해 트랜잭션이 분산 트랜잭션으로 에스컬레이션됩니다.

Q8. 오류가 발생할 수 있습니다. TransactionScopeOption.Required가 사용되고 연결이 트랜잭션 범위 트랜잭션에 이미 참여한 경우 오류가 없습니다. 실제로 범위에 대해 작성된 새 트랜잭션이 없으며 트랜잭션 수 (@@ trancount)가 증가하지 않습니다. 그러나 TransactionScopeOption.RequiresNew를 사용하는 경우 새 트랜잭션 범위 트랜잭션에 연결을 등록하려고하면 “현재 연결에 트랜잭션이 등록되었습니다. 현재 트랜잭션을 완료하고 다시 시도하십시오.”라는 오류 메시지가 나타납니다. 예, 연결을 완료 한 트랜잭션을 완료하면 새 트랜잭션에 연결을 안전하게 참여시킬 수 있습니다.
업데이트 : 이전에 연결에서 BeginTransaction을 호출 한 경우 새 트랜잭션 범위 트랜잭션에 참여하려고 할 때 약간 다른 오류가 발생합니다. “연결에서 로컬 트랜잭션이 진행 중이므로 트랜잭션에 참여할 수 없습니다. 로컬 트랜잭션을 완료하고 다시 해 보다.” 반면에 트랜잭션 범위 트랜잭션에 참여하는 동안 SqlConnection에서 BeginTransaction을 안전하게 호출 할 수 있으며 중첩 된 트랜잭션 범위의 필수 옵션을 사용하는 것과 달리 @@ trancount가 1 씩 증가합니다. 증가하다. 흥미롭게도 필수 옵션을 사용하여 다른 중첩 트랜잭션 범위를 만들면 오류가 발생하지 않습니다.

Q9. 예. C # 코드의 활성 트랜잭션 범위에 관계없이 명령은 연결에 참여한 모든 트랜잭션에 참여합니다.


답변

좋은 일 Triynko, 귀하의 답변은 모두 정확하고 완벽하게 보입니다. 내가 지적하고 싶은 다른 것들 :

(1) 수동 입대

위의 코드에서 다음과 같이 수동 참여를 올바르게 표시합니다.

using (SqlConnection conn = new SqlConnection(connStr))
{
    conn.Open();
    using (TransactionScope ts = new TransactionScope())
    {
        conn.EnlistTransaction(Transaction.Current);
    }
}

그러나 연결 문자열에서 Enlist = false를 사용하여 이와 같이 수행 할 수도 있습니다.

string connStr = "...; Enlist = false";
using (TransactionScope ts = new TransactionScope())
{
    using (SqlConnection conn1 = new SqlConnection(connStr))
    {
        conn1.Open();
        conn1.EnlistTransaction(Transaction.Current);
    }

    using (SqlConnection conn2 = new SqlConnection(connStr))
    {
        conn2.Open();
        conn2.EnlistTransaction(Transaction.Current);
    }
}

여기에 주목해야 할 또 다른 것이 있습니다. conn2가 열릴 때 연결 풀 코드는 나중에 conn1과 동일한 트랜잭션에 참여시키려는 것을 알지 못합니다. 즉 conn2에 conn1과 다른 내부 연결이 제공됩니다. 그런 다음 conn2가 등록되면 이제 2 개의 연결이 등록되므로 트랜잭션을 MSDTC로 승격해야합니다. 이 프로모션은 자동 참여를 통해서만 피할 수 있습니다.

(2) .Net 4.0 이전 에는 연결 문자열에서 “Transaction Binding = Explicit Unbind”를 설정하는 것이 좋습니다 . 이 문제는 .Net 4.0에서 수정되어 명시 적 언 바인드를 완전히 불필요하게 만듭니다.

(3) 자신의 롤링 CommittableTransaction과 그 설정 Transaction.Current은 본질적으로하는 것과 같습니다 TransactionScope. FYI만으로는 거의 유용하지 않습니다.

(4) Transaction.Current 는 스레드 정적입니다. Transaction.Current이것은를 생성 한 스레드에서만 설정 됨을 의미 합니다 TransactionScope. 동일한 실행하기 위해 다중 스레드 TransactionScope(아마도이 사용 Task) 가능하지 않다.


답변

우리가 보았던 또 다른 기괴한 상황은 당신이 EntityConnectionStringBuilder그것을 만들면 TransactionScope.Current거래에 참여하고 (우리가 생각하는) 거래에 참여한다는 것입니다. 우리는 디버거,이 관찰 한 TransactionScope.Current‘s에 current.TransactionInformation.internalTransaction쇼를 enlistmentCount == 1구성하고, 전 enlistmentCount == 2이후.

이것을 피하려면 내부에 구성하십시오

using (new TransactionScope(TransactionScopeOption.Suppress))

그리고 아마도 당신의 작업 범위 밖에서 가능합니다 (우리는 연결이 필요할 때마다 구성했습니다).


답변