O TransactionScope está escalando automaticamente para o MSDTC em algumas máquinas?

284

Em nosso projeto, estamos usando o TransactionScope para garantir que nossa camada de acesso a dados execute suas ações em uma transação. Nosso objetivo é não exigir que o serviço MSDTC seja ativado nas máquinas de nossos usuários finais.

O problema é que, na metade das máquinas de nossos desenvolvedores, podemos executar com o MSDTC desativado. A outra metade deve estar ativada ou a mensagem de erro "MSDTC no [SERVIDOR] está indisponível" .

Isso realmente me fez coçar a cabeça e me levou a pensar seriamente em voltar para uma solução semelhante ao TransactionScope, baseada em objetos de transação do ADO.NET. É aparentemente insano - o mesmo código que funciona (e não escalar) na metade do nosso desenvolvedor é faz escalar do outro desenvolvedor do.

Eu estava esperando uma resposta melhor para o Trace porque uma transação é escalada para o DTC, mas infelizmente não.

Aqui está um exemplo de código que causará o problema: nas máquinas que tentam escalar, ele tenta escalar na segunda conexão.Open () (e sim, não há outra conexão aberta no momento).

using (TransactionScope transactionScope = new TransactionScope() {
   using (SqlConnection connection = new SqlConnection(_ConStr)) {
      using (SqlCommand command = connection.CreateCommand()) {
         // prep the command
         connection.Open();
         using (SqlDataReader reader = command.ExecuteReader()) {
            // use the reader
            connection.Close();
         }
      }
   }

   // Do other stuff here that may or may not involve enlisting 
   // in the ambient transaction

   using (SqlConnection connection = new SqlConnection(_ConStr)) {
      using (SqlCommand command = connection.CreateCommand()) {
         // prep the command
         connection.Open();  // Throws "MSDTC on [SERVER] is unavailable" on some...

         // gets here on only half of the developer machines.
      }
      connection.Close();
   }

   transactionScope.Complete();
}

Nós realmente descobrimos e tentamos descobrir isso. Aqui estão algumas informações sobre as máquinas nas quais ele funciona:

  • Desenvolvedor 1: Windows 7 x64 SQL2008
  • Desenvolvedor 2: Windows 7 x86 SQL2008
  • Dev 3: Windows 7 x64 SQL2005 SQL2008

Desenvolvedores nos quais não trabalha:

  • Dev 4: 7 x64 do Windows, SQL2008 SQL2005
  • Desenvolvedor 5: Windows Vista x86, SQL2005
  • Desenvolvedor 6: Windows XP X86, SQL2005
  • Meu PC doméstico: Windows Vista Home Premium, x86, SQL2005

Devo acrescentar que todas as máquinas, em um esforço para caçar o problema, foram totalmente corrigidas com tudo o que está disponível no Microsoft Update.

Atualização 1:

Essa página de escalonamento de transações do MSDN indica que as seguintes condições farão com que uma transação seja escalada para o DTC:

  1. Pelo menos um recurso durável que não suporta notificações monofásicas é alistado na transação.
  2. Pelo menos dois recursos duráveis ​​que suportam notificações monofásicas são alistados na transação. Por exemplo, inscrever uma única conexão com não faz com que uma transação seja promovida. No entanto, sempre que você abre uma segunda conexão com um banco de dados, o infra-estrutura System.Transactions detecta que ele é o segundo recurso durável da transação e o encaminha para uma transação MSDTC.
  3. Uma solicitação para "empacotar" a transação para um domínio de aplicativo ou processo diferente é invocada. Por exemplo, a serialização do objeto de transação através de um limite do domínio do aplicativo. O objeto de transação é empacotado por valor, o que significa que qualquer tentativa de passar por um limite de domínio de aplicativo (mesmo no mesmo processo) resulta em serialização do objeto de transação. Você pode passar os objetos de transação fazendo uma chamada em um método remoto que usa uma transação como parâmetro ou pode tentar acessar um componente de serviço transacional remoto. Isso serializa o objeto de transação e resulta em uma escalação, como quando uma transação é serializada em um domínio de aplicativo. Ele está sendo distribuído e o gerenciador de transações local não é mais adequado.

Não estamos enfrentando o # 3. O número 2 não está acontecendo porque existe apenas uma conexão por vez e também para um único 'recurso durável'. Existe alguma maneira de # 1 estar acontecendo? Alguma configuração do SQL2005 / 8 que faz com que ele não ofereça suporte a notificações monofásicas?

Atualização 2:

Investigamos pessoalmente, pessoalmente, as versões de todos os servidores do SQL Server - "Dev 3" na verdade tem o SQL2008 e "Dev 4" na verdade é o SQL2005. Isso me ensinará a nunca mais confiar em meus colegas de trabalho. ;) Devido a essa alteração nos dados, tenho certeza que encontramos o nosso problema. Nossos desenvolvedores do SQL2008 não estavam enfrentando o problema, porque o SQL2008 possui muitas cópias incríveis incluídas, que o SQL2005 não possui.

