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

11

J'utilise SQL Server 2005 Express.

Dans un scénario, j'ai ajouté une Begin Transactioncommande juste avant une INSERTinstruction dans une procédure stockée. Lorsque j'ai exécuté cette procédure stockée, elle a verrouillé toute la table et toutes les connexions simultanées ont affiché un affichage bloqué jusqu'à ce que cela se INSERTtermine.

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

Édité

La requête est comme ci-dessous:

INSERT INTO <table2> SELECT * FROM <table1> WHERE table1.workCompleted = 'NO'
RPK
la source
2
Il ne verrouille pas la table dans postgresql.
Scott Marlowe
Besoin de plus de @RPK. Avec le tableau DDL et un échantillon des inserts, nous pouvons vous donner une explication précise de ce qui se passe. Sans cela, nous ne faisons que deviner.
Mark Storey-Smith,
Cette question est trop vague. Je supprime toute référence à d'autres SGBD et limite les réponses à SqlServer. Si le PO ou tout autre lecteur souhaite discuter des mérites de ce concept de base sur d'autres plates-formes, nous devrions le faire discuter une fois par plate-forme. Il est néfaste d'en faire une jointure cartésienne, il y aura trop de fils de conversation différents sur une seule page.
jcolebrand

Réponses:

25

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 validé. Pour plus d'informations, voir 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:

Fenêtre du profileur

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érialisables?

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

Mark Storey-Smith
la source
2
Très instructif. Beau travail @Mark!
jcolebrand
0

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

C'est par conception, comme indiqué dans la transaction de début :

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 NOLOCKou de définir le niveau d'isolement de la transaction.


la source
4
Je n'avais pas remarqué cette déclaration plutôt mal formulée dans BOL auparavant. Un verrou exclusif sur quelque chose dans la hiérarchie des ressources sera nécessaire, mais ce n'est certainement pas toujours la table.
Mark Storey-Smith,
6
-1 pour les documents (ce n'est pas votre faute) - il est facile de prouver que ce n'est pas vrai dans l'isolement de cliché, donc la couverture "acquiert toujours un verrou exclusif (X)" est erronée. Pas sûr des autres niveaux d'isolement.
Jack dit d'essayer topanswers.xyz