Sqlserver
 sql >> Baza danych >  >> RDS >> Sqlserver

Jaki jest powód użycia kontekstu Transakcji przez inną sesję?

Trochę za późno na odpowiedź :), ale mam nadzieję, że przyda się innym. Odpowiedź składa się z trzech części:

  1. Co to znaczy „Kontekst transakcji używany przez inną sesję?”
  2. Jak odtworzyć błąd „Kontekst transakcji używany przez inną sesję”.

1. Co to znaczy „Kontekst transakcji używany przez inną sesję”.

Ważna uwaga:Blokada kontekstu transakcji jest pobierana tuż przed i zwalniana natychmiast po interakcji między SqlConnection i SQL Server.

Kiedy wykonujesz jakieś zapytanie SQL, SqlConnection "wygląda", czy jest jakaś transakcja, która go otacza. Może to być SqlTransaction ("native" dla SqlConnection) lub Transaction z System.Transactions montaż.

Po znalezieniu transakcji SqlConnection używa go do komunikacji z SQL Serverem i w tej chwili komunikuje się Transaction kontekst jest zablokowany na wyłączność.

Co oznacza TransactionScope ? Tworzy Transaction i dostarcza informacji o komponentach .NET Framework, aby każdy, w tym SqlConnection, mógł (i powinien) z niego korzystać.

Więc deklarowanie TransactionScope tworzymy nową transakcję, która jest dostępna dla wszystkich obiektów "transactable" utworzonych w bieżącym Thread .

Opisany błąd oznacza:

  1. Utworzyliśmy kilka SqlConnections pod tym samym TransactionContext (co oznacza, że ​​dotyczyły tej samej transakcji)
  2. Poprosiliśmy te SqlConnection komunikować się z SQL Server jednocześnie
  3. Jeden z nich zablokował bieżącą Transaction kontekst, a następny wyrzucił błąd

2. Jak odtworzyć błąd „Kontekst transakcji używany przez inną sesję”.

Przede wszystkim kontekst transakcji jest używany ("zablokowany") w momencie wykonania polecenia sql. Więc na pewno trudno jest odtworzyć takie zachowanie.

Ale możemy spróbować to zrobić, uruchamiając wiele wątków wykonujących stosunkowo długie operacje SQL w ramach pojedynczej transakcji. Przygotujmy tabelę [dbo].[Persons] w [tests] Baza danych:

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

I odtwórz „Kontekst transakcji używany przez inną sesję”. błąd z kodem C# opartym na przykładzie kodu 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();
            }
        }
    }
}

A na zakończenie kilka słów o wdrożeniu obsługi transakcji w Twojej aplikacji:

  • Unikaj wielowątkowych operacji na danych, jeśli to możliwe (bez względu na ładowanie lub zapisywanie). Np. zapisz SELECT /UPDATE / etc... żądań w jednej kolejce i obsługuj je za pomocą jednowątkowego pracownika;
  • W aplikacjach wielowątkowych używaj transakcji. Zawsze. Wszędzie. Nawet do czytania;
  • Nie udostępniaj pojedynczej transakcji między wieloma wątkami. Powoduje dziwne, nieoczywiste, transcendentalne i nieodtwarzalne komunikaty o błędach:
    • "Kontekst transakcji używany przez inną sesję":wiele jednoczesnych interakcji z serwerem w ramach jednej transakcji;
    • "Upłynął limit czasu. Limit czasu, który upłynął przed zakończeniem operacji lub serwer nie odpowiada.":zakończone zostały niezależne transakcje;
    • "Transakcja jest wątpliwa.";
    • ... i zakładam wiele innych...
  • Nie zapomnij ustawić poziomu izolacji dla TransactionScope . Wartość domyślna to Serializable ale w większości przypadków ReadCommitted wystarczy;
  • Nie zapomnij o funkcji Complete() TransactionScope i DependentTransaction


  1. Database
  2.   
  3. Mysql
  4.   
  5. Oracle
  6.   
  7. Sqlserver
  8.   
  9. PostgreSQL
  10.   
  11. Access
  12.   
  13. SQLite
  14.   
  15. MariaDB
  1. Jak wstawić do tabeli z tylko jedną kolumną IDENTITY?

  2. Optymistyczne vs. pesymistyczne blokowanie

  3. Partycjonowanie skutkuje uruchomieniem zapytania podsumowującego

  4. SQL Server zlicza liczbę odrębnych wartości w każdej kolumnie tabeli

  5. Wyłącz wszystkie indeksy nieklastrowane