Ele também me diz que, como daremos suporte ao SQL2005, não podemos usar o TransactionScope como antes e, se quisermos usar o TransactionScope, precisaremos passar um único objeto SqlConnection por aí ... o que parece problemático em situações em que o SqlConnection não pode ser facilmente contornado ... apenas cheira a instância global-SqlConnection. Pew!

Atualização 3

Apenas para esclarecer aqui na pergunta:

SQL2008:

  • Permite várias conexões em um único TransactionScope (como demonstrado no código de exemplo acima).
  • Advertência nº 1: se essas várias SqlConnections estiverem aninhadas, ou seja, duas ou mais SqlConnections forem abertas ao mesmo tempo, o TransactionScope será escalado imediatamente para o DTC.
  • Advertência # 2: Se um SqlConnection adicional for aberto para um 'recurso durável' diferente (ou seja: um SQL Server diferente), ele será escalado imediatamente para o DTC

SQL2005:

  • Não permite várias conexões em um único TransactionScope, ponto final. Ele será escalado quando / se um segundo SqlConnection for aberto.

Atualização 4

No interesse de tornar essa questão ainda mais uma bagunça útil, e apenas por uma questão de clareza, veja como você pode fazer o SQL2005 escalar para o DTC com um único SqlConnection :

using (TransactionScope transactionScope = new TransactionScope()) {
   using (SqlConnection connection = new SqlConnection(connectionString)) {
      connection.Open();
      connection.Close();
      connection.Open(); // escalates to DTC
   }
}

Isso só me parece ruim, mas acho que consigo entender se todas as chamadas SqlConnection.Open()são recebidas do pool de conexões.

"Por que isso pode acontecer?" Bem, se você usar um SqlTableAdapter nessa conexão antes de ser aberto, o SqlTableAdapter abrirá e fechará a conexão, finalizando efetivamente a transação para você, porque agora você não pode reabri-la.

Portanto, basicamente, para usar com êxito o TransactionScope com o SQL2005, é necessário ter algum tipo de objeto de conexão global que permaneça aberto desde o primeiro momento em que o TransactionScope é instanciado até que não seja mais necessário. Além do cheiro de código de um objeto de conexão global, abrir a conexão primeiro e fechá-la por último está em desacordo com a lógica de abrir uma conexão o mais tarde possível e fechá-la o mais rápido possível.

Yoopergeek
fonte
Você pode expandir a seção "Faça outras coisas aqui que possam ou não envolver na transação ambiental". Certamente o que está lá afeta muito o comportamento do código?
RichardOD
2
"O nº 2 não está acontecendo porque existe apenas uma conexão por vez" - o nº 2 não diz que a segunda conexão precisa ser aberta ao mesmo tempo, apenas que precisa ser alistada na mesma transação.
7119 Joe
3
Muito obrigado por relatar a atualização 4, mostrando como a escalação pode ocorrer com apenas um único SqlConnection. Isso é exatamente o que eu estava encontrando, apesar de garantir cuidadosamente que apenas um único SqlConnection seja usado. É bom saber que é o computador que é louco e não eu. :-)
Oran Dennison
Em termos de pool de conexões, se tivermos várias conexões (e aninhadas, se necessário), se estivermos abrindo e fechando uma de cada vez, estamos utilizando 1 recurso de pool de conexão real ou 1 por conexão, estou tentando racionalizar isso para determinar se deve ou não ter adequadamente escopo conneection "enlistable" (que eu gostaria de evitar)
brumScouse
1
Conexões aninhadas no mesmo escopo de transação serão promovidas para uma transação distribuída. No SQL Server 2008 e acima, várias conexões (sem aninhamento) no mesmo escopo de transação não serão promovidas para uma transação distribuída.
PreguntonCojoneroCabrón

