web-dev-qa-db-fra.com

Pourquoi "Commencer la transaction" avant "Insérer une requête" verrouille la table entière?

J'utilise SQL Server 2005 Express.

Dans un scénario, j'ai ajouté Begin Transaction commande juste avant une instruction INSERT dans une procédure stockée. Lorsque j'ai exécuté cette procédure stockée, elle a verrouillé la table entière et toutes les connexions simultanées ont affiché un affichage bloqué jusqu'à la fin de cette INSERT.

Pourquoi la table entière est-elle verrouillée et comment résoudre ce problème dans SQL Server 2005 Express?

Modifié

La requête est comme ci-dessous:

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

Cette réponse peut s'avérer utile pour la question d'origine, mais vise principalement à corriger des informations inexactes dans d'autres publications. Il met également en évidence une section de non-sens dans BOL.

Et comme indiqué pour la documentation INSERT , il obtiendra un verrou exclusif sur la table. La seule façon d'effectuer un SELECT sur la table est d'utiliser NOLOCK ou de définir le niveau d'isolement de la transaction.

La section liée de BOL indique:

Une instruction INSERT acquiert toujours un verrou exclusif (X) sur la table qu'elle modifie et conserve ce verrou jusqu'à la fin de la transaction. Avec un verrou exclusif (X), aucune autre transaction ne peut modifier les données; les opérations de lecture ne peuvent avoir lieu qu'avec l'utilisation de l'indicateur NOLOCK ou le niveau d'isolement non engagé. Pour plus d'informations, consultez Verrouillage dans le moteur de base de données .

NB: Depuis le 2014-8-27 BOL a été mis à jour pour supprimer les déclarations incorrectes citées ci-dessus.

Heureusement, ce n'est pas le cas. S'il en était ainsi, les insertions dans une table se produiraient en série et tous les lecteurs seraient bloqués de la table entière jusqu'à la fin de la transaction d'insertion. Cela ferait de SQL Server un serveur de base de données aussi efficace que NTFS. Pas très.

Le bon sens suggère qu'il ne peut en être ainsi, mais comme le souligne Paul Randall, " Rendez-vous service, ne faites confiance à personne ". Si vous ne pouvez faire confiance à personne, y compris BOL , je suppose que nous devrons simplement le prouver.

Créez une base de données et remplissez une table fictive avec un tas de lignes, en notant le DatabaseId renvoyé.

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

Configurez une trace de profileur qui suivra les événements de verrouillage: acquis et de verrouillage: libéré, filtrage sur le DatabaseId à partir du script précédent, définition d'un chemin d'accès au fichier et notant le TraceId renvoyé.

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

Insérez une ligne et arrêtez la trace:

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

Ouvrez le fichier de trace et vous devriez trouver ce qui suit:

Profiler window

La séquence de verrous pris est:

  1. Verrouillage exclusif sur MyTable
  2. Serrure intentionnelle exclusive sur la page 1: 211
  3. RangeInsert-NullResource sur l'entrée d'index cluster pour la valeur insérée
  4. Serrure exclusive sur la clé

Les verrous sont ensuite libérés dans l'ordre inverse. À aucun moment un verrou exclusif n'a été acquis sur la table.

Mais ce n'est qu'une insertion par lots! Ce n'est pas la même chose que deux, trois ou des dizaines fonctionnant en parallèle.

Oui, ça l'est. SQL Server (et sans doute tout moteur de base de données relationnelle) ne sait pas quels autres lots peuvent être exécutés lorsqu'il traite une instruction et/ou un lot, de sorte que la séquence d'acquisition des verrous ne varie pas.

Qu'en est-il des niveaux d'isolement plus élevés, par exemple Sérialisable?

Pour cet exemple particulier, exactement les mêmes verrous sont pris. Ne me faites pas confiance, essayez-le!

25

Je ne fais pas beaucoup de travail T-SQL mais à la lecture de la documentation ...

C'est par conception, comme indiqué dans le COMMENCER LA TRANSACTION :

Selon les paramètres de niveau d'isolation de transaction actuels, de nombreuses ressources acquises pour prendre en charge les instructions Transact-SQL émises par la connexion sont verrouillées par la transaction jusqu'à ce qu'elle soit terminée avec une instruction COMMIT TRANSACTION ou ROLLBACK TRANSACTION.

Et comme indiqué pour la documentation INSERT , il obtiendra un verrou exclusif sur la table. La seule façon d'effectuer un SELECT sur la table est d'utiliser NOLOCK ou de définir le niveau d'isolement de la transaction.

0
user507