Utiliser la base de données pour suivre le verrouillage / déverrouillage des objets

8

J'ai une obligation de suivre les actions de verrouillage / déverrouillage des objets. Avant toute action effectuée sur un objet (contrat, partenaire, etc.), un lockévénement est émis. Une fois l'action terminée, il émet l' unlockévénement.

Je veux obtenir ces objets qui sont verrouillés mais pas encore déverrouillés. Le but est de rendre la requête rapide et d'éviter les blocages.

Ci-dessous le tableau

create table locks (
    id int identity,
    name varchar(255),
    lock int
) 

insert into locks values('a', 1)
insert into locks values('b', 1)

insert into locks values('c', 1)
insert into locks values('d', 1)

insert into locks values('a', 0)
insert into locks values('c', 0)


insert into locks values('a', 1)
insert into locks values('b', 1)

J'utilise la requête ci-dessous pour objecter les objets non encore déverrouillés:

select distinct m.name from locks m
    where (select COUNT(id) from locks locked
           where locked.lock = 1 and locked.name = m.name)
        >  (select COUNT(id) from locks unlocked
            where unlocked.lock = 0 and unlocked.name = m.name)

Cela fonctionne correctement et le résultat a, bet d.

Mes questions sont les suivantes: - Ma solution est-elle suffisante pour éviter les blocages? Y a-t-il un problème qui peut se produire s'il y en a beaucoup INSERTpendant l'exécution de la requête? - Avez-vous un autre (meilleur) moyen de résoudre ce problème?

MISE À JOUR

Je m'excuse de ne pas avoir mis le contexte dans la question. La conception de la base de données ci-dessus n'est pas destinée à remplacer le verrouillage de la base de données.

Nous avons un système externe que nous appelons de notre système. Il nécessite d'appeler locket de unlockméthode sur leurs systèmes avant chaque action exécutée sur un objet (peut être un contrat ou un partenaire).

Récemment, nous avons des situations telles que le serveur plante et nous devons le redémarrer. Malheureusement, les processus en cours qui ont déjà appelé lockn'ont pas eu la possibilité d'appeler unlockpour libérer les objets, ce qui a conduit à plusieurs autres problèmes lorsque notre système se connecte à nouveau à l'extérieur.

Nous voulons donc fournir une capacité de suivre chaque lockappel. Au redémarrage du serveur, nous invoquerons unlockles objets précédemment verrouillés.

Merci à Remus Rusanu d'avoir souligné que ma question utilise un prototype DDL. C'est la première fois que je poste une question sur DBA et je m'excuse de ne pas avoir lu la FAQ.

Merci

Genzer
la source

Réponses:

11

Le but est de rendre la requête rapide et d'éviter les blocages.

Il s'agit d'un objectif irréaliste. Les interblocages sont déterminés par l'application qui acquiert les verrous et n'a aucun contrôle sur la façon dont vous implémentez le verrouillage. Le mieux que vous puissiez espérer est de détecter les blocages.

L'implémentation de verrous en tant qu'enregistrement est problématique. Les lignes persistent et vous perdrez des verrous en cas de plantage de l'application, même lorsque l'implémentation est parfaite . Faites des verrous avec des applocks . Ils ont une sémantique transactionnelle claire et des blocages sont détectés par le moteur.

Cependant, en regardant ce que vous essayez d'atteindre, il est peu probable que vous avez besoin serrures du tout . Vous décrivez une file d'attente (choisissez l'élément suivant disponible à traiter et évitez les conflits => file d'attente, pas de verrouillage). Lisez Utilisation de tables comme files d'attente .

