web-dev-qa-db-fra.com

Quel effet HOLDLOCK a-t-il sur UPDLOCK?

J'ai vu de nombreux exemples de l'indice HOLDLOCK utilisé en combinaison avec UPDLOCK ( comme ceci ). Cependant la documentation de Microsoft pour ces conseils donne l'impression que HOLDLOCK devrait être redondant, car UPDLOCK persiste déjà le verrou jusqu'à la fin de la transaction. (Il semble également que HOLDLOCK ne s'applique de toute façon qu'aux verrous partagés.)

Comment HOLDLOCK affecte-t-il la requête, le cas échéant?

30
marijne

Cela a un impact important.

Le verrou de mise à jour prend un verrou de mise à jour sur la ligne, une mise à jour d'intention sur la page et un verrou partagé sur la table/base de données.

Cela n'empêche pas d'autres requêtes d'accéder aux données de la table, car les verrous de la page/base de données sont des verrous de partage purement. Ils peuvent simplement ne pas heurter les verrous contre la ligne/page/table individuelle en tentant d'effectuer une opération qui serait en contradiction avec les verrous. Si cela se produisait, la demande ferait la queue derrière les verrous actuels et attendrait qu'elle soit disponible avant de pouvoir continuer.

En utilisant le verrou, la requête est forcée d'être sérialisée, verrouillant la table exclusivement jusqu'à ce que l'action soit terminée. Cela empêche quiconque de lire le tableau à moins que l'indicateur nolock ne soit utilisé, ce qui permet une lecture potentiellement sale.

Pour voir l'effet, générez un exemple de table 'foo' et mettez-y des données de corbeille.

begin tran

select * from foo with (updlock)
where tableid = 1
-- notice there is no commit tran

Ouvrez une autre fenêtre et essayez:

select * from foo

Les lignes reviennent, maintenant valident la transaction de requête d'origine. Réexécutez-le modifié pour utiliser également le verrou:

begin tran

select * from foo with (updlock, holdlock)
where tableid = 1

Revenez à l'autre fenêtre et essayez de sélectionner à nouveau les données, la requête ne renverra pas de valeurs car elle est bloquée par le verrou exclusif. Validez la transaction dans la première fenêtre et les résultats de la deuxième requête apparaîtront car elle n'est plus bloquée.

Le test final consiste à utiliser le nolock, à réexécuter la transaction en utilisant updlock et holdlock. puis exécutez la commande suivante dans la deuxième fenêtre:

select * from foo (nolock)

Les résultats reviendront automatiquement, puisque vous avez accepté le risque d'une lecture sale (lecture non validée).

Donc, cela semble avoir un impact important, dans la mesure où vous forcez des actions contre cette table à être sérialisées, ce qui pourrait être ce que vous voulez (selon la mise à jour effectuée) ou créera un très gros goulot d'étranglement sur cette table. Si tout le monde faisait cela à une table occupée avec des transactions de longue durée, cela entraînerait des retards importants dans une application.

Comme pour toutes les fonctionnalités SQL, lorsqu'elles sont utilisées correctement, elles peuvent être puissantes, mais une mauvaise utilisation d'une fonctionnalité/d'un indice peut entraîner des problèmes importants. Je préfère utiliser des astuces en dernier recours lorsque je dois remplacer le moteur - pas comme approche par défaut.

Modifier comme demandé: testé dans SQL 2005, 2008, 2008R2 (toutes les entreprises) - tous installés sur à peu près les paramètres par défaut, base de données de test créée en utilisant toutes les valeurs par défaut (juste entré le nom de la base de données uniquement).

63
Andrew

La réponse d'Andrew est correcte selon la documentation MSDN, cependant j'ai testé par rapport à 2008R2 et 2012 et je ne vois pas ce comportement alors veuillez vous tester

Le comportement que je vois est le suivant:

Exécutez d'abord ceci sur une base de données de jeu.

CREATE TABLE [dbo].[foo](
    [tableid] [int] IDENTITY(1,1) NOT NULL,
    [Col2] [varchar](100) NOT NULL,
    CONSTRAINT [PK_foo] PRIMARY KEY CLUSTERED 
    (
        [tableid] ASC
    )
)

... et mettez quelques rangées dedans.

Collez maintenant ce code dans deux onglets de requête (modifiez le texte de l'onglet un dans l'onglet deux):

begin tran

select * from foo with (UPDLOCK, HOLDLOCK)
where tableid = 1

UPDATE foo SET Col2 = 'tab one'
where tableid = 1

commit tran

Et mettez cela dans un autre onglet:

select * from foo
where tableid = 1
  1. Assurez-vous de pointer votre base de données de jeu où se trouve la table.

  2. Mettez en surbrillance tout AVANT l'instruction de mise à jour dans onglet 1 et exécutez.

  3. Faites de même dans onglet 2 vous constaterez que l'onglet 2 NE sera PAS terminé et est toujours en cours d'exécution.

  4. Maintenant, exécutez le simple SELECT dans ongletdans mon environnement, il se termine.

  5. Mettez en surbrillance l'instruction de mise à jour dans onglet 1 et exécutez-la (ne faites pas encore la validation), vous verrez que l'onglet 2 est ENCORE en cours d'exécution.

  6. Allez-y et exécutez la validation dans tab 1 ...tab 2 va maintenant terminer la sélection ... vous pouvez exécuter le reste.

13
Darren