Comment verrouiller les tables dans SQL Server 2005, et dois-je même le faire ?

StackOverflow https://stackoverflow.com/questions/57625

  •  09-06-2019
  •  | 
  •  

Question

Celui-ci nécessitera quelques explications.Ce que j'ai fait, c'est créer une file d'attente de messages personnalisée spécifique dans SQL Server 2005.J'ai un tableau avec des messages contenant des horodatages pour l'accusé de réception et l'achèvement.La procédure stockée que les appelants exécutent pour obtenir le message suivant dans leur file d'attente reconnaît également le message.Jusqu'ici, tout va bien.Eh bien, si le système subit un nombre massif de transactions (des milliers par minute), n'est-il pas possible qu'un message soit reconnu par une autre exécution de la procédure stockée pendant qu'une autre est prête à le faire elle-même ?Laissez-moi vous aider en affichant mon code SQL dans le processus stocké :

--Grab the next message id
declare @MessageId uniqueidentifier
set @MessageId = (select top(1) ActionMessageId from UnacknowledgedDemands);

--Acknowledge the message
update ActionMessages
set AcknowledgedTime = getdate()
where ActionMessageId = @MessageId

--Select the entire message
...
...

Dans le code ci-dessus, une autre procédure stockée exécutée en même temps ne pourrait-elle pas obtenir le même identifiant et tenter de l'accuser de réception en même temps ?Pourrais-je (ou devrais-je) implémenter une sorte de verrouillage pour empêcher un autre processus stocké d'accuser réception des messages qu'un autre processus stocké interroge ?

Wow, est-ce que tout cela avait un sens ?C'est un peu difficile à exprimer avec des mots...

Était-ce utile?

La solution

Quelque chose comme ça

--Grab the next message id
begin tran
declare @MessageId uniqueidentifier
select top 1 @MessageId =   ActionMessageId from UnacknowledgedDemands with(holdlock, updlock);

--Acknowledge the message
update ActionMessages
set AcknowledgedTime = getdate()
where ActionMessageId = @MessageId

-- some error checking
commit tran

--Select the entire message
...
...

Autres conseils

Cela ressemble au genre de situation dans laquelle OUTPUT peut être utile:

-- Acknowledge and grab the next message
declare @message table (
    -- ...your `ActionMessages` columns here...
)
update ActionMessages
set    AcknowledgedTime = getdate()
output INSERTED.* into @message
where  ActionMessageId in (select top(1) ActionMessageId from UnacknowledgedDemands)
  and  AcknowledgedTime is null

-- Use the data in @message, which will have zero or one rows assuming
-- `ActionMessageId` uniquely identifies a row (strongly implied in your question)
...
...

Là, nous mettons à jour et récupérons la ligne dans la même opération, ce qui indique à l'optimiseur de requêtes exactement ce que nous faisons, lui permettant de choisir le verrou le plus granulaire possible et de le maintenir pendant la durée la plus brève possible.(Bien que le préfixe de la colonne soit INSERTED, OUTPUT est comme des déclencheurs, exprimés en termes de UPDATE c'est comme supprimer la ligne et insérer la nouvelle.)

J'aurais besoin de plus d'informations sur votre ActionMessages et UnacknowledgedDemands tables (vues/TVF/peu importe), sans parler d'une meilleure connaissance du verrouillage automatique de SQL Server, pour dire si cela and AcknowledgedTime is null une clause est nécessaire.Il est là pour se défendre contre une condition de concurrence entre la sous-sélection et la mise à jour.Je suis certain que cela ne serait pas nécessaire si nous choisissions parmi ActionMessages lui-même (par exemple, where AcknowledgedTime is null avec un top sur le update, au lieu de la sous-sélection sur UnacknowledgedDemands).Je pense que même si c'est inutile, c'est inoffensif.

Noter que OUTPUT est dans SQL Server 2005 et supérieur.C'est ce que vous avez dit utiliser, mais si la compatibilité avec les installations gériatriques de SQL Server 2000 était requise, vous voudriez suivre une autre voie.

@Kilhoffer :

L'ensemble du lot SQL est analysé avant l'exécution, afin que SQL sache que vous allez mettre à jour la table et en faire une sélection.

Modifier:De plus, SQL ne verrouillera pas nécessairement la table entière – il pourrait simplement verrouiller les lignes nécessaires.Voir ici pour un aperçu du verrouillage dans SQL Server.

Au lieu d'un verrouillage explicite, qui est souvent élevé par SQL Server vers une granularité plus élevée que souhaitée, pourquoi ne pas simplement essayer cette approche :

declare @MessageId uniqueidentifier
select top 1 @MessageId = ActionMessageId from UnacknowledgedDemands

update ActionMessages
  set AcknowledgedTime = getdate()
  where ActionMessageId = @MessageId and AcknowledgedTime is null

if @@rowcount > 0
  /* acknoweldge succeeded */
else
  /* concurrent query acknowledged message before us,
     go back and try another one */

Moins vous verrouillez, plus la concurrence est élevée.

Faut-il vraiment traiter les choses une par une ?Ne devriez-vous pas simplement demander à SQL Server d'accuser réception de tous les messages non reconnus avec la date d'aujourd'hui et de les renvoyer ?(le tout aussi dans une transaction bien sûr)

En savoir plus sur le verrouillage de sélection SQL Server ici et ici.SQL Server a la possibilité d'invoquer un verrou de table sur une sélection.Rien n'arrivera à la table pendant la transaction.Une fois la transaction terminée, toutes les insertions ou mises à jour seront alors résolues d'elles-mêmes.

Vous souhaitez envelopper votre code dans une transaction, puis le serveur SQL gérera le verrouillage des lignes ou des tables appropriées.

begin transaction

--Grab the next message id
declare @MessageId uniqueidentifier
set @MessageId = (select top(1) ActionMessageId from UnacknowledgedDemands);

--Acknowledge the message
update ActionMessages
set AcknowledgedTime = getdate()
where ActionMessageId = @MessageId

commit transaction

--Select the entire message
...
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top