it-swarm.it

Perché "Inizia transazione" prima di "Inserisci query" blocca l'intera tabella?

Sto usando SQL Server 2005 Express.

In uno scenario, ho aggiunto Begin Transaction comando appena prima di un'istruzione INSERT in una procedura memorizzata. Quando ho eseguito questa procedura memorizzata, ha bloccato l'intera tabella e tutte le connessioni simultanee hanno mostrato un display bloccato fino al termine di questo INSERT.

Perché l'intera tabella viene bloccata e come posso superare questo problema in SQL Server 2005 Express?

Edited

La query è la seguente:

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

Questa risposta può rivelarsi utile alla domanda originale ma è principalmente indirizzata a informazioni inaccurate in altri post. Evidenzia anche una sezione di assurdità in BOL.

E come indicato per la documentazione INSERT , acquisirà un blocco esclusivo sul tavolo. L'unico modo in cui un SELECT può essere eseguito sulla tabella è utilizzare NOLOCK o impostare il livello di isolamento della transazione.

La sezione collegata di BOL afferma:

Un'istruzione INSERT acquisisce sempre un blocco esclusivo (X) sulla tabella che modifica e mantiene quel blocco fino al completamento della transazione. Con un blocco esclusivo (X), nessuna altra transazione può modificare i dati; le operazioni di lettura possono avvenire solo con l'uso del suggerimento NOLOCK o leggere il livello di isolamento senza commit. Per ulteriori informazioni, vedere Blocco nel motore di database .

NB: A partire dal 2014-8-27 BOL è stato aggiornato per rimuovere le dichiarazioni errate sopra citate.

Per fortuna non è così. Se così fosse, gli inserimenti in una tabella verrebbero eseguiti in serie e tutti i lettori verrebbero bloccati dall'intera tabella fino al completamento della transazione di inserimento. Ciò renderebbe SQL Server un server di database efficiente quanto NTFS. Non molto.

Il buon senso suggerisce che non può essere così, ma come sottolinea Paul Randall, " Fatti un favore, non fidarti di nessuno ". Se non puoi fidarti di nessuno, incluso BOL , immagino che dovremo solo provarlo.

Crea un database e popola una tabella fittizia con un gruppo di righe, notando che DatabaseId restituito.

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

Imposta una traccia del profiler che segua il lock: acquisito e lock: eventi rilasciati, filtrando su DatabaseId dallo script precedente, impostando un percorso per il file e notando il ritorno di 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

Inserisci una riga e interrompi la traccia:

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

Apri il file di traccia e dovresti trovare quanto segue:

Profiler window

La sequenza dei blocchi acquisiti è:

  1. Blocco intento-esclusivo su MyTable
  2. Blocco intento-esclusivo nella pagina 1: 211
  3. RangeInsert-NullResource sulla voce dell'indice cluster per il valore da inserire
  4. Chiave di blocco esclusivo

I blocchi vengono quindi rilasciati in ordine inverso. In nessun momento è stato acquisito un blocco esclusivo sul tavolo.

Ma questo è solo un inserimento batch! Non è lo stesso di due, tre o dozzine che corrono in parallelo.

Sì. SQL Server (e probabilmente qualsiasi motore di database relazionale) non ha alcuna previsione su quali altri batch possano essere in esecuzione quando elabora un'istruzione e/o un batch, quindi la sequenza di acquisizione dei blocchi non varia.

Che dire di livelli di isolamento più elevati, ad es. Serializable?

Per questo esempio particolare vengono presi esattamente gli stessi blocchi. Non fidarti di me, provalo!

25

Non lavoro molto su T-SQL ma dalla lettura della documentazione ...

Questo è in base alla progettazione, come indicato in INIZIO TRANSAZIONE :

A seconda delle impostazioni correnti del livello di isolamento della transazione, molte risorse acquisite per supportare le istruzioni Transact-SQL emesse dalla connessione vengono bloccate dalla transazione fino a quando non viene completata con un'istruzione COMMIT TRANSACTION o ROLLBACK TRANSACTION.

E come indicato per la documentazione INSERT , acquisirà un blocco esclusivo sul tavolo. L'unico modo in cui un SELECT può essere fatto sulla tabella è usare NOLOCK o impostare il livello di isolamento della transazione.

0
user507