Dlaczego „Rozpocznij transakcję” przed „Wstaw zapytanie” blokuje cały stół?

11

Korzystam z programu SQL Server 2005 Express.

W scenariuszu dodałem Begin Transactionpolecenie tuż przed INSERTinstrukcją w procedurze składowanej. Kiedy wykonałem tę procedurę przechowywaną, zablokowałem całą tabelę, a wszystkie jednoczesne połączenia pokazywały zawieszony ekran do czasu INSERTzakończenia.

Dlaczego cała tabela jest blokowana i jak rozwiązać ten problem w SQL Server 2005 Express?

Edytowane

Zapytanie jest jak poniżej:

INSERT INTO <table2> SELECT * FROM <table1> WHERE table1.workCompleted = 'NO'
RPK
źródło
2
Nie blokuje tabeli w postgresql.
Scott Marlowe
Potrzebujesz więcej @RPK. Dzięki tabeli DDL i próbce wkładek możemy dokładnie wyjaśnić, co się dzieje. Bez tego zgadujemy.
Mark Storey-Smith
To pytanie jest zbyt niejasne. Usuwam wszelkie odniesienia do innych DBMS i ograniczam odpowiedzi do SqlServer. Jeśli OP lub inny czytelnik chce omówić zalety tej podstawowej koncepcji na innych platformach, powinniśmy ją omówić raz na platformę. Jest to szkodliwe, aby uczynić to kartezjańskim połączeniem, na jednej stronie będzie zbyt wiele różnych wątków konwersacji.
jcolebrand

Odpowiedzi:

25

Ta odpowiedź może okazać się pomocna w stosunku do pierwotnego pytania, ale ma przede wszystkim dotyczyć niedokładnych informacji w innych postach. Podkreśla także część nonsensów w BOL.

I jak stwierdzono w dokumentacji INSERT , uzyska on wyłączną blokadę na stole. Jedynym sposobem, w jaki można dokonać SELECT względem tabeli, jest użycie NOLOCK lub ustawienie poziomu izolacji transakcji.

Połączona sekcja BOL stwierdza:

Instrukcja INSERT zawsze nabywa blokadę wyłączności (X) w tabeli, którą modyfikuje, i utrzymuje tę blokadę do czasu zakończenia transakcji. Dzięki blokadzie na wyłączność (X) żadne inne transakcje nie mogą modyfikować danych; operacje odczytu mogą odbywać się tylko przy użyciu podpowiedzi NOLOCK lub odczytu niezaangażowanego poziomu izolacji. Aby uzyskać więcej informacji, zobacz Blokowanie w silniku bazy danych .

Uwaga: W dniu 2014–8–27 BOL został zaktualizowany w celu usunięcia niepoprawnych stwierdzeń cytowanych powyżej.

Na szczęście tak nie jest. Gdyby tak było, wstawianie do tabeli występowałoby szeregowo, a wszystkie czytniki byłyby blokowane z całej tabeli, dopóki transakcja wstawiania nie zostanie zakończona. Dzięki temu SQL Server byłby tak samo wydajnym serwerem bazy danych jak NTFS. Nie bardzo.

Zdrowy rozsądek sugeruje, że tak nie może być, ale jak zauważa Paul Randall: „ Zrób sobie przysługę, nie ufaj nikomu ”. Jeśli nie możesz ufać nikomu, w tym BOL , chyba będziemy musieli to udowodnić.

Utwórz bazę danych i wypełnij tabelę zastępczą wiązką wierszy, zwracając uwagę, że zwrócono 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

Skonfiguruj śledzenie profilera, które będzie śledzić blokadę: uzyskane i blokowanie: zwolnione zdarzenia, filtrowanie na DatabaseId z poprzedniego skryptu, ustawienie ścieżki do pliku i odnotowanie zwrócenia 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 TraceID=@TraceID
goto finish

error: 
select ErrorCode=@rc

finish: 
go

Wstaw wiersz i zatrzymaj śledzenie:

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

Otwórz plik śledzenia, a znajdziesz następujące informacje:

Okno Profiler

Sekwencja wykonanych blokad jest następująca:

  1. Blokada celowa w MyTable
  2. Blokada celowa na stronie 1: 211
  3. RangeInsert-NullResource na pozycji indeksu klastrowego dla wstawianej wartości
  4. Ekskluzywny zamek na klucz

Blokady są następnie zwalniane w odwrotnej kolejności. W żadnym momencie nie uzyskano wyłącznego zamka na stole.

Ale to tylko jedna wstawka partii! To nie to samo, co dwa, trzy lub kilkadziesiąt biegnących równolegle.

Tak to jest. SQL Server (i prawdopodobnie dowolny silnik relacyjnej bazy danych) nie ma przewidywania, jakie inne partie mogą być uruchomione podczas przetwarzania instrukcji i / lub partii, więc sekwencja pozyskiwania blokady nie jest różna.

Co z wyższymi poziomami izolacji, np. Serializable?

W tym konkretnym przykładzie przyjmowane są dokładnie takie same blokady. Nie ufaj mi, spróbuj!

Mark Storey-Smith
źródło
2
Bardzo informujące. Dobra robota @ Mark!
jcolebrand
0

Nie wykonuję dużo pracy w języku T-SQL, ale czytam dokumentację

Jest to zgodne z projektem, jak stwierdzono w POCZĄTKU TRANSAKCJI :

W zależności od bieżących ustawień poziomu izolacji transakcji wiele zasobów uzyskanych w celu obsługi instrukcji Transact-SQL wydanych przez połączenie jest blokowanych przez transakcję, dopóki nie zostanie zakończona instrukcją COMMIT TRANSACTION lub ROLLBACK TRANSACTION.

I jak stwierdzono w dokumentacji INSERT , uzyska on wyłączną blokadę na stole. Jedynym sposobem, w jaki można wykonać SELECT względem tabeli, jest użycie NOLOCKlub ustawienie poziomu izolacji transakcji.


źródło
4
Nie zauważyłem wcześniej tak źle sformułowanego oświadczenia w BOL. Wymagana będzie wyłączna blokada czegoś w hierarchii zasobów, ale zdecydowanie nie zawsze jest to tabela.
Mark Storey-Smith
6
-1 dla dokumentów (to nie twoja wina) - łatwo jest udowodnić, że nie jest to prawdą w izolacji migawki, więc koc „zawsze uzyskuje blokadę wyłączną (X)” jest niewłaściwy. Nie jestem pewien co do innych poziomów izolacji.
Jack mówi, że spróbuj topanswers.xyz