¿TransactionScope escala automáticamente a MSDTC en algunas máquinas?

284

En nuestro proyecto, estamos utilizando TransactionScope para garantizar que nuestra capa de acceso a datos realice sus acciones en una transacción. Nuestro objetivo es no exigir que el servicio MSDTC esté habilitado en las máquinas de nuestros usuarios finales.

El problema es que, en la mitad de las máquinas de nuestros desarrolladores, podemos ejecutar MSDTC deshabilitado. La otra mitad debe tenerlo habilitado o recibirán el mensaje de error "MSDTC en [SERVIDOR] no está disponible" .

Realmente me tiene rascándome la cabeza y me hace pensar seriamente en volver a una solución similar a TransactionScope basada en el hogar basada en objetos de transacción ADO.NET. Aparentemente es una locura: el mismo código que funciona (y no aumenta) en la mitad de nuestros desarrolladores sí lo hace en los otros desarrolladores.

Esperaba una mejor respuesta a Trace por qué una transacción se escala a DTC, pero desafortunadamente no.

Aquí hay un fragmento de código de muestra que causará el problema, en las máquinas que intentan escalar, intenta escalar en la segunda conexión. Abrir () (y sí, no hay otra conexión abierta en ese 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();
}

Realmente investigamos y tratamos de resolver esto. Aquí hay información sobre las máquinas en las que funciona:

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

Desarrolladores en los que no funciona:

  • Dev 4: Windows 7 x64, SQL2008 SQL2005
  • Dev 5: Windows Vista x86, SQL2005
  • Dev 6: Windows XP X86, SQL2005
  • My Home PC: Windows Vista Home Premium, x86, SQL2005

Debo agregar que todas las máquinas, en un esfuerzo por detectar el problema, han sido completamente parcheadas con todo lo que está disponible en Microsoft Update.

Actualización 1:

Esa página de escalado de transacciones de MSDN establece que las siguientes condiciones harán que una transacción se escale a DTC:

  1. Al menos un recurso duradero que no admite notificaciones monofásicas se incluye en la transacción.
  2. Al menos dos recursos duraderos que admiten notificaciones monofásicas se inscriben en la transacción. Por ejemplo, alistar una sola conexión no hace que se promocione una transacción. Sin embargo, cada vez que abre una segunda conexión a una base de datos que hace que la base de datos se enliste, la infraestructura de System.Transactions detecta que es el segundo recurso duradero en la transacción y la escala a una transacción MSDTC.
  3. Se invoca una solicitud para "ordenar" la transacción a un dominio de aplicación diferente o un proceso diferente. Por ejemplo, la serialización del objeto de transacción a través de un límite de dominio de aplicación. El objeto de transacción se ordena por valor, lo que significa que cualquier intento de pasarlo a través de un límite de dominio de aplicación (incluso en el mismo proceso) da como resultado la serialización del objeto de transacción. Puede pasar los objetos de transacción haciendo una llamada en un método remoto que toma una transacción como parámetro o puede intentar acceder a un componente remoto de servicio transaccional. Esto serializa el objeto de transacción y da como resultado una escalada, como cuando una transacción se serializa en un dominio de aplicación. Se está distribuyendo y el administrador de transacciones local ya no es adecuado.

No estamos experimentando el # 3. # 2 no está sucediendo porque solo hay una conexión a la vez, y también es a un solo 'recurso duradero'. ¿Hay alguna manera de que # 1 pueda estar sucediendo? ¿Alguna configuración SQL2005 / 8 que hace que no admita notificaciones monofásicas?

Actualización 2:

Vuelva a investigar, personalmente, las versiones de SQL Server de todos: "Dev 3" en realidad tiene SQL2008, y "Dev 4" es en realidad SQL2005. Eso me enseñará a no confiar nunca más en mis compañeros de trabajo. ;) Debido a este cambio en los datos, estoy bastante seguro de que hemos encontrado nuestro problema. Nuestros desarrolladores de SQL2008 no estaban experimentando el problema porque SQL2008 tiene una gran cantidad de increíbles incluidos que SQL2005 no tiene.

También me dice que debido a que vamos a admitir SQL2005, no podemos usar TransactionScope como lo hemos hecho, y si queremos usar TransactionScope, necesitaremos pasar un solo objeto SqlConnection ... lo que parece problemático en situaciones en las que SqlConnection no se puede pasar fácilmente ... simplemente huele a instancia global-SqlConnection. ¡Banco de iglesia!

Actualización 3

Solo para aclarar aquí en la pregunta:

SQL2008:

  • Permite múltiples conexiones dentro de un solo TransactionScope (como se demuestra en el código de muestra anterior).
  • Advertencia # 1: Si esas múltiples conexiones SqlConnections están anidadas, es decir, dos o más conexiones SqlConnections se abren al mismo tiempo, TransactionScope se escalará inmediatamente a DTC.
  • Advertencia # 2: Si se abre una SqlConnection adicional a un 'recurso duradero' diferente (es decir: un SQL Server diferente), se escalará inmediatamente a DTC