Respostas:

71

O SQL Server 2008 pode usar vários SQLConnections em um TransactionScopesem escalar, desde que as conexões não estejam abertas ao mesmo tempo, o que resultaria em várias conexões TCP "físicas" e, portanto, requer escalação.

Vejo alguns de seus desenvolvedores com o SQL Server 2005 e outros com o SQL Server 2008. Tem certeza de que identificou corretamente quais estão sendo escalados e quais não?

A explicação mais óbvia seria que os desenvolvedores do SQL Server 2008 são os que não estão escalando.

Joe
fonte
Sim, os detalhes estão corretos e alguém está realmente vendo o código? Existem duas conexões no escopo da transação; no entanto, existe apenas uma conexão e aberta em um único momento no tempo. Além disso, não, o DTC não está sendo executado nas máquinas que estão funcionando.
Yoopergeek 7/11/2009
1
"no entanto, existe apenas uma conexão instanciada e aberta em um único momento" - por que isso é relevante? Com o SQL2005, se você abrir mais de uma conexão no escopo de uma transação, escalará se elas permanecerão abertas ou não simultaneamente. O que é lógico se você pensar sobre isso.
728 Joe
Agora, você e os engenheiros têm uma opinião duvidosa e estou ansioso para começar o trabalho na segunda-feira e inspecionar suas máquinas individuais mais de perto e garantir que as versões do SQL Server sejam as relatadas anteriormente.
Yoopergeek 7/11/2009
19
Você e os hwiechers estão certos. Eu tenho ovo em todo o meu rosto. Obrigado por me acertar com a dica. :) Como você foi o primeiro, você obtém a resposta. Gostaria de acrescentar um ponto de esclarecimento - o SQL2008 permite que várias conexões sejam abertas, mas não ao mesmo tempo. Ainda pode haver apenas uma única conexão aberta a qualquer momento ou o TransactionScope será escalado para o DTC.
Yoopergeek 9/11/2009
@Yoopergeek Pude verificar se o seu "não ao mesmo tempo" é importante e editei a resposta do @Joe de acordo. O monitoramento das conexões TCP durante o teste mostrou que a conexão TCP antiga será reutilizada quando as conexões não são usadas ao mesmo tempo e, portanto, TransactionScopepode se contentar com uma única COMMITno lado do servidor, o que tornaria a escalação supérflua.
Eugene Beresovsky
58

O resultado da minha pesquisa sobre o tema:

insira a descrição da imagem aqui

Consulte Evitar encaminhamento indesejado para transações distribuídas

Ainda estou investigando o comportamento de escalação do Oracle: as transações que abrangem várias conexões com o mesmo banco de dados são escaladas para o DTC?

Peter Meinl
fonte
1
Obrigado por compartilhar sua pesquisa. Isso realmente ajudou. Mais uma consulta rápida. Qual é a diferença entre TransactionScope () e sqlConnection.BeginTransaction ()?
Baig
De acordo com essa solicitação de recurso , o ODAC 12C agora deve se comportar como o SQL 2008, não promovendo a distribuição ao usar conexões consecutivas à mesma fonte de dados.
Frédéric
31

Esse código irá causar uma escalada ao conectar a 2005.

Verifique a documentação no MSDN - http://msdn.microsoft.com/en-us/library/ms172070.aspx

Transações promovidas no SQL Server 2008

