sql >> Databáze >  >> RDS >> Sqlserver

Jaký je důvod použití kontextu transakce jinou relací

Na odpověď je trochu pozdě :), ale doufám, že to bude užitečné pro ostatní. Odpověď obsahuje tři části:

  1. Co to znamená "Kontext transakce je používán jinou relací?"
  2. Jak reprodukovat chybu „Kontext transakce je používán jinou relací.“

1. Co to znamená „Kontext transakce je používán jinou relací.“

Důležité upozornění:Uzamčení kontextu transakce je získáno těsně před a uvolněno ihned po interakci mezi SqlConnection a SQL Server.

Když spustíte nějaký SQL dotaz, SqlConnection "vypadá" je tam nějaká transakce, která to obaluje. Může to být SqlTransaction ("nativní" pro SqlConnection) nebo Transaction z System.Transactions shromáždění.

Když byla transakce nalezena SqlConnection používá jej ke komunikaci s SQL Serverem a v tuto chvíli komunikuje Transaction kontext je výhradně uzamčen.

Co znamená TransactionScope ? Vytvoří Transaction a poskytuje o něm informace .NET Framework Components, takže jej může používat každý, včetně SqlConnection (a podle návrhu by měl).

Takže deklaruji TransactionScope vytváříme novou transakci, která je dostupná všem "transakovatelným" objektům vytvořeným v aktuálním Thread .

Popsaná chyba znamená následující:

  1. Vytvořili jsme několik SqlConnections pod stejným TransactionContext (což znamená, že se týkají stejné transakce)
  2. Zeptali jsme se těchto SqlConnection komunikovat se serverem SQL současně
  3. Jeden z nich uzamkl aktuální Transaction kontextu a další vyhodila chybu

2. Jak reprodukovat chybu „Kontext transakce je používán jinou relací.“

Za prvé, transakční kontext je použit („uzamčen“) přímo v okamžiku provádění příkazu sql. Je tedy obtížné takové chování s jistotou reprodukovat.

Můžeme to ale zkusit udělat tak, že spustíme více vláken s relativně dlouhými SQL operacemi v rámci jedné transakce. Připravme si tabulku [dbo].[Persons] v [tests] Databáze:

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

A reprodukovat "Kontext transakce používaný jinou relací." chyba s kódem C# na základě příkladu kódu 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 závěr pár slov o implementaci podpory transakcí ve vaší aplikaci:

  • Pokud je to možné, vyhněte se vícevláknovým datovým operacím (bez ohledu na načítání nebo ukládání). Např. uložit SELECT /UPDATE / atd... požadavky v jedné frontě a obsluhují je pomocí jednoho vlákna;
  • Ve vícevláknových aplikacích používejte transakce. Vždy. Všude. Dokonce i na čtení;
  • Nesdílejte jednu transakci mezi více vlákny. Způsobuje to podivné, nezjevné, transcendentální a nereprodukovatelné chybové zprávy:
    • "Kontext transakce je používán jinou relací.":několik současných interakcí se serverem v rámci jedné transakce;
    • "Časový limit vypršel. Časový limit uplynul před dokončením operace nebo server neodpovídá.":nezávislé transakce byly dokončeny;
    • "Transakce je nejistá.";
    • ... a předpokládám, že mnoho dalších ...
  • Nezapomeňte nastavit úroveň izolace pro TransactionScope . Výchozí hodnota je Serializable ale ve většině případů ReadCommitted stačí;
  • Nezapomeňte na Complete() TransactionScope a DependentTransaction


  1. Převeďte časové razítko ve formátu řetězce TZ na časové razítko v Oracle

  2. Porušení omezení integrity:1452 Nelze přidat nebo aktualizovat podřízený řádek:

  3. SQL dotaz zahrnující skupinu podle a spojení

  4. PHP mysql dotaz posuzovat a ne echo opakovat část