SQL2005:

  • No permite conexiones múltiples dentro de un solo TransactionScope, punto. Se intensificará cuando / si se abre una segunda conexión SqlConnection.

Actualización 4

Con el fin de hacer esta pregunta aún más de un lío útil, y sólo por el bien de la claridad más, aquí es cómo se puede obtener SQL2005 a escalar a DTC con una sola SqlConnection :

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

Esto simplemente me parece roto, pero supongo que puedo entender si cada llamada a la SqlConnection.Open()toma del grupo de conexiones.

"¿Pero por qué podría pasar esto?" Bueno, si usa un SqlTableAdapter contra esa conexión antes de que se abra, el SqlTableAdapter abrirá y cerrará la conexión, terminando efectivamente la transacción porque ahora no puede volver a abrirla.

Entonces, básicamente, para usar TransactionScope con SQL2005 con éxito, necesita tener algún tipo de objeto de conexión global que permanezca abierto desde el punto en que se instancia la primera TransactionScope hasta que ya no sea necesario. Además del olor a código de un objeto de conexión global, abrir la conexión primero y cerrarla al final está en desacuerdo con la lógica de abrir una conexión lo más tarde posible y cerrarla lo antes posible.

Yoopergeek
fuente
¿Puede ampliar el "Haga otras cosas aquí que pueden o no involucrar en la transacción ambiental". ¿Seguramente lo que hay allí afecta en gran medida el comportamiento del código?
RichardOD
2
"# 2 no está sucediendo porque solo hay una conexión a la vez" - # 2 no dice que la segunda conexión debe estar abierta al mismo tiempo, solo que necesita ser incluida en la misma transacción.
Joe
3
Muchas gracias por informarnos con la Actualización 4 que muestra cómo puede ocurrir la escalada con solo una SqlConnection. Esto es exactamente a lo que me estaba enfrentando a pesar de asegurarme cuidadosamente de que solo se usa una SqlConnection. Es bueno saber que es la computadora la que está loca y no yo. :-)
Oran Dennison
En términos de agrupación de conexiones, si tenemos múltiples conexiones (y anidadas si es necesario) si estamos abriendo y cerrando una a la vez, estamos utilizando 1 recurso de agrupación de conexión real o 1 por conexión, estoy tratando de racionalizar esto para determinar si tener o no una conexión "enlistable" con el alcance apropiado (lo que me gustaría evitar)
brumScouse
1
Las conexiones anidadas bajo el mismo alcance de transacción promoverán a una transacción distribuida. Desde SQL Server 2008 y superior, las conexiones múltiples (no anidadas) bajo el mismo alcance de transacción no promoverán a una transacción distribuida.
PreguntonCojoneroCabrón

Respuestas:

71

SQL Server 2008 puede usar múltiples SQLConnections en uno TransactionScopesin escalar, siempre que las conexiones no estén abiertas al mismo tiempo, lo que daría como resultado múltiples conexiones TCP "físicas" y, por lo tanto, requerirá una escalada.

Veo que algunos de sus desarrolladores tienen SQL Server 2005 y otros tienen SQL Server 2008. ¿Está seguro de haber identificado correctamente cuáles están aumentando y cuáles no?

La explicación más obvia sería que los desarrolladores con SQL Server 2008 son los que no están escalando.

Joe
fuente
Sí, los detalles son correctos, y ¿alguien está mirando el código? Hay dos conexiones dentro del alcance de la transacción, sin embargo, solo hay una conexión instanciada y abierta en un solo momento en el tiempo. Además, no, DTC no se ejecuta en las máquinas que funcionan.
Yoopergeek el
1
"Sin embargo, solo hay una conexión instanciada y abierta en un solo momento", ¿por qué es relevante? Con SQL2005, si abre más de una conexión dentro del alcance de una transacción, aumentará si permanecen abiertas o no simultáneamente. Lo cual es lógico si lo piensas.
Joe
Usted y hwiechers ahora me hacen dudar y estoy ansioso por ponerme a trabajar el lunes e inspeccionar sus máquinas individuales más de cerca y asegurarme de que las versiones de SQL Server sean como se informó anteriormente.
Yoopergeek el
19
Usted y los hwiechers tienen razón. Tengo huevo por toda la cara. Gracias por golpearme con el palo de la pista. :) Porque fuiste el primero, obtienes la respuesta. Sin embargo, me gustaría agregar un punto de aclaración: SQL2008 permite abrir múltiples conexiones, pero no al mismo tiempo. Todavía puede haber una sola conexión abierta en un momento dado o TransactionScope se escalará a DTC.
Yoopergeek
@Yoopergeek Pude verificar que tu "no al mismo tiempo" es importante y edité la respuesta de @Joe en consecuencia. El monitoreo de las conexiones TCP durante las pruebas demostró que la conexión TCP anterior se reutilizará cuando las conexiones no se usen al mismo tiempo, y por lo tanto, se TransactionScopepuede hacer con una sola COMMITen el lado del servidor, lo que haría innecesaria la escalada.
Eugene Beresovsky
58

