2010-09-14 5 views
7

Lors de l'exécution d'une instruction SELECT avec JOIN de deux tables, SQL Server semble verrouiller individuellement les deux tables de l'instruction. Par exemple par une requête comme ceci:Blocage provoqué par l'instruction SELECT JOIN avec SQL Server

SELECT ... 
FROM 
    table1 
    LEFT JOIN table2 
     ON table1.id = table2.id 
    WHERE ... 

J'ai découvert que l'ordre des serrures dépend de la condition WHERE. L'optimiseur de requête essaie de produire un plan d'exécution qui lit uniquement autant de lignes que nécessaire. Donc, si la condition WHERE contient une colonne de table1 , elle obtiendra d'abord les lignes de résultat de table1 et obtiendra ensuite les lignes correspondantes à partir de table2. Si la colonne provient de table2, elle le fera dans l'autre sens round. Conditions plus complexes ou l'utilisation d'index peuvent avoir un effet sur la décision de l'optimiseur de requête aussi.

Lorsque les données lues par une déclaration devrait être mis à jour plus tard dans la transaction avec UPDATE il est pas garanti que l'ordre des instructions UPDATE correspond à l'ordre qui a été utilisé pour lire les données des 2 tables. Si une autre transaction tente de lire des données alors qu'une transaction met à jour les tables , cela peut provoquer un blocage lorsque l'instruction SELECT est exécutée en entre les instructions UPDATE car ni le SELECT ne peut obtenir le verrou la première table, ni la mise à jour prenez le verrou sur la deuxième table. Pour exemple:

T1: SELECT ... FROM ... JOIN ... 
T1: UPDATE table1 SET ... WHERE id = ? 
T2: SELECT ... FROM ... JOIN ... (locks table2, then blocked by lock on table1) 
T1: UPDATE table2 SET ... WHERE id = ? 

Les deux tableaux représentent une hiérarchie de type et sont toujours chargés ensemble. Donc il est logique de charger un objet en utilisant un SELECT avec un JOIN. Le chargement des deux tables individuellement ne donnerait pas à l'optimiseur de requête une chance de trouver le meilleur plan d'exécution . Mais puisque les instructions UPDATE ne peuvent mettre à jour qu'une seule table à une heure , cela peut provoquer des blocages lorsqu'un objet est chargé alors que l'objet est mis à jour par une autre transaction. Les mises à jour d'objets provoquent souvent UPDATE sur les deux tables lorsque les propriétés de l'objet qui appartiennent à différents types de la hiérarchie de type sont mises à jour.

J'ai essayé d'ajouter des indicateurs de verrouillage à l'instruction SELECT, mais cela ne change pas le problème. Il provoque simplement le blocage dans les instructions SELECT lorsque les deux instructions tentent de verrouiller les tables et une instruction SELECT obtient le verrou dans l'ordre inverse de l'autre instruction. Peut-être qu'il serait possible de charger les données pour les mises à jour toujours avec la même instruction forçant les serrures à dans le même ordre. Cela empêcherait un blocage entre deux transactions qui veulent mettre à jour les données, mais n'empêcherait pas une transaction qui lit uniquement les données dans un interblocage qui doit avoir des conditions WHERE différentes.

Le seul work-a-round donc cela semble être que les lectures peuvent ne pas avoir de verrous du tout. Avec SQL Server 2005, cela peut être fait en utilisant SNAPSHOT ISOLATION. Le seul moyen pour SQL Server 2000 serait d'utiliser le niveau d'isolement READ UNCOMMITED .

Je voudrais savoir s'il existe une autre possibilité pour empêcher le SQL Server de causer ces interblocages?

+0

niveau d'isolement de capture instantanée? –

+0

Si c'est une question, la réponse est non. Cela se produit avec tous les niveaux d'isolation, sauf peut-être LIRE NON CONVENU que je n'ai pas testé, car je ne veux pas que les transactions puissent lire les données à moitié mises à jour. – Reboot

Répondre

5

Cela ne se produira jamais avec l'isolation d'instantané, lorsque les lecteurs ne bloquent pas les rédacteurs. À part cela, il n'y a aucun moyen d'empêcher de telles choses. J'ai écrit beaucoup de scripts repro ici: Reproducing deadlocks involving only one table

Edit:

