web-dev-qa-db-fra.com

Quelles sont les principales causes de blocage et peuvent-elles être évitées?

Récemment, l'une de nos applications ASP.NET a affiché une erreur de blocage de la base de données et on m'a demandé de vérifier et de corriger l'erreur. J'ai réussi à trouver que la cause du blocage était une procédure stockée qui mettait à jour rigoureusement une table dans un curseur.

C'est la première fois que je vois cette erreur et je ne savais pas comment la suivre et la corriger efficacement. J'ai essayé toutes les façons possibles que je connais et j'ai finalement trouvé que la table qui était en train d'être mise à jour n'avait pas de clé primaire! heureusement, c'était une colonne d'identité.

Plus tard, j'ai trouvé le développeur qui a scripté la base de données pour le déploiement foiré. J'ai ajouté une clé primaire et le problème a été résolu.

Je me suis sentie heureuse et suis revenue à mon projet, et j'ai fait des recherches pour trouver la raison de cette impasse ...

Apparemment, c'est une condition d'attente circulaire qui a provoqué l'impasse. Les mises à jour prennent apparemment plus de temps sans clé primaire qu'avec clé primaire.

Je sais que ce n'est pas une conclusion bien définie, c'est pourquoi je poste ici ...

  • La clé primaire manquante est-elle le problème?
  • Y a-t-il d'autres conditions qui provoquent une impasse autre que (exclusion mutuelle, attente et attente, pas de préemption et attente circulaire)?
  • Comment prévenir et suivre les blocages?
57
CoderHawk

le suivi des blocages est le plus simple des deux:

Par défaut, les blocages ne sont pas écrits dans le journal des erreurs. Vous pouvez provoquer SQL pour écrire des blocages dans le journal des erreurs avec les indicateurs de trace 1204 et 3605.

Écrire des informations de blocage dans le journal des erreurs SQL Server: DBCC TRACEON (-1, 1204, 3605)

Désactivez-le: DBCC TRACEOFF (-1, 1204, 3605)

Voir "Dépannage des blocages" pour une discussion sur l'indicateur de trace 1204 et la sortie que vous obtiendrez lorsqu'il sera activé. https://msdn.Microsoft.com/en-us/library/ms178104.aspx

La prévention est plus difficile, vous devez essentiellement faire attention aux éléments suivants:

Le bloc de code 1 verrouille la ressource A, puis la ressource B, dans cet ordre.

Le bloc de code 2 verrouille la ressource B, puis la ressource A, dans cet ordre.

C'est la condition classique où un blocage peut se produire, si le verrouillage des deux ressources n'est pas atomique, le bloc de code 1 peut verrouiller A et être préempté, puis le bloc de code 2 verrouille B avant que A ne récupère le temps de traitement. Vous avez maintenant une impasse.

Pour éviter cette condition, vous pouvez faire quelque chose comme ce qui suit

Bloc de code A (code pseudo)

Lock Shared Resource Z
    Lock Resource A
    Lock Resource B
Unlock Shared Resource Z
...

Bloc de code B (pseudo-code)

Lock Shared Resource Z
    Lock Resource B
    Lock Resource A
Unlock Shared Resource Z
...

sans oublier de déverrouiller A et B lorsque vous en avez terminé avec eux

cela empêcherait le blocage entre le bloc de code A et le bloc de code B

Du point de vue de la base de données, je ne sais pas comment éviter cette situation, car les verrous sont gérés par la base de données elle-même, c'est-à-dire les verrous de ligne/table lors de la mise à jour des données. Là où j'ai vu le plus de problèmes, c'est là que vous avez vu le vôtre, à l'intérieur d'un curseur. Les curseurs sont notoirement inefficaces, évitez-les si possible.

39
BlackICE

mes articles préférés pour lire et en savoir plus sur les blocages sont: Simple Talk - Traquer les blocages et SQL Server Central - Utilisation de Profiler pour résoudre les blocages . Ils vous donneront des échantillons et des conseils sur la façon de gérer la situation.

En bref, pour résoudre un problème actuel, je raccourcirais les transactions, en retirerais la partie inutile, prendrais soin de l'ordre d'utilisation des objets, verrais quel niveau d'isolement est réellement nécessaire, ne lirais pas inutile Les données...

Mais mieux lire les articles, ils seront bien plus agréables dans les conseils.

24
Marian

Parfois, un blocage peut être résolu en ajoutant l'indexation, car cela permet à la base de données de verrouiller des enregistrements individuels plutôt que la table entière, de sorte que vous réduisez les conflits et la possibilité de blocage.

Par exemple, dans InnoDB :

