c# - ¿TransactionScope escala automáticamente a MSDTC en algunas máquinas?
.net (6)
El resultado de mi investigación sobre el tema:
Consulte Evitar la escalada no deseada a transacciones distribuidas.
Todavía estoy investigando el comportamiento de escalado de Oracle: ¿Las transacciones que abarcan varias conexiones a la misma base de datos se escalan a DTC?
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 requerir 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 con MSDTC desactivado. La otra mitad debe tenerlo habilitado o recibirán el mensaje de error "MSDTC en [SERVIDOR] no está disponible" .
Realmente me hizo rascarme la cabeza y me ha planteado seriamente volver a una solución similar a TransactionScope 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 nuestro desarrollador aumenta en el otro desarrollador.
Esperaba una mejor respuesta a Trace por qué una transacción se escala a DTC, pero desafortunadamente no es así.
Aquí hay una muestra de código 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 hemos cavado y tratado de resolver esto. Aquí hay algo de información sobre las máquinas en las que funciona:
- Desarrollo 1: Windows 7 x64 SQL2008
- Dev 2: Windows 7 x86 SQL2008
- Desarrollo 3: Windows 7 x64
SQL2005SQL2008
Desarrolladores no funciona en:
- Dev 4: Windows 7 x64,
SQL2008SQL2005 - Dev 5: Windows Vista x86, SQL2005
- Dev 6: Windows XP X86, SQL2005
- Mi PC de casa: Windows Vista Home Premium, x86, SQL2005
Debo agregar que todas las máquinas, en un esfuerzo por resolver el problema, han sido completamente parchadas con todo lo que está disponible en Microsoft Update.
Actualización 1:
- http://social.msdn.microsoft.com/forums/en-US/windowstransactionsprogramming/thread/a5462509-8d6d-4828-aefa-a197456081d3/ describe un problema similar ... ¡en 2006!
- http://msdn.microsoft.com/en-us/library/system.transactions.transactionscope%28VS.80%29.aspx - lea ese ejemplo de código, muestra claramente una conexión de segundo anidado (a un segundo servidor SQL, en realidad) lo que escalará a DTC. No estamos haciendo esto en nuestro código (no estamos usando servidores SQL diferentes, ni cadenas de conexión diferentes, ni tenemos conexiones secundarias anidadas abiertas), no debería haber una escalada a DTC .
- http://davidhayden.com/blog/dave/archive/2005/12/09/2615.aspx (desde 2005) habla de cómo la escalada a DTC siempre ocurrirá cuando se conecte a SQL2000. Estamos utilizando SQL2005 / 2008
- http://msdn.microsoft.com/en-us/library/ms229978.aspx MSDN en la escalada de transacciones.
Esa página de escalada de transacciones de MSDN indica que las siguientes condiciones harán que una transacción se extienda a DTC:
- Al menos un recurso duradero que no admite notificaciones monofásicas se inscribe en la transacción.
- Al menos dos recursos duraderos que admiten notificaciones monofásicas se enlistan en la transacción. Por ejemplo, al enlistar una sola conexión con no se promueve 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 Transacciones del Sistema detecta que es el segundo recurso duradero en la transacción y lo eleva a una transacción MSDTC.
- Se invoca una solicitud para "calcular" 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 calcula por valor, lo que significa que cualquier intento de pasarlo a través de un límite de dominio de la 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 de servicio transaccional remoto. 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. El # 2 no está ocurriendo porque solo hay una conexión a la vez, y también a un solo ''recurso duradero''. ¿Hay alguna manera de que el # 1 podría estar pasando? ¿Alguna configuración SQL2005 / 8 que hace que no admita las notificaciones monofásicas?
Actualización 2:
Re-investigado, personalmente, las versiones de SQL Server de todos: "Dev 3" en realidad tiene SQL2008, y "Dev 4" en realidad es SQL2005. Eso me enseñará a nunca volver a confiar 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, como vamos a admitir a SQL2005, no podemos usar TransactionScope como lo hemos sido, y si queremos usar TransactionScope, tendremos que pasar un solo objeto SqlConnection ... lo que parece problemático en situaciones donde la Conexión Sql no se puede pasar fácilmente ... simplemente huele a instancia global de Conexión Sql. ¡Banco de iglesia!
Actualización 3
Solo para aclarar aquí en la pregunta:
SQL2008:
- Permite múltiples conexiones dentro de un único TransactionScope (como se muestra en el código de muestra anterior).
- Advertencia # 1: si esas múltiples Conexiones de SQL están anidadas, es decir, se abren dos o más Conexiones de Sql al mismo tiempo, TransactionScope escalará inmediatamente a DTC.
- Advertencia # 2: si se abre una conexión Sql adicional a un ''recurso duradero'' diferente (es decir, a un servidor SQL diferente), se escalará inmediatamente a DTC
SQL2005:
- No permite múltiples conexiones dentro de un solo TransactionScope, punto. Se intensificará cuando / si se abre una segunda SqlConnection.
Actualización 4
Con el fin de hacer que esta pregunta sea aún más útil, y solo para mayor claridad, aquí le explicamos cómo puede hacer que SQL2005 se convierta en DTC con una sola Conexión Sql:
using (TransactionScope transactionScope = new TransactionScope()) {
using (SqlConnection connection = new SqlConnection(connectionString)) {
connection.Open();
connection.Close();
connection.Open(); // escalates to DTC
}
}
Esto me parece roto, pero creo que puedo entender si todas las llamadas a SqlConnection.Open()
se están tomando del grupo de conexiones.
"¿Por qué podría pasar esto, sin embargo?" Bueno, si usa un SqlTableAdapter contra esa conexión antes de que se abra, SqlTableAdapter abrirá y cerrará la conexión, completando la transacción de manera efectiva porque ahora no puede volver a abrirla.
Así que, básicamente, para utilizar con éxito TransactionScope con SQL2005, es necesario tener algún tipo de objeto de conexión global que permanezca abierto desde el punto en que se crea una instancia del primer 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 por última vez es contrario a la lógica de abrir una conexión lo más tarde posible y cerrarla lo antes posible.
Ese código causará una escalada cuando se conecte 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 utilizaban 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 la inscripción 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?
No estoy muy seguro de si la conexión anidada es el problema. Estoy llamando a una instancia local del 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();
}
}
No sé por qué se eliminó esta respuesta, pero parece tener cierta información relevante.
Respondió el 4 de agosto del 10 a las 17:42 de Eduardo
Establezca Enlist = false en la cadena de conexión para evitar el alistamiento automático en la transacción.
Enlistar 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]
SQL Server 2008 puede usar múltiples SQLConnection
s en un TransactionScope
sin escalar, siempre que las conexiones no estén abiertas al mismo tiempo, lo que daría lugar a múltiples conexiones TCP "físicas" y, por lo tanto, requerirá 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 escalando 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.
TransactionScope siempre pasa a la transacción DTC, si usa acceso a más de 1 conexión interna. 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 desactivado". ¿Estás seguro de que está deshabilitado?