Je n'ai pas accès à SQL 2000, mais je voudrais essayer de sérialiser l'accès à l'objet en utilisant sp_getapplock, de sorte que la lecture et les modifications ne courir simultanément. Si vous ne pouvez pas utiliser sp_getapplock, déployez votre propre mutex.

+0

+1 pour la recherche de l'impasse –

+0

La réponse m'a aidé à trouver une solition avec SQL Server 2005 ou plus récent. Mais le logiciel est toujours utilisé avec SQL Server 2000, je peux probablement implémenter des solutions différentes pour les deux versions, donc une solution qui fonctionne pour toutes les versions ou une solution différente pour SQL Server 2000 serait appréciée. – Reboot

-1

Je faisais face au même problème. L'utilisation de l'indice de requête FORCE ORDER résoudra ce problème. L'inconvénient est que vous ne serez pas en mesure d'exploiter le meilleur plan que l'optimiseur de requête a pour votre requête, mais cela permettra d'éviter le blocage. Donc (ceci provient de l'utilisateur de "Bill the Lizard") si vous avez une requête FROM table1 LEFT JOIN table2 et que votre clause WHERE ne contient que des colonnes de table2, le plan d'exécution sélectionnera normalement les lignes de table2 puis cherchera les lignes de table1. Avec un petit jeu de résultats de table2 seulement quelques lignes de table1 doivent être récupérées. Avec FORCE ORDER d'abord toutes les lignes de table1 doivent être récupérées, parce qu'il n'a pas de clause WHERE, puis les lignes de table2 sont jointes et le résultat est filtré à l'aide de la clause WHERE. Cela dégrade les performances.

Mais si vous savez que ce ne sera pas le cas, utilisez ceci. Vous souhaiterez peut-être optimiser la requête manuellement.

La syntaxe est

SELECT ... 
FROM 
    table1 
    LEFT JOIN table2 
     ON table1.id = table2.id 
    WHERE ... 
OPTION (FORCE ORDER) 
+0

En fait, j'ai écrit le commentaire sur la performance, semble Stack Overflow a gâché votre réponse et mon commentaire, je me demandais déjà pourquoi il a disparu. Le problème n'est pas seulement la performance avec FORCE ORDER, c'est aussi le verrouillage. Si vous utilisez FORCE ORDER et que le plan d'exécution lit toutes les lignes de table1, il leur attribue également un verrou partagé. Donc, fondamentalement, toute requête n'utilisant que des colonnes de table2 verrouillera toute la table1 rendant toute mise à jour impossible. Ce n'est pas une seule requête qui doit fonctionner, le SELECT doit fonctionner avec n'importe quelle clause WHERE. – Reboot

+0

Donc, vous ne voulez pas verrouiller table1, tout en étant en mesure de rejoindre avec table2? Dans mon cas, j'avais des inserts qui se passaient dans table1 puis dans table2.Et l'optimiseur de requêtes sélectionnait l'ordre inverse en cas de jointure. Donc, l'impasse. L'ajout de FORCE ORDER ne résout donc aucun blocage. Notez ici que je veux toujours lire le verrou sur table1. Les inserts peuvent attendre jusqu'à la fin de la sélection. Mais il n'y aura pas d'impasse. – Ankush

+0

Aucun verrou n'est bon sur table1, mais avec FORCE ORDER il verrouille toutes les lignes de la table au lieu de seulement les lignes qui doivent être jointes avec les lignes de table2. L'allocation de verrous pour toutes les lignes peut également devenir un problème, car c'est une ressource que les processus doivent allouer. Si le SQL Server exécute notre de mémoire pour les verrous, les processus peuvent bloquer l'allocation de mémoire. – Reboot

0

Une autre façon de résoudre ce problème est de diviser la sélection ... de ... rejoindre dans plusieurs instructions select. Définir le niveau d'isolement à lire commis. Utilisez la variable table pour canaliser les données de select à joindre à other. Utilisez distinct pour filtrer les insertions dans ces variables de table. Donc, si j'ai deux tables A, B. J'insère/mise à jour dans A, puis B. Où l'optimiseur de requête de sql préfère lire B d'abord et A. Je vais séparer le single select en 2 sélectionne. Je vais d'abord lire B. Ensuite, transmettre ces données à l'instruction select suivante qui lit A.

Ici, un interblocage ne se produira pas car les verrous de lecture sur la table B seront libérés dès que la première instruction est terminée. PS J'ai rencontré ce problème et cela a très bien fonctionné. Beaucoup mieux que ma réponse de commande de force.