Quant à votre implémentation spécifique (à l'aide d'une table qui conserve l' historique du verrouillage) je dois être honnête: c'est un désastre. Pour commencer, la conception de la table est complètement inadéquate pour l'utilisation prévue: vous interrogez par nom mais la table est un tas sans index. Il a une colonne d'identité sans raison apparente. Vous pouvez répondre que c'est juste une table de 'pseudo-code', mais c'est DBA.SE, vous ne postez pas ici DDL incomplet!

Mais plus important encore, l'implémentation n'implémente pas de verrouillage! Il n'y a rien pour empêcher deux utilisateurs de « verrouillage » du même objet deux fois. Votre «verrouillage» repose entièrement sur le fait que les appelants se comportent correctement comme par magie. Même une meilleure application écrite n'a pas pu utiliser ce verrouillage, car il n'y a aucun moyen de vérifier et d'acquérir le verrou de manière atomique . Deux utilisateurs peuvent vérifier, conclure que «a» est déverrouillé et insérer simultanément l' ('a', 1)enregistrement. À tout le moins, vous auriez besoin d'une contrainte unique. Ce qui, bien sûr, briserait la sémantique de «compter les verrous par rapport aux déverrouillages pour déterminer le statut».

Désolé de le dire, mais il s'agit d'une implémentation de grade F.

MISE À JOUR

Nous voulons donc fournir une capacité de suivre chaque appel de verrouillage. Au redémarrage du serveur, nous appellerons unlock sur les objets précédemment verrouillés.

Sans vous engager dans une transaction distribuée de validation en deux phases avec le système distant, tout ce que vous pouvez faire est un «meilleur effort», car il existe de nombreuses conditions de concurrence entre l'écriture du «déverrouillage» et l'appel réel du «déverrouillage» sur le système tiers . Au mieux, voici ma recommandation:

  • créer un tableau simple pour suivre les verrous:

    CREATE TABLE locks (name VARCHAR(255) NOT NULL PRIMARY KEY);

  • avant d'appeler, lockinsérez le verrou dans votre table et validez .

  • après avoir appelé, unlock supprimez le verrou de votre table et validez
  • au démarrage du système, regardez le tableau. dans chaque rangée, il y a un verrou restant lors d'un passage précédent et doit être «déverrouillé». Appelez unlockpour chaque ligne, puis supprimez la ligne. Ce n'est qu'après avoir déverrouillé tous les verrous en attente que vous pouvez reprendre la fonctionnalité normale de l'application.

Je propose cela parce que le tableau restera petit. À tout moment, il ne contient que des verrous actifs et actifs. Il ne poussera pas ad nauseam et causera des problèmes plus tard en raison de sa taille. Est trivial de voir ce qui est verrouillé.

Bien entendu, cette implémentation n'offre pas d'historique d'audit de ce qui a été verrouillé par qui et quand. Vous pouvez ajouter cela si nécessaire, en tant que table différente, dans laquelle vous insérez uniquement les événements (verrouiller ou déverrouiller) et ne jamais l'interroger pour déterminer les verrous «orphelins».

Vous devez toujours être prêt à ce que les appels à `` déverrouiller '' échouent au démarrage, car vous ne pouvez pas garantir que votre système ne s'est pas bloqué après l' appel unlockmais avant de supprimer la ligne (en d'autres termes, votre table et le système tiers se sont éloignés et ont différents versions de la vérité). Encore une fois, vous ne pouvez pas empêcher cela sans transactions distribuées et je ne conseillerais jamais pour DTC.

Remus Rusanu
la source
4

Cela va tuer la concurrence sur votre application. SQL Server a déjà tout ce qu'il faut pour éviter de mettre à jour les mêmes lignes en même temps, il n'y a donc vraiment pas besoin de le faire.

Les blocages peuvent se produire pour plusieurs raisons, je vous suggère donc d'enquêter sur ces raisons avant de créer votre propre système de verrouillage. Vous pouvez capturer des graphiques de blocage à partir de la session XE d'intégrité système et commencer à les analyser.

Habituellement, les interblocages résultent de la prise de verrous sur des objets dans des ordres différents, vous devez donc essayer de les prendre toujours dans le même ordre. Il existe d'autres raisons pour lesquelles des interblocages peuvent se produire, mais les conseils généraux sont que les transactions courtes maintiennent les verrous pendant une courte période, donc le réglage de vos requêtes avec un meilleur code, des index et des stratégies de division et d'impera sera probablement le meilleur moyen de se débarrasser de blocages.

Les interblocages de type «Lecteurs bloquant les écrivains» peuvent être fortement atténués en basculant la base de données sur Read Committed Snapshot Isolation . Si votre application a été conçue avec un verrouillage pessimiste à l'esprit, vous devez tout examiner et tester très soigneusement avant d'activer cette option sur votre base de données.

Dans le cas où vous insistez pour emprunter la voie du "système de verrouillage personnalisé", utilisez au moins quelque chose qui garantit la libération des verrous en cas de problème avec l'application. Vous voudrez peut-être étudier la procédure stockée sp_getapplock intégrée , qui fait quelque chose de similaire à ce que vous semblez être après.

MISE À JOUR: Après avoir lu votre question modifiée, c'est une autre façon d'exprimer la même requête:

SELECT *
FROM (
    SELECT *, RN = ROW_NUMBER() OVER(PARTITION BY name ORDER BY id DESC) 
    FROM locks
) AS data
WHERE RN = 1 
    AND lock = 1;

Cela fonctionnera si les objets ne peuvent être verrouillés qu'une seule fois, ce qui semble être de toute façon le but du système de verrouillage.

spaghettidba
la source