web-dev-qa-db-fra.com

SELECT FOR UPDATE avec SQL Server

J'utilise une base de données Microsoft SQL Server 2005 avec un niveau d'isolation READ_COMMITTED et READ_COMMITTED_SNAPSHOT=ON.

Maintenant je veux utiliser:

SELECT * FROM <tablename> FOR UPDATE

... afin que les autres connexions de base de données se bloquent lors de la tentative d'accès à la même ligne "FOR UPDATE".

J'ai essayé:

SELECT * FROM <tablename> WITH (updlock) WHERE id=1

... mais cela bloque toutes les autres connexions même pour la sélection d'un identifiant autre que "1".

Quel est le bon conseil pour faire un SELECT FOR UPDATE comme connu pour Oracle, DB2, MySql?

EDIT 2009-10-03:

Voici les instructions pour créer la table et l'index:

CREATE TABLE example ( Id BIGINT NOT NULL, TransactionId BIGINT, 
    Terminal BIGINT, Status SMALLINT );
ALTER TABLE example ADD CONSTRAINT index108 PRIMARY KEY ( Id )
CREATE INDEX I108_FkTerminal ON example ( Terminal )
CREATE INDEX I108_Key ON example ( TransactionId )

Beaucoup de processus parallèles font cela SELECT:

SELECT * FROM example o WITH (updlock) WHERE o.TransactionId = ?

EDIT 2009-10-05:

Pour une meilleure vue d'ensemble, j'ai noté toutes les solutions essayées dans le tableau suivant:

mécanisme 
 | SELECT sur différents blocs de lignes | SELECT sur les mêmes blocs de lignes 
 ----------------------- + ---------------- ---------------- + -------------------------- 
 ROWLOCK | non | non 
 updlock, rowlock | oui | oui 
 xlock, rowlock | oui | oui 
 répétable, lire | non | non 
 DBCC TRACEON (1211, -1) | oui | oui 
 rowlock, xlock, holdlock | oui | oui 
 updlock, holdlock | oui | oui 
 UPDLOCK, READPAST | non | non 
 
 Je recherche | non | Oui
74
tangens

Récemment, j'ai eu un problème de blocage car Sql Server verrouille plus que nécessaire (page). Vous ne pouvez rien faire contre cela. Nous avons maintenant des exceptions d'interblocage ... et j'aimerais avoir Oracle à la place.

