sql >> Databasteknik >  >> RDS >> Sqlserver

Vad är anledningen till att Transaktionskontext används av en annan session

Det är lite sent att svara :) men hoppas att det kommer att vara användbart för andra. Svaret innehåller tre delar:

  1. Vad betyder det "Transaktionskontext som används av en annan session."
  2. Hur man återskapar felet "Transaktionskontext används av en annan session."

1. Vad betyder det "Transaktionskontext används av en annan session."

Viktigt meddelande:Transaktionskontextlåset förvärvas precis före och släpps omedelbart efter interaktion mellan SqlConnection och SQL Server.

När du kör en SQL-fråga, SqlConnection "looks" finns det någon transaktion som omsluter det. Det kan vara SqlTransaction ("native" för SqlConnection) eller Transaction från System.Transactions hopsättning.

När transaktionen hittades SqlConnection använder den för att kommunicera med SQL Server och för tillfället kommunicerar de Transaction sammanhanget är uteslutande låst.

Vad betyder TransactionScope ? Den skapar Transaction och tillhandahåller .NET Framework Components-information om det, så alla inklusive SqlConnection kan (och genom design borde) använda det.

Så deklarerar TransactionScope vi skapar en ny transaktion som är tillgänglig för alla "transaktionsbara" objekt som instansierats i nuvarande Thread .

Beskrivet fel betyder följande:

  1. Vi skapade flera SqlConnections under samma TransactionContext (vilket betyder att de relaterade till samma transaktion)
  2. Vi frågade dessa SqlConnection att kommunicera med SQL Server samtidigt
  3. En av dem låste nuvarande Transaction sammanhang och nästa gav fel

2. Hur man återskapar felet "Transaktionskontext används av en annan session."

Först och främst används transaktionskontext ("låst") precis vid tidpunkten för exekvering av sql-kommandot. Så det är säkert svårt att reproducera ett sådant beteende.

Men vi kan försöka göra det genom att starta flera trådar som kör relativt långa SQL-operationer under den enda transaktionen. Låt oss förbereda tabellen [dbo].[Persons] i [tests] Databas:

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

Och återskapa "Transaktionskontext som används av en annan session." fel med C#-kod baserat på Shrike-kodexempel

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();
            }
        }
    }
}

Och avslutningsvis några ord om att implementera transaktionsstöd i din applikation:

  • Undvik flertrådade dataoperationer om det är möjligt (oavsett om du laddar eller sparar). T.ex. spara SELECT /UPDATE / etc... förfrågningar i en enda kö och servera dem med en entrådsarbetare;
  • Använd transaktioner i flertrådiga applikationer. Alltid. Överallt. Även för att läsa;
  • Dela inte en transaktion mellan flera trådar. Det orsakar konstiga, omedvetna, transcendentala och ej reproducerbara felmeddelanden:
    • "Transaktionskontext används av en annan session.":flera samtidiga interaktioner med servern under en transaktion;
    • "Timeout har gått ut. Timeoutperioden förflutit innan operationen slutfördes eller så svarar inte servern.":icke beroende transaktioner slutfördes;
    • "Transaktionen är osäker.";
    • ... och jag antar många andra ...
  • Glöm inte att ställa in isoleringsnivå för TransactionScope . Standard är Serializable men i de flesta fall ReadCommitted räcker;
  • Glöm inte att Complete() TransactionScope och DependentTransaction


  1. Undantag för överträdelse av begränsningar ORA-00001

  2. mySQL CAST och ASC/DESC

  3. Sql LEFT OUTER JOIN med WHERE-sats

  4. Knex.js tvingar sessionsåteranvändning för 4 följande frågor