El resultado de mi investigación sobre el tema:

ingrese la descripción de la imagen aquí

Consulte Evitar la escalada no deseada a transacciones distribuidas

Todavía estoy investigando el comportamiento de escalamiento de Oracle: ¿Las transacciones que abarcan múltiples conexiones a la misma base de datos se escalan a DTC?

Peter Meinl
fuente
1
Gracias por compartir tu investigación. Realmente ayudó. Una consulta rápida más. ¿Cuál es la diferencia entre TransactionScope () y sqlConnection.BeginTransaction ()?
Baig
De acuerdo con esta solicitud de función , ODAC 12C ahora debería comportarse como SQL 2008, no promocionando la distribución cuando se usan conexiones consecutivas al mismo origen de datos.
Frédéric
31

Ese código va a provocar una escalada cuando se conecta a 2005.

Consulte la documentación en MSDN: http://msdn.microsoft.com/en-us/library/ms172070.aspx

Transacciones promocionables en SQL Server 2008

En la versión 2.0 de .NET Framework y SQL Server 2005, abrir una segunda conexión dentro de un TransactionScope promovería automáticamente la transacción a una transacción distribuida completa, incluso si ambas conexiones usaban cadenas de conexión idénticas. En este caso, una transacción distribuida agrega una sobrecarga innecesaria que disminuye el rendimiento.

A partir de SQL Server 2008 y la versión 3.5 de .NET Framework, las transacciones locales ya no se promocionan a transacciones distribuidas si se abre otra conexión en la transacción después de cerrar la transacción anterior. Esto no requiere cambios en su código si ya está utilizando la agrupación de conexiones y alistándose en las transacciones.

No puedo explicar por qué Dev 3: Windows 7 x64, SQL2005 tiene éxito y Dev 4: Windows 7 x64 falla. ¿Estás seguro de que no es al revés?

hwiechers
fuente
10

No sé por qué se eliminó esta respuesta, pero parece tener información relevante.

respondió el 4 de agosto de 10 a las 17:42 Eduardo

  1. Establezca Enlist = false en la cadena de conexión para evitar el alistamiento automático en la transacción.

  2. Alistar manualmente la conexión como participantes en el alcance de la transacción. [ artículo original desactualizado] o haga esto: Cómo evitar la promoción automática de MSDTC [archive.is]

Chris Marisic
fuente
msdn.microsoft.com/en-us/library/ms172153%28v=VS.80%29.aspx no encontrado, Visual Studio 2005 Documentación retirada
Kiquenet
2

No estoy muy seguro si la conexión anidada es el problema. Estoy llamando a una instancia local de servidor SQL y no genera el 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
fuente
¿Qué edición de SQL Server estás usando? Me pregunto si la respuesta de @Peter Meinl debe actualizarse para reflejar los cambios realizados en 2008R2 y / o Denali.
Yoopergeek
Estoy usando SQL Server 2008 R2.
Iftikhar Ali
Me pregunto si 2008 R2 se comporta mejor. La respuesta de @hwiechers también me hace preguntarme si la versión del Framework con la que está compilando está evitando la escalada. Finalmente, me pregunto si ser una instancia local de R2 hace alguna diferencia. Desearía tener el tiempo / recursos para investigar cómo ha cambiado esto con el lanzamiento de 2008 R2 y SQL Server 2012.
Yoopergeek
¿No está seguro si la conexión anidada es el problema? jajaja ... bueno florece quitadlo entonces !, por qué demonios la gente anida usando declaraciones cuando no es absolutamente necesario, nunca lo sabré.
Paul Zahra
1

TransactionScope siempre se escala a una transacción DTC, si usa el acceso a más de 1 conexión dentro. La única forma en que el código anterior puede funcionar con DTC deshabilitado es si, por una gran posibilidad, obtiene la misma conexión del grupo de conexiones en ambas ocasiones.

"El problema es que, en la mitad de las máquinas de nuestros desarrolladores, podemos ejecutar con MSDTC deshabilitado". ¿Estás seguro de que está desactivado;)

aficionado
fuente
0

Asegúrese de que su connectionString no establezca la agrupación en falso. Esto dará como resultado una nueva conexión para cada nueva SqlConnection en TransactionScope y la escalará a DTC.

FreddyV
fuente