Edit: Nous utilisons entre-temps l’isolation de capture instantanée, qui résout beaucoup de problèmes, mais pas tous. Malheureusement, pour pouvoir utiliser l'isolation d'instantané, il doit être autorisé par le serveur de base de données, ce qui peut entraîner des problèmes inutiles sur le site du client. Désormais, nous ne détectons pas seulement les exceptions d'interblocage (qui peuvent toujours se produire, bien sûr), mais également des problèmes de concurrence d'accès instantané pour répéter des transactions à partir de processus en arrière-plan (qui ne peuvent pas être répétés par l'utilisateur). Mais cela fonctionne toujours beaucoup mieux qu'avant.

33
Stefan Steinegger

J'ai un problème similaire, je veux verrouiller une seule ligne. Autant que je sache, avec l'option UPDLOCK, SQLSERVER verrouille toutes les lignes qu'il doit lire pour obtenir la ligne. Ainsi, si vous ne définissez pas d'index pour accéder directement à la ligne, toutes les lignes précédentes seront verrouillées. Dans votre exemple:

Supposons que vous ayez une table nommée TBL avec un champ id. Vous voulez verrouiller la ligne avec id=10. Vous devez définir un index pour l'identifiant du champ (ou tout autre champ impliqué dans la sélection):

CREATE INDEX TBLINDEX ON TBL ( id )

Et ensuite, votre requête pour verrouiller UNIQUEMENT les lignes que vous lisez est la suivante:

SELECT * FROM TBL WITH (UPDLOCK, INDEX(TBLINDEX)) WHERE id=10.

Si vous n'utilisez pas l'option INDEX (TBLINDEX), SQLSERVER doit lire toutes les lignes du début de la table pour rechercher votre ligne avec id=10, ces lignes seront donc verrouillées.

19
ManuelConde

Vous ne pouvez pas avoir l'isolation de capture instantanée et le blocage de lectures en même temps. L’isolement d’images instantanées a pour but de empêcher le blocage des lectures.

7
Christian Hayter

peut-être que rendre mvcc permanent pourrait résoudre le problème (par opposition à un lot spécifique uniquement: SET INSTANTANÉ DE NIVEAU D'ISOLATION DE TRANSACTION):

ALTER DATABASE yourDbNameHere SET READ_COMMITTED_SNAPSHOT ON;

[EDIT: 14 octobre]

Après avoir lu ceci: Meilleure concurrence dans Oracle que SQL Server? et ceci: http://msdn.Microsoft.com/en-us/library/ms175095.aspx

Lorsque l'option de base de données READ_COMMITTED_SNAPSHOT est définie sur ON, les mécanismes utilisés pour prendre en charge l'option sont immédiatement activés. Lorsque vous définissez l'option READ_COMMITTED_SNAPSHOT, seule la connexion qui exécute la commande ALTER DATABASE est autorisée dans la base de données. Il ne doit y avoir aucune autre connexion ouverte dans la base de données jusqu'à ce que ALTER DATABASE soit terminé. La base de données ne doit pas nécessairement être en mode mono-utilisateur.

je suis arrivé à la conclusion que vous devez définir deux drapeaux afin d'activer MVCC de mssql de manière permanente sur une base de données donnée:

ALTER DATABASE yourDbNameHere SET ALLOW_SNAPSHOT_ISOLATION ON;
ALTER DATABASE yourDbNameHere SET READ_COMMITTED_SNAPSHOT ON;
5
Michael Buen

Essayez (updlock, rowlock)

5
BlueMonkMN

La réponse complète pourrait approfondir les aspects internes du SGBD. Cela dépend de la manière dont le moteur de requête (qui exécute le plan de requête généré par l'optimiseur SQL) fonctionne.

Cependant, une explication possible (applicable à au moins certaines versions de certains SGBD - pas nécessairement à MS SQL Server) est qu’il n’existe pas d’index dans la colonne ID. Par conséquent, tout processus essayant de traiter une requête avec 'WHERE id = ? 'in finit par effectuer une analyse séquentielle de la table, et cette analyse séquentielle frappe le verrou que votre processus a appliqué. Vous pouvez également rencontrer des problèmes si le SGBD applique le verrouillage au niveau de la page par défaut; le verrouillage d'une ligne verrouille la page entière et toutes les lignes de cette page.

Il y a des façons de réduire cela en tant que source de problèmes. Regardez le plan de requête; étudier les index; Essayez votre SELECT avec l'ID de 1000000 au lieu de 1 et voyez si d'autres processus sont toujours bloqués.

5
Jonathan Leffler

OK, une seule sélection utilisera par défaut l’isolation de transaction "Read Committed" qui se verrouille et arrête donc les écritures dans cet ensemble. Vous pouvez modifier le niveau d'isolation de transaction avec

Set Transaction Isolation Level { Read Uncommitted | Read Committed | Repeatable Read | Serializable }
Begin Tran
  Select ...
Commit Tran

Celles-ci sont expliquées en détail dans SQL Server BOL

Votre problème suivant est que, par défaut, SQL Server 2K5 augmentera les verrous si vous avez plus de 2 500 verrous ou si vous utilisez plus de 40% de la mémoire "normale" dans la transaction de verrouillage. L'escalade passe à la page, puis au verrou de la table

Vous pouvez désactiver cette escalade en définissant "indicateur de trace" 1211t, voir BOL pour plus d'informations.

3
TFD

Les verrous d’application sont un moyen de mettre en œuvre votre propre verrouillage avec une granularité personnalisée tout en évitant une escalade "utile" du verrouillage. Voir sp_getapplock .

2
Constantin

Créez une fausse mise à jour pour appliquer le verrouillage de ligne.

UPDATE <tablename> (ROWLOCK) SET <somecolumn> = <somecolumn> WHERE id=1

Si cela ne verrouille pas votre rangée, dieu sait ce qui se passera.

Après cette "UPDATE" vous pouvez faire votre SELECT (ROWLOCK) et les mises à jour suivantes.

2
Feu

Je suppose que vous ne voulez pas qu'une autre session puisse lire la ligne tant que cette requête est en cours d'exécution ...

Enveloppant votre SELECT dans une transaction en utilisant WITH (XLOCK, READPAST), le conseil de verrouillage obtiendra les résultats souhaités. Assurez-vous simplement que ces autres lectures simultanées n'utilisent PAS WITH (NOLOCK). READPAST permet à d’autres sessions d’effectuer le même SELECT mais sur d’autres lignes.

BEGIN TRAN
  SELECT *
  FROM <tablename> WITH (XLOCK,READPAST) 
  WHERE RowId = @SomeId

  -- Do SOMETHING

  UPDATE <tablename>
  SET <column>=@somevalue
  WHERE RowId=@SomeId
COMMIT
2
ewoo

Essayez d'utiliser:

SELECT * FROM <tablename> WITH ROWLOCK XLOCK HOLDLOCK

Cela devrait rendre le verrou exclusif et le conserver pendant toute la durée de la transaction.

1
RMorrisey

J'ai résolu le problème du locklock d'une manière complètement différente. J'ai réalisé que le serveur SQL n'était pas capable de gérer un tel verrou de manière satisfaisante. J'ai choisi de résoudre ce problème d'un point de vue programmatique en utilisant un mutex ... waitForLock ... releaseLock ...

1
jessn

Selon cet article , la solution consiste à utiliser l'indicateur WITH (REPEATABLEREAD).

1
erikkallen

Vous devez gérer l'exception au moment de la validation et répéter la transaction.

1
user205622

Question - s’il est prouvé que ce cas est le résultat d’une escalade de verrous (c’est-à-dire si vous tracez avec le profileur des événements d’escalade de verrous, c’est bien ce qui se produit pour provoquer le blocage)? Si tel est le cas, il existe une explication complète et une solution de contournement (plutôt extrême) en activant un indicateur de trace au niveau de l'instance pour empêcher la remontée des verrous. Voir http://support.Microsoft.com/kb/3236 Indicateur de suivi 1211

Mais, cela aura probablement des effets secondaires non souhaités.

Si vous verrouillez délibérément une ligne et que vous la maintenez verrouillée pendant une période prolongée, l'utilisation du mécanisme de verrouillage interne des transactions n'est pas la meilleure méthode (au moins dans SQL Server). Toute l'optimisation dans SQL Server est axée sur les transactions courtes: entrez, effectuez une mise à jour, sortez. C'est la raison de l'escalade de verrous en premier lieu.

Donc, si l'intention est de "vérifier" une ligne pendant une période prolongée, au lieu de verrouiller les transactions, il est préférable d'utiliser une colonne avec des valeurs et une instruction plain ol 'update pour marquer les lignes comme étant verrouillées ou non.

1
onupdatecascade

Revisitez toutes vos requêtes, vous avez peut-être une requête qui sélectionne sans indication ROWLOCK/FOR UPDATE de la même table que vous avez SELECT FOR UPDATE.


MSSQL transforme souvent ces verrous de ligne en verrous de niveau page (même les verrous de niveau table, si vous n'avez pas d'index sur le champ que vous interrogez), voyez ceci explication . Puisque vous demandez FOR UPDATE, je peux supposer que vous avez besoin d’une robustesse au niveau transacion (par exemple, financière, d'inventaire, etc.). Les conseils sur ce site ne sont donc pas applicables à votre problème. C'est juste un aperçu pourquoi MSSQL escalade les verrous .


Si vous utilisez déjà MSSQL 2005 (et les versions ultérieures), ils sont basés sur MVCC, je pense que vous ne devriez pas avoir de problème avec le verrouillage au niveau de la ligne à l'aide de l'indicateur ROWLOCK/UPDLOCK. Toutefois, si vous utilisez déjà MSSQL 2005 et versions ultérieures, essayez de vérifier certaines de vos requêtes qui interrogent la même table que vous souhaitez FOR UPDATE si elles remontent des verrous en vérifiant les champs de leur clause WHERE s'ils ont un index.


P.S.
J'utilise PostgreSQL, il utilise également MVCC FOR FOR UPDATE, je ne rencontre pas le même problème. MVCC résout le problème des escalades de verrous. Je serais donc surpris que MSSQL 2005 continue à escalader les verrous de la table avec des clauses WHERE qui n’ont pas d’index sur ses champs. Si cela (escalade de verrous) est toujours le cas pour MSSQL 2005, essayez de vérifier les champs des clauses WHERE s'ils ont un index.

Avertissement: ma dernière utilisation de MSSQL est la version 2000 uniquement.

1
Michael Buen

Avez-vous essayé READPAST?

J'ai utilisé UPDLOCK et READPAST ensemble pour traiter une table comme une file d'attente.

0
Gratzy

Pourquoi ne pas essayer de faire une simple mise à jour sur cette ligne en premier (sans vraiment changer les données)? Après cela, vous pouvez continuer avec la ligne telle que sélectionnée dans la mise à jour.

UPDATE dbo.Customer SET FieldForLock = FieldForLock WHERE CustomerID = @CustomerID
/* do whatever you want */

Edit: vous devriez l'envelopper dans une transaction bien sûr

Edit 2: une autre solution consiste à utiliser le niveau d'isolement SERIALIZABLE

0
Vladimir