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


11

Sto usando SQL Server 2005 Express.

In uno scenario, ho aggiunto il Begin Transactioncomando appena prima di INSERTun'istruzione 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 INSERTtermine.

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

Modificato

La query è la seguente:

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

2
Non blocca la tabella in postgresql.
Scott Marlowe,

Hai bisogno di più @RPK. Con la tabella DDL e un esempio di inserti possiamo darti una spiegazione accurata di ciò che sta accadendo. Senza di essa stiamo solo indovinando.
Mark Storey-Smith,

Questa domanda è troppo vaga. Sto rimuovendo qualsiasi riferimento ad altri DBMS e sto limitando le risposte a SqlServer. Se l'OP o qualsiasi altro lettore vuole discutere i meriti di questo concetto chiave su altre piattaforme, allora dovremmo discuterne una volta per piattaforma. È dannoso rendere questo unirsi cartesiano, ci saranno troppi diversi thread di conversazione su una pagina.
jcolebrand

Risposte:


25

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 efficiente come server di database come 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 , credo che dovremo solo dimostrarlo.

Creare un database e popolare 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 seguirà 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 TraceID=@TraceID
goto finish

error: 
select ErrorCode=@rc

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:

Finestra del profiler

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 potrebbero essere in esecuzione quando elabora un'istruzione e / o un batch, quindi la sequenza di acquisizione dei blocchi non varia.

Che dire dei livelli di isolamento più elevati, ad esempio serializzabili?

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


2
Molto informativo. Bel lavoro @Mark!
jcolebrand

0

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

Questo è in base alla progettazione, come indicato nell'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 eseguito sulla tabella è utilizzare NOLOCKo impostare il livello di isolamento della transazione.


4
Non avevo mai notato quell'affermazione piuttosto mal formulata in BOL prima. Sarà richiesto un blocco esclusivo di qualcosa all'interno della gerarchia delle risorse, ma sicuramente non è sempre la tabella.
Mark Storey-Smith,

6
-1 per i documenti (non per colpa tua) - è facile dimostrare che ciò non è vero nell'isolamento dell'istantanea, quindi la coperta "acquisisce sempre un blocco esclusivo (X)" è errata. Non sono sicuro di altri livelli di isolamento.
Jack dice di provare topanswers.xyz il
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.