it-swarm-eu.dev

Proč "Zahájit transakci" před "Vložit dotaz" uzamkne celou tabulku?

Používám SQL Server 2005 Express.

Ve scénáři jsem přidal příkaz Begin Transaction Těsně před příkaz INSERT v uložené proceduře. Když jsem provedl tuto uloženou proceduru, uzamkl celou tabulku a všechna souběžná připojení zobrazovala zavěšené zobrazení, dokud tento INSERT skončil.

Proč je uzamčena celá tabulka a jak překonám tento problém v SQL Server 2005 Express?

praveno

Dotaz je níže:

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

Tato odpověď se může ukázat jako užitečná pro původní otázku, ale primárně se týká nepřesných informací v jiných příspěvcích. Zdůrazňuje také část nesmyslů v BOL.

A jak je uvedeno pro dokumentaci INSERT , získá exkluzivní zámek na stole. Jediným způsobem, jak lze VYBRAT proti tabulce, je použít NOLOCK nebo nastavit úroveň izolace transakce.

Propojená část BOL uvádí:

Příkaz INSERT vždy získá exkluzivní (X) zámek v tabulce, kterou upravuje, a tento zámek drží, dokud není transakce dokončena. S exkluzivním (X) zámkem nemohou žádné jiné transakce upravovat data; operace čtení mohou probíhat pouze s použitím nápovědy NOLOCK nebo čtení nesouhlasené úrovně izolace. Další informace viz Zamykání v databázovém stroji .

Pozn .: Od roku 2014-8-27 byl BOL aktualizován, aby odstranil výše uvedené nesprávné příkazy

Naštěstí tomu tak není. Pokud by tomu tak bylo, vložení do tabulky by nastalo sériově a všichni čtenáři by byli blokováni z celé tabulky, dokud nebude dokončena transakce vložení. Díky tomu by byl SQL Server stejně efektivní jako databázový server jako NTFS. Nepříliš.

Zdravý rozum naznačuje, že to tak nemůže být, ale jak Paul Randall zdůrazňuje, „ dělejte laskavost, nevěřte nikom “. Pokud nikomu nemůžete věřit, včetně BOL , tak to prostě musíme dokázat.

Vytvořte databázi a vyplňte tabulku figuríny spoustou řádků, všimněte si vrácené DatabaseId.

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

Nastavte trasování profilu, které bude sledovat události lock: získané a lock: release, filtrování na DatabaseId z předchozího skriptu, nastavení cesty k souboru a zaznamenání vráceného TraceId.

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

Vložte řádek a zastavte trasování:

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

Otevřete soubor trasování a měli byste najít následující:

Profiler window

Posloupnost provedených zámků je:

  1. Exkluzivní zámek na MyTable
  2. Zámek Intent-Exclusive na stránce 1: 211
  3. RangeInsert-NullResource v položce seskupeného indexu pro vloženou hodnotu
  4. Exkluzivní zámek na klíči

Zámky se poté uvolní v obráceném pořadí. V žádném okamžiku nebyl na stole získán exkluzivní zámek.

Ale to je jen jedna šarže! To není stejné jako dvě, tři nebo desítky běžící paralelně.

Ano to je. SQL Server (a pravděpodobně jakýkoli relační databázový stroj) nemá předvídavost, jaké další dávky mohou být spuštěny, když zpracovává příkaz a/nebo dávku, takže se sekvence získávání zámku nemění.

A co vyšší izolační úrovně, např. Serializovatelné?

Pro tento konkrétní příklad se berou přesně stejné zámky. Nevěř mi, zkus to!

25

Nemám moc práce s T-SQL, ale ze čtení dokumentace ...

Toto je záměrné, jak je uvedeno v BEGIN TRANSACTION :

V závislosti na aktuálním nastavení úrovně izolace transakcí je mnoho prostředků získaných na podporu příkazů Transact-SQL vydaných spojením transakcí uzamčeno, dokud není dokončeno příkazem COMMIT TRANSACTION nebo ROLLBACK TRANSACTION.

A jak je uvedeno pro dokumentaci INSERT , získá exkluzivní zámek na stole. Jediným způsobem, jak lze provést SELECT proti tabulce, je použít NOLOCK nebo nastavit úroveň izolace transakce.

0
user507