Na versão 2.0 do .NET Framework e SQL Server 2005, a abertura de uma segunda conexão dentro de um TransactionScope promoveria automaticamente a transação para uma transação distribuída completa, mesmo se as duas conexões estivessem usando cadeias de conexão idênticas. Nesse caso, uma transação distribuída adiciona uma sobrecarga desnecessária que diminui o desempenho.

A partir do SQL Server 2008 e da versão 3.5 do .NET Framework, as transações locais não são mais promovidas para transações distribuídas se outra conexão for aberta na transação após o fechamento da transação anterior. Isso não exige alterações no seu código se você já estiver usando o pool de conexões e o alistamento em transações.

Não sei explicar por que o Dev 3: Windows 7 x64, SQL2005 é bem-sucedido e o Dev 4: Windows 7 x64 falha. Tem certeza de que não é o contrário?

hwiechers
fonte
10

Não sei por que essa resposta foi excluída, mas isso parece ter algumas informações relevantes.

respondeu Aug 4 '10 às 17:42 Eduardo

  1. Defina Enlist = false na cadeia de conexão para evitar o alistamento automático na transação.

  2. Registre manualmente a conexão como participantes no escopo da transação. [ artigo original desatualizado] ou faça o seguinte: Como impedir a promoção automática do MSDTC [archive.is]

Chris Marisic
fonte
msdn.microsoft.com/pt-br/library/ms172153%28v=VS.80%29.aspx não encontrado, Visual Studio 2005 Aposentado documentação
Kiquenet
2

Não tenho certeza se a conexão aninhada é o problema. Estou chamando uma instância local do servidor SQL e ele não gera o DTC?

    public void DoWork2()
    {
        using (TransactionScope ts2 = new TransactionScope())
        {
            using (SqlConnection conn1 = new SqlConnection("Data Source=Iftikhar-PC;Initial Catalog=LogDB;Integrated Security=SSPI;"))
            {
                SqlCommand cmd = new SqlCommand("Insert into Log values(newid(),'" + "Dowork2()" + "','Info',getDate())");
                cmd.Connection = conn1;
                cmd.Connection.Open();
                cmd.ExecuteNonQuery();

                using (SqlConnection conn2 = new SqlConnection("Data Source=Iftikhar-PC;Initial Catalog=LogDB;Integrated Security=SSPI;Connection Timeout=100"))
                {
                    cmd = new SqlCommand("Insert into Log values(newid(),'" + "Dowork2()" + "','Info',getDate())");
                    cmd.Connection = conn2;
                    cmd.Connection.Open();
                    cmd.ExecuteNonQuery();
                }
            }

            ts2.Complete();
        }
    }
Iftikhar Ali
fonte
Qual edição do SQL Server você está usando? Gostaria de saber se a resposta de @Peter Meinl precisa ser atualizada para refletir as alterações feitas em 2008R2 e / ou Denali.
Yoopergeek 17/05/12
Estou usando o SQL Server 2008 R2.
Iftikhar Ali
Gostaria de saber se 2008 R2 é melhor comportado? A resposta da @hwiechers também me faz pensar se a versão do Framework contra a qual você está compilando está impedindo a escalação. Por fim, gostaria de saber se é uma instância local do R2 faz alguma diferença. Eu gostaria de ter o tempo / recursos para investigar como isso mudou com o lançamento do 2008 R2 e SQL Server 2012.
Yoopergeek
Não tem certeza se a conexão aninhada é o problema? lol ... bem florescendo removê-lo então !, por que diabos as pessoas fazem ninhos usando declarações quando não são absolutamente necessárias, eu nunca vou saber.
Paul Zahra
1

O TransactionScope sempre encaminha para a transação DTC, se você usar acessar mais de uma conexão interna. A única maneira de o código acima funcionar com o DTC desabilitado é se, por uma grande chance, você obtiver a mesma conexão do conjunto de conexões duas vezes.

"O problema é que, na metade das máquinas de nossos desenvolvedores, podemos executar com o MSDTC desativado." Tem certeza de que está desativado;)

amador
fonte
0

Verifique se o connectionString não está configurando pooling como false. Isso resultará em uma nova conexão para cada novo SqlConnection no TransactionScope e a escalará para o DTC.

FreddyV
fonte