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


201

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?)
}

답변:


188

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

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 # 코드의 활성 트랜잭션 범위에 관계없이 명령은 연결에 참여한 모든 트랜잭션에 참여합니다.


11
Q8에 대한 답변을 작성한 후, 나는이 재료가 Magic : The Gathering의 규칙처럼 복잡해지기 시작한다는 것을 알고 있습니다. TransactionScope 설명서에는 이에 대한 설명이 없으므로이 방법을 제외하고는 더 나쁩니다.
Triynko

Q3의 경우 동일한 연결 문자열을 사용하여 두 개의 연결을 동시에 열고 있습니까? 그렇다면 분산 트랜잭션이 될 것입니다 (SQL Server 2008에서도)
Randy는 Monica

2
아니요. 명확히하기 위해 게시물을 수정했습니다. 두 개의 연결을 동시에 열면 SQL Server 버전에 관계없이 항상 분산 트랜잭션이 발생한다는 것을 이해합니다. SQL 2008 이전에는 동일한 연결 문자열로 한 번에 하나의 연결 만 열면 여전히 DT가 발생하지만 SQL 2008에서는 동일한 연결 문자열로 한 번에 하나의 연결을 열면 (한 번에 두 번 열지 않음) DT
트리 인 코

1
Q2에 대한 답을 명확히하기 위해 두 명령이 동일한 스레드에서 순차적으로 수행되면 제대로 실행되어야합니다.
Jared Moore

2
SQL 2008의 동일한 연결 문자열에 대한 Q3 승격 문제에 대한 MSDN 인용은 다음과 같습니다. msdn.microsoft.com/en-us/library/ms172070(v=vs.90).aspx
pseudocoder

19

좋은 일 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) 가능하지 않다.


방금이 시나리오를 테스트했으며 설명대로 작동하는 것 같습니다. 또한 자동 참여를 사용하더라도 두 번째 연결을 열기 전에 "SqlConnection.ClearAllPools ()"를 호출하면 분산 트랜잭션으로 에스컬레이션됩니다.
Triynko

이것이 사실이라면, 트랜잭션과 관련된 단일 "실제"연결 만있을 수 있습니다. 분산 트랜잭션 (transaction)로 확대하지 않고 TransactionScope에의 트랜잭션에 대한 연결을 열고, 닫고, 다시 할 수있는 능력은 정말 다음 연결 풀에 의해 만들어진 환상 일반적으로 열려있는 배치 연결을 떠날 것이다, 그리고 반환이 다시 경우 똑같은 연결 자동 입회를 위해 개설.
Triynko

당신이 실제로 말하는 것은 자동 참여 프로세스를 회피하면 올바른 연결을 잡아내는 연결 풀 대신 트랜잭션 범위 트랜잭션 (TST) 내에서 새로운 연결을 다시 열 때 (원래 연결) TST에 등록 된 경우 완전히 새 연결을 적절하게 가져와 수동으로 참여할 경우 TST가 에스컬레이션됩니다.
Triynko

어쨌든, 그것은 연결 문자열에 "Enlist = false"가 지정되어 있지 않으면 풀이 적합한 연결을 찾는 방법에 대해 이야기 한 경우 Q1에 대한 대답에서 암시 한 것입니다.
Triynko

멀티 스레딩이 진행되는 한 Q2에 대한 답변에서 링크를 방문하면 Transaction.Current가 각 스레드마다 고유하지만 한 스레드에서 쉽게 참조를 획득하여 다른 스레드로 전달할 수 있습니다. 그러나 두 개의 다른 스레드에서 TST에 액세스하면 "다른 세션에서 사용중인 트랜잭션 컨텍스트"라는 매우 특정한 오류가 발생합니다. TST를 멀티 스레딩하려면 DependantTransaction을 작성해야하지만, 동시에 동시 명령을 실행하려면 두 번째 독립 연결이 필요하고 두 개를 조정하려면 MSDTC가 필요하므로 분산 트랜잭션이어야합니다.
Triynko

1

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

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

using (new TransactionScope(TransactionScopeOption.Suppress))

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.