it-swarm-eu.dev

Warum "Transaktion starten", bevor "Abfrage einfügen" die gesamte Tabelle sperrt?

Ich verwende SQL Server 2005 Express.

In einem Szenario habe ich Begin Transaction Befehl kurz vor einer INSERT Anweisung in einer gespeicherten Prozedur. Als ich diese gespeicherte Prozedur ausführte, sperrte sie die gesamte Tabelle und alle gleichzeitigen Verbindungen zeigten eine blockierte Anzeige, bis diese INSERT beendet war.

Warum wird die gesamte Tabelle gesperrt und wie kann ich dieses Problem in SQL Server 2005 Express beheben?

bearbeitet

Die Abfrage ist wie folgt:

INSERT INTO <table2> SELECT * FROM <table1> WHERE table1.workCompleted = 'NO'
11
RPK

Diese Antwort kann sich als hilfreich für die ursprüngliche Frage erweisen, dient jedoch in erster Linie dazu, ungenaue Informationen in anderen Posts zu behandeln. Es wird auch ein Abschnitt des Unsinns in BOL hervorgehoben.

Und wie in der Dokumentation INSERT angegeben, erhält es eine exklusive Sperre für die Tabelle. Die einzige Möglichkeit, ein SELECT für die Tabelle durchzuführen, besteht darin, NOLOCK zu verwenden oder die Isolationsstufe der Transaktion festzulegen.

Der verknüpfte Abschnitt von BOL lautet:

Eine INSERT-Anweisung erhält immer eine exklusive (X) Sperre für die Tabelle, die sie ändert, und hält diese Sperre, bis die Transaktion abgeschlossen ist. Mit einer exklusiven (X) Sperre können keine anderen Transaktionen Daten ändern. Lesevorgänge können nur unter Verwendung des NOLOCK-Hinweises oder der nicht festgeschriebenen Isolationsstufe erfolgen. Weitere Informationen finden Sie unter Sperren in der Datenbank-Engine .

NB: Ab dem 27.08.2014 wurde BOL aktualisiert, um die oben angegebenen falschen Aussagen zu entfernen.

Zum Glück ist dies nicht der Fall. Wenn dies der Fall wäre, würden Einfügungen in eine Tabelle seriell erfolgen und alle Leser würden für die gesamte Tabelle blockiert, bis die Einfügetransaktion abgeschlossen ist. Das würde SQL Server als Datenbankserver genauso effizient machen wie NTFS. Nicht sehr.

Der gesunde Menschenverstand legt nahe, dass dies nicht der Fall sein kann, aber wie Paul Randall betont: " Tu dir selbst einen Gefallen, vertraue niemandem ". Wenn Sie niemandem vertrauen können, einschließlich BOL , müssen wir es wohl nur beweisen.

Erstellen Sie eine Datenbank und füllen Sie eine Dummy-Tabelle mit einer Reihe von Zeilen, wobei Sie die zurückgegebene DatabaseId notieren.

SET STATISTICS IO OFF;
SET STATISTICS TIME OFF;

USE [master]
GO

IF EXISTS (SELECT name FROM sys.databases WHERE name = N'LockDemo')
DROP DATABASE [LockDemo]
GO

DECLARE @DataFilePath NVARCHAR(4000)
SELECT 
    @DataFilePath = SUBSTRING(physical_name, 1, CHARINDEX(N'master.mdf', LOWER(physical_name)) - 1)
FROM 
    master.sys.master_files
WHERE 
    database_id = 1 AND file_id = 1

