sql >> Base de Datos >  >> RDS >> Sqlserver

¿Cuál es el motivo del contexto de transacción en uso por otra sesión?

Es un poco tarde para la respuesta :) pero espero que sea útil para otros. La respuesta consta de tres partes:

  1. ¿Qué significa "Contexto de transacción en uso por otra sesión".
  2. Cómo reproducir el error "Contexto de transacción en uso por otra sesión".

1. ¿Qué significa "Contexto de transacción en uso por otra sesión".

Aviso importante:el bloqueo de contexto de transacción se adquiere justo antes y se libera inmediatamente después de la interacción entre SqlConnection y servidor SQL.

Cuando ejecuta alguna consulta SQL, SqlConnection "parece" hay alguna transacción que lo envuelva. Puede ser SqlTransaction ("nativo" para SqlConnection) o Transaction de System.Transactions asamblea.

Cuando la transacción encontró SqlConnection lo usa para comunicarse con SQL Server y en el momento en que comunican Transaction el contexto está bloqueado exclusivamente.

¿Qué hace TransactionScope ? Crea Transaction y proporciona información sobre los componentes de .NET Framework, por lo que todos, incluido SqlConnection, pueden (y por diseño deben) usarlo.

Declarando TransactionScope estamos creando una nueva Transacción que está disponible para todos los objetos "transaccionables" instanciados en el Thread actual .

El error descrito significa lo siguiente:

  1. Creamos varias SqlConnections bajo el mismo TransactionContext (lo que significa que se relacionaron con la misma transacción)
  2. Les preguntamos a estos SqlConnection para comunicarse con SQL Server simultáneamente
  3. Uno de ellos bloqueó la Transaction actual contexto y el siguiente arrojó un error

2. Cómo reproducir el error "Contexto de transacción en uso por otra sesión".

En primer lugar, el contexto de la transacción se usa ("bloqueado") justo en el momento de la ejecución del comando sql. Por lo tanto, es difícil reproducir ese comportamiento con seguridad.

Pero podemos intentar hacerlo iniciando varios subprocesos que ejecutan operaciones SQL relativamente largas en una sola transacción. Preparemos la tabla [dbo].[Persons] en [tests] Base de datos:

USE [tests]
GO
DROP TABLE [dbo].[Persons]
GO
CREATE TABLE [dbo].[Persons](
    [Id] [bigint] IDENTITY(1,1) NOT NULL PRIMARY KEY,
    [Name] [nvarchar](1024) NOT NULL,
    [Nick] [nvarchar](1024) NOT NULL,
    [Email] [nvarchar](1024) NOT NULL)
GO
DECLARE @Counter INT
SET @Counter = 500

WHILE (@Counter > 0) BEGIN
    INSERT [dbo].[Persons] ([Name], [Nick], [Email])
    VALUES ('Sheev Palpatine', 'DarthSidious', '[email protected]')
    SET @Counter = @Counter - 1
END
GO

Y reproduzca "Contexto de transacción en uso por otra sesión". error con el código C# basado en el ejemplo de código de Shrike

using System;
using System.Collections.Generic;
using System.Threading;
using System.Transactions;
using System.Data.SqlClient;

namespace SO.SQL.Transactions
{
    public static class TxContextInUseRepro
    {
        const int Iterations = 100;
        const int ThreadCount = 10;
        const int MaxThreadSleep = 50;
        const string ConnectionString = "Initial Catalog=tests;Data Source=.;" +
                                        "User ID=testUser;PWD=Qwerty12;";
        static readonly Random Rnd = new Random();
        public static void Main()
        {
            var txOptions = new TransactionOptions();
            txOptions.IsolationLevel = IsolationLevel.ReadCommitted;
            using (var ctx = new TransactionScope(
                TransactionScopeOption.Required, txOptions))
            {
                var current = Transaction.Current;
                DependentTransaction dtx = current.DependentClone(
                    DependentCloneOption.BlockCommitUntilComplete);               
                for (int i = 0; i < Iterations; i++)
                {
                    // make the transaction distributed
                    using (SqlConnection con1 = new SqlConnection(ConnectionString))
                    using (SqlConnection con2 = new SqlConnection(ConnectionString))
                    {
                        con1.Open();
                        con2.Open();
                    }

                    var threads = new List<Thread>();
                    for (int j = 0; j < ThreadCount; j++)
                    {
                        Thread t1 = new Thread(o => WorkCallback(dtx));
                        threads.Add(t1);
                        t1.Start();
                    }

                    for (int j = 0; j < ThreadCount; j++)
                        threads[j].Join();
                }
                dtx.Complete();
                ctx.Complete();
            }
        }

        private static void WorkCallback(DependentTransaction dtx)
        {
            using (var txScope1 = new TransactionScope(dtx))
            {
                using (SqlConnection con2 = new SqlConnection(ConnectionString))
                {
                    Thread.Sleep(Rnd.Next(MaxThreadSleep));
                    con2.Open();
                    using (var cmd = new SqlCommand("SELECT * FROM [dbo].[Persons]", con2))
                    using (cmd.ExecuteReader()) { } // simply recieve data
                }
                txScope1.Complete();
            }
        }
    }
}

Y en conclusión, algunas palabras sobre la implementación del soporte de transacciones en su aplicación:

  • Evite las operaciones de datos de subprocesos múltiples si es posible (no importa cargar o guardar). P.ej. guardar SELECT /UPDATE / etc... solicitudes en una sola cola y atenderlas con un trabajador de un solo subproceso;
  • En aplicaciones de subprocesos múltiples, use transacciones. Siempre. En todas partes. Incluso para leer;
  • No comparta transacciones únicas entre varios subprocesos. Causas extrañas, no obvias, trascendentales y no reproducibles mensajes de error:
    • "Contexto de transacción en uso por otra sesión.":múltiples interacciones simultáneas con el servidor en una transacción;
    • "El tiempo de espera expiró. El período de tiempo de espera transcurrió antes de completar la operación o el servidor no responde".:no se completaron transacciones dependientes;
    • "La transacción está en duda.";
    • ... y supongo que muchas otras...
  • No olvide establecer el nivel de aislamiento para TransactionScope . El valor predeterminado es Serializable pero en la mayoría de los casos ReadCommitted es suficiente;
  • No olvide completar() TransactionScope y DependentTransaction