Si vous n'avez aucun index adapté à votre instruction et que MySQL doit analyser l'intégralité de la table pour traiter l'instruction, chaque ligne de la table est verrouillée, ce qui bloque à son tour toutes les insertions d'autres utilisateurs dans la table. Il est important de créer de bons index afin que vos requêtes n'analysent pas inutilement de nombreuses lignes.

Une autre solution courante consiste à désactiver la cohérence transactionnelle lorsqu'elle n'est pas nécessaire, ou à modifier autrement votre niveau d'isolement , par exemple, un travail de longue durée pour calculer des statistiques ... une réponse proche suffit généralement, vous n'ont pas besoin de chiffres précis, car ils changent de dessous vous. Et si cela prend 30 minutes, vous ne voulez pas qu'il arrête toutes les autres transactions sur ces tables.

...

Quant à leur suivi, cela dépend du logiciel de base de données que vous utilisez.

16
Joe

Juste pour développer le curseur. c'est vraiment vraiment mauvais. Il verrouille la table entière puis traite les lignes une par une.

Il est préférable de parcourir les lignes à la manière d'un curseur en utilisant une boucle while

Dans la boucle while, une sélection sera effectuée pour chaque ligne de la boucle et le verrouillage se produira sur une seule ligne à la fois. Le reste des données de la table est libre pour les requêtes, ce qui réduit les risques de blocage.

De plus, c'est plus rapide. Vous fait vous demander pourquoi il y a des curseurs de toute façon.

Voici un exemple de ce type de structure:

DECLARE @LastID INT = (SELECT MAX(ID) FROM Tbl)
DECLARE @ID     INT = (SELECT MIN(ID) FROM Tbl)
WHILE @ID <= @LastID
    BEGIN
    IF EXISTS (SELECT * FROM Tbl WHERE ID = @ID)
        BEGIN
        -- Do something to this row of the table
        END

    SET @ID += 1  -- Don't forget this part!
    END

Si votre champ d'ID est clairsemé, vous souhaiterez peut-être extraire une liste distincte d'ID et répéter cela:

DECLARE @IDs TABLE
    (
    Seq INT NOT NULL IDENTITY PRIMARY KEY,
    ID  INT NOT NULL
    )
INSERT INTO @IDs (ID)
    SELECT ID
    FROM Tbl
    WHERE 1=1  -- Criteria here

DECLARE @Rec     INT = 1
DECLARE @NumRecs INT = (SELECT MAX(Seq) FROM @IDs)
DECLARE @ID      INT
WHILE @Rec <= @NumRecs
    BEGIN
    SET @ID = (SELECT ID FROM @IDs WHERE Seq = @Seq)

    -- Do something to this row of the table

    SET @Seq += 1  -- Don't forget this part!
    END
7

Il manque pas le problème. Au moins en soi. Tout d'abord, vous n'avez pas besoin d'un primaire pour avoir des index. Deuxièmement, même si vous effectuez des analyses de table (ce qui doit se produire si votre requête particulière n'utilise pas d'index, un verrou de table ne provoquera pas en lui-même un blocage. Un processus d'écriture attendra une lecture et un processus de lecture attendez une écriture, et bien sûr les lectures n'auront pas du tout à s'attendre.

Pour ajouter aux autres réponses, le niveau d'isolement des transactions est important, car la lecture répétable et la sérialisation sont à l'origine du maintien des verrous en lecture jusqu'à la fin de la transaction. Le verrouillage d'une ressource ne provoque pas de blocage. Le garder verrouillé le fait. Les opérations d'écriture gardent toujours leur ressource verrouillée jusqu'à la fin de la transaction.

Ma stratégie de prévention des verrous préférée utilise les fonctionnalités de "capture instantanée". La fonction Read Committed Snapshot signifie que les lectures n'utilisent pas de verrous! Et si vous avez besoin de plus de contrôle que "Lecture validée", il existe la fonction "Niveau d'isolement de capture instantanée". Celui-ci permet à une transaction sérialisée (en utilisant les termes MS ici) de se produire sans bloquer les autres joueurs.

Enfin, une classe de blocages peut être évitée en utilisant un verrou de mise à jour. Si vous lisez et maintenez la lecture (HOLD ou en utilisant la lecture répétable), et qu'un autre processus fait de même, alors les deux essaient de mettre à jour les mêmes enregistrements, vous aurez un blocage. Mais si les deux demandent un verrou de mise à jour, le deuxième processus attendra le premier, tout en permettant à d'autres processus de lire les données à l'aide de verrous partagés jusqu'à ce que les données soient réellement écrites. Bien sûr, cela ne fonctionnera pas si l'un des processus demande toujours un verrou HOLD partagé.

6
Gerard ONeill