EXEC ('
CREATE DATABASE [LockDemo] ON  PRIMARY 
( NAME = N''LockDemo'', FILENAME = N''' + @DataFilePath + N'LockDemo.mdf' + ''', SIZE = 2MB , MAXSIZE = UNLIMITED, FILEGROWTH = 2MB )
 LOG ON 
( NAME = N''LockDemo_log'', FILENAME = N''' + @DataFilePath + N'LockDemo_log.ldf' + ''', SIZE = 1MB , MAXSIZE = UNLIMITED , FILEGROWTH = 1MB )
')

GO

USE [LockDemo]
GO

SELECT DB_ID() AS DatabaseId

CREATE TABLE [dbo].[MyTable]
(
    [id] [int] IDENTITY(1,1) PRIMARY KEY CLUSTERED
    , [filler] CHAR(4030) NOT NULL DEFAULT REPLICATE('A', 4030) 
)
GO

INSERT MyTable DEFAULT VALUES;
GO 100

Richten Sie einen Profiler-Trace ein, der die Ereignisse sperren: erfasst und gesperrt: freigegeben verfolgt, nach der Datenbank-ID aus dem vorherigen Skript filtert, einen Pfad für die Datei festlegt und die zurückgegebene Trace-ID notiert.

declare @rc int
declare @TraceID int
declare @maxfilesize BIGINT
declare @databaseid INT
DECLARE @tracefile NVARCHAR(4000)

set @maxfilesize = 5 
SET @tracefile = N'D:\Temp\LockTrace'
SET @databaseid = 9

exec @rc = sp_trace_create @TraceID output, 0, @tracefile, @maxfilesize, NULL 
if (@rc != 0) goto error

declare @on bit
set @on = 1
exec sp_trace_setevent @TraceID, 24, 32, @on
exec sp_trace_setevent @TraceID, 24, 1, @on
exec sp_trace_setevent @TraceID, 24, 57, @on
exec sp_trace_setevent @TraceID, 24, 3, @on
exec sp_trace_setevent @TraceID, 24, 51, @on
exec sp_trace_setevent @TraceID, 24, 12, @on
exec sp_trace_setevent @TraceID, 60, 32, @on
exec sp_trace_setevent @TraceID, 60, 57, @on
exec sp_trace_setevent @TraceID, 60, 3, @on
exec sp_trace_setevent @TraceID, 60, 51, @on
exec sp_trace_setevent @TraceID, 60, 12, @on
exec sp_trace_setevent @TraceID, 23, 32, @on
exec sp_trace_setevent @TraceID, 23, 1, @on
exec sp_trace_setevent @TraceID, 23, 57, @on
exec sp_trace_setevent @TraceID, 23, 3, @on
exec sp_trace_setevent @TraceID, 23, 51, @on
exec sp_trace_setevent @TraceID, 23, 12, @on

-- DatabaseId filter
exec sp_trace_setfilter @TraceID, 3, 0, 0, @databaseid

-- Set the trace status to start
exec sp_trace_setstatus @TraceID, 1

-- display trace id for future references
select [email protected]
goto finish

error: 
select [email protected]

finish: 
go

Fügen Sie eine Zeile ein und stoppen Sie die Ablaufverfolgung:

USE LockDemo
GO
INSERT MyTable DEFAULT VALUES
GO
EXEC sp_trace_setstatus 3, 0
EXEC sp_trace_setstatus 3, 2
GO

Öffnen Sie die Trace-Datei und Sie sollten Folgendes finden:

Profiler window

Die Reihenfolge der gesperrten Sperren ist:

  1. Absichts-exklusive Sperre für MyTable
  2. Absichts-Exklusiv-Sperre auf Seite 1: 211
  3. RangeInsert-NullResource für den Clustered-Indexeintrag für den einzufügenden Wert
  4. Exklusives Schlüsselschloss

Die Schlösser werden dann in umgekehrter Reihenfolge freigegeben. Zu keinem Zeitpunkt wurde eine exklusive Sperre für den Tisch erworben.

Dies ist jedoch nur eine Chargeneinfügung! Das ist nicht dasselbe wie zwei, drei oder Dutzende, die parallel laufen.

Ja, so ist es. SQL Server (und möglicherweise jedes relationale Datenbankmodul) hat keine Voraussicht darüber, welche anderen Stapel möglicherweise ausgeführt werden, wenn eine Anweisung und/oder ein Stapel verarbeitet werden, sodass die Reihenfolge der Sperrenerfassung nicht variiert.

Was ist mit höheren Isolationsniveaus, z. Serialisierbar?

Für dieses spezielle Beispiel werden genau die gleichen Sperren verwendet. Vertrau mir nicht, versuch es!

25

Ich mache nicht viel T-SQL-Arbeit, sondern lese Dokumentation ...

Dies ist beabsichtigt, wie in BEGIN TRANSACTION angegeben:

Abhängig von den aktuellen Einstellungen für die Transaktionsisolationsstufe werden viele Ressourcen, die zur Unterstützung der von der Verbindung ausgegebenen Transact-SQL-Anweisungen erworben wurden, von der Transaktion gesperrt, bis sie entweder mit einer COMMIT TRANSACTION- oder einer ROLLBACK TRANSACTION-Anweisung abgeschlossen wird.

Und wie in der Dokumentation INSERT angegeben, wird die Tabelle exklusiv gesperrt. Die einzige Möglichkeit, ein SELECT für die Tabelle durchzuführen, besteht darin, NOLOCK zu verwenden oder die Isolationsstufe der Transaktion festzulegen.

0
user507