Sqlserver
 sql >> Base de données >  >> RDS >> Sqlserver

Quelle est la raison du contexte de transaction utilisé par une autre session

Il est un peu tard pour répondre :) mais j'espère que cela sera utile pour les autres. La réponse contient trois parties :

  1. Que signifie "Contexte de transaction utilisé par une autre session".
  2. Comment reproduire l'erreur "Contexte de transaction utilisé par une autre session."

1. Qu'est-ce que cela signifie "Contexte de transaction utilisé par une autre session."

Avis important :le verrou de contexte de transaction est acquis juste avant et libéré immédiatement après l'interaction entre SqlConnection et SQL Server.

Lorsque vous exécutez une requête SQL, SqlConnection "regarde" y a-t-il une transaction qui l'enveloppe. Il peut s'agir de SqlTransaction ("natif" pour SqlConnection) ou Transaction de System.Transactions Assemblée.

Lorsque la transaction a trouvé SqlConnection l'utilise pour communiquer avec SQL Server et au moment où ils communiquent Transaction le contexte est exclusivement verrouillé.

Que fait TransactionScope ? Il crée Transaction et fournit des informations sur les composants .NET Framework à ce sujet, afin que tout le monde, y compris SqlConnection, puisse (et par conception devrait) l'utiliser.

Donc, en déclarant TransactionScope nous créons une nouvelle Transaction qui est disponible pour tous les objets "transactables" instanciés dans le Thread actuel .

L'erreur décrite signifie ce qui suit :

  1. Nous avons créé plusieurs SqlConnections sous le même TransactionContext (ce qui signifie qu'ils sont liés à la même transaction)
  2. Nous avons demandé à ces SqlConnection pour communiquer avec SQL Server simultanément
  3. L'un d'eux a verrouillé la Transaction actuelle contexte et la prochaine erreur renvoyée

2. Comment reproduire l'erreur "Contexte de transaction utilisé par une autre session."

Tout d'abord, le contexte de transaction est utilisé ("verrouillé") juste au moment de l'exécution de la commande sql. Il est donc difficile de reproduire un tel comportement à coup sûr.

Mais nous pouvons essayer de le faire en démarrant plusieurs threads exécutant des opérations SQL relativement longues sous la même transaction. Préparons la table [dbo].[Persons] dans [tests] Base de données :

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

Et reproduisez "Contexte de transaction utilisé par une autre session". erreur avec le code C# basé sur l'exemple de code 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();
            }
        }
    }
}

Et pour conclure, quelques mots sur l'implémentation du support des transactions dans votre application :

  • Évitez les opérations de données multithread si c'est possible (peu importe le chargement ou l'enregistrement). Par exemple. enregistrer SELECT /UPDATE / etc... requêtes dans une seule file d'attente et les traiter avec un seul thread worker ;
  • Dans les applications multithread, utilisez des transactions. Toujours. Partout. Même pour la lecture ;
  • Ne partagez pas une seule transaction entre plusieurs threads. Cela provoque des causes étranges, non évidentes, transcendantales et non reproductibles messages d'erreur :
    • "Contexte de transaction utilisé par une autre session." :plusieurs interactions simultanées avec le serveur dans le cadre d'une transaction ;
    • "Timeout expiré. Le délai d'attente s'est écoulé avant la fin de l'opération ou le serveur ne répond pas." :aucune transaction dépendante n'a été effectuée ;
    • "La transaction est incertaine." ;
    • ... et je suppose que beaucoup d'autres...
  • N'oubliez pas de définir le niveau d'isolement pour TransactionScope . La valeur par défaut est Serializable mais dans la plupart des cas ReadCommitted est suffisant ;
  • N'oubliez pas de Complete() TransactionScope et DependentTransaction