web-dev-qa-db-fra.com

Invité à ne pas utiliser de transactions et à utiliser une solution de contournement pour en simuler une

Je développe T-SQL depuis plusieurs années et je continue de creuser, continuant à apprendre tout ce que je peux sur tous les aspects du langage. J'ai récemment commencé à travailler dans une nouvelle entreprise et j'ai reçu ce que je pense être une étrange suggestion concernant les transactions. Ne les utilisez jamais. Utilisez plutôt une solution de contournement qui simule une transaction. Cela vient de notre DBA qui travaille dans une base de données avec beaucoup de transactions et par la suite, beaucoup de blocage. La base de données dans laquelle je travaille principalement ne souffre pas de ce problème et je constate que des transactions ont été utilisées dans le passé.

Je comprends que le blocage est attendu avec les transactions car c'est dans leur nature de le faire et si vous pouvez vous en sortir sans en utiliser un, faites-le par tous les moyens. Mais j'ai de nombreuses occasions où chaque instruction DOIT s'exécuter avec succès. Si l'un échoue, tous doivent échouer.

J'ai toujours gardé la portée de mes transactions aussi étroite que possible, toujours utilisée en conjonction avec SET XACT_ABORT ON et toujours dans un TRY/CATCH.

Exemple:

CREATE SCHEMA someschema;
GO


CREATE TABLE someschema.tableA
(id   INT NOT NULL IDENTITY(1, 1) PRIMARY KEY, 
 ColA VARCHAR(10) NOT NULL
);
GO

CREATE TABLE someschema.tableB
(id   INT NOT NULL IDENTITY(1, 1) PRIMARY KEY, 
 ColB VARCHAR(10) NOT NULL
); 
GO


CREATE PROCEDURE someschema.ProcedureName @ColA VARCHAR(10), 
                                          @ColB VARCHAR(10)
AS
SET NOCOUNT, XACT_ABORT ON;
BEGIN
BEGIN TRY
    BEGIN TRANSACTION;

    INSERT INTO someschema.tableA(ColA)
    VALUES(@ColA);

    INSERT INTO someschema.tableB(ColB)
    VALUES(@ColB);

--Implement error
    SELECT 1/0 

    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    IF @@trancount > 0
    BEGIN
        ROLLBACK TRANSACTION;
    END;
    THROW;
    RETURN;
END CATCH;
END;
GO

Voici ce qu'ils m'ont suggéré de faire.

GO



CREATE PROCEDURE someschema.ProcedureNameNoTransaction @ColA VARCHAR(10), 
                                                       @ColB VARCHAR(10)
AS
SET NOCOUNT ON;
BEGIN
BEGIN TRY
    DECLARE @tableAid INT;
    DECLARE @tableBid INT;

    INSERT INTO someschema.tableA(ColA)
    VALUES(@ColA);
    SET @tableAid = SCOPE_IDENTITY();

    INSERT INTO someschema.tableB(ColB)
    VALUES(@ColB);
    SET @tableBid = SCOPE_IDENTITY();

--Implement error
    SELECT 1/0 

END TRY
BEGIN CATCH
    DELETE FROM someschema.tableA
    WHERE id = @tableAid;

    DELETE FROM someschema.tableB
    WHERE id = @tableBid;

    THROW;

    RETURN;
END CATCH;
END;
GO

Ma question à la communauté est la suivante. Est-ce que cela a du sens comme solution de contournement viable pour les transactions?

D'après ce que je sais des transactions et de ce que propose la solution, mon opinion est que non, ce n'est pas une solution viable et introduit de nombreux points de défaillance.

Dans la solution de contournement suggérée, je vois quatre transactions implicites se produire. Les deux insertions dans l'essai, puis deux transactions supplémentaires pour les suppressions dans la capture. Il "défait" les inserts mais sans rien faire reculer donc rien n'est réellement reculé.

Il s'agit d'un exemple très simple pour démontrer le concept qu'ils proposent. Certaines des procédures stockées réelles dans lesquelles je fais cela les rendent extrêmement longues et difficiles à gérer, car "annuler" plusieurs jeux de résultats par rapport à deux valeurs de paramètres dans cet exemple devient assez compliqué comme vous pouvez l'imaginer. Étant donné que le "retour en arrière" se fait manuellement maintenant, l'occasion de rater quelque chose est réel.

Un autre problème qui, selon moi, existe concerne les délais d'attente ou les connexions rompues. Est-ce que cela est toujours annulé? C'est ma compréhension des raisons pour lesquelles SET XACT_ABORT ON doit être utilisé afin que dans ces cas, la transaction soit annulée.

Merci pour vos commentaires à l'avance!

43
Forrest

Vous ne pouvez pas pas utiliser des transactions dans SQL Server (et probablement tout autre SGBDR approprié). En l'absence de limites de transaction explicites (begin transaction ... commit) chaque instruction SQL démarre une nouvelle transaction, qui est implicitement validée (ou annulée) une fois l'instruction terminée (ou échoue).

La simulation de transaction suggérée par la personne qui se présente comme votre "DBA" n'assure pas trois des quatre propriétés requises du traitement des transactions, car elle ne traite que les erreurs "légères" et n'est pas capable de traiter les erreurs "dures", telles que les déconnexions réseau, les pannes de courant, les pannes de disque, etc.

  • Atomicité: échec. Si une erreur "dure" se produit quelque part au milieu de votre pseudo-transaction, le changement sera non atomique.

  • Cohérence: échec. Il résulte de ce qui précède que vos données seront dans un état incohérent suite à une erreur "dure".

  • Isolement: échec. Il est possible qu'une pseudo-transaction simultanée change certains des données modifiées par votre pseudo-transaction avant la fin de la vôtre.

  • Durabilité: succès. Les modifications que vous apportez seront durables, le serveur de base de données s'assurera que; c'est la seule chose que l'approche de votre collègue ne peut pas gâcher.

Les verrous sont une méthode largement utilisée et empiriquement efficace pour assurer l'ACIDité des transactions de toutes sortes ou SGBDR (ce site étant un exemple). Je trouve très peu probable qu'un DBA aléatoire puisse trouver une meilleure solution au problème de concurrence que des centaines, voire des milliers d'informaticiens et d'ingénieurs qui ont construit des systèmes de base de données intéressants au cours des derniers, quoi, 50? 60 ans? (Je me rends compte que c'est un peu fallacieux en tant qu'argument "appel à l'autorité", mais je m'en tiendrai quand même.)

En conclusion, ignorez les conseils de votre "DBA" si vous le pouvez, combattez-le si vous en avez l'esprit et revenez ici avec des problèmes de concurrence spécifiques s'ils surviennent.

61
mustaccio

Certaines erreurs sont si graves que le bloc CATCH n'est jamais entré. De la documentation

Erreurs dont la gravité est égale ou supérieure à 20 et qui arrêtent le traitement des tâches du moteur de base de données SQL Server pour la session. Si une erreur se produit avec une gravité de 20 ou plus et que la connexion à la base de données n'est pas interrompue, TRY ... CATCH gèrera l'erreur.

Attentions, telles que les demandes d'interruption du client ou les connexions client rompues.

Lorsque la session est terminée par un administrateur système à l'aide de l'instruction KILL.

...

Compiler des erreurs, telles que des erreurs de syntaxe, qui empêchent l'exécution d'un lot.

Erreurs qui se produisent ... en raison d'une résolution de nom différée.

Beaucoup d'entre eux sont faciles à produire via SQL dynamique. Les déclarations d'annulation telles que celles que vous avez montrées ne protégeront pas vos données contre de telles erreurs.

14
Michael Green

i-one: La solution de contournement qui vous est proposée permet (au moins) de violer "A" du - ACIDE . Par exemple, si SP est exécuté par un client distant et que la connexion est interrompue, un "commit"/"rollback" partiel peut se produire, car le serveur peut terminer session entre deux insertions/suppressions (et abandonner SP exécution avant qu'il n'atteigne sa fin).

Est-ce que cela a du sens comme solution de contournement viable pour les transactions?

dan-guzman: Non, le bloc CATCH n'est jamais exécuté dans le cas d'un timeout de requête car l'API client a annulé le lot. Sans transaction, SET XACT_ABORT ON ne peut pas annuler autre chose que l'instruction en cours.

tibor-karaszi: Vous avez 4 transactions, ce qui signifie plus de journalisation dans le fichier journal des transactions. N'oubliez pas que chaque transaction nécessite une écriture synchrone des enregistrements du journal jusqu'à ce point, c'est-à-dire que vous obtenez de moins bonnes performances de cet aspect lorsque vous utilisez de nombreuses transactions.

rbarryyoung: S'ils obtiennent beaucoup de blocage, ils doivent soit corriger leur conception de données, rationaliser leur ordre d'accès à la table ou utilisez un niveau d'isolement plus approprié. Ils supposent que leurs problèmes (et leur incapacité à le comprendre) deviendront votre problème. La preuve de millions d'autres bases de données est que ce ne sera pas le cas.

En outre, ce qu'ils essaient d'implémenter manuellement est effectivement une concurrence optimiste pour les pauvres. Ce qu'ils devraient faire à la place, c'est d'utiliser certaines des meilleures concurrents optimistes au monde, déjà intégrées à SQL Server. Cela va au point d'isolement ci-dessus. Selon toute vraisemblance, ils doivent passer du niveau d'isolement de concurrence pessimiste qu'ils utilisent actuellement à l'un des niveaux d'isolement de concurrence optimistes, SNAPSHOT ou READ_COMMITTED_SNAPSHOT. Ceux-ci feront effectivement la même chose que leur code manuel, sauf qu'il le fera correctement.

ross-presser : Si vous avez des processus extrêmement longs - comme si quelque chose se passe aujourd'hui et la semaine prochaine, quelque chose doit suivre, et si la chose de la semaine prochaine échoue, celle d'aujourd'hui doit échouer rétroactivement - vous voudrez examiner sagas . Strictement parlant, cela est en dehors de la base de données, car il nécessite un bus de service.

10
user126897

Un mauvais code d'idée va juste coûter plus cher à réparer.

S'il y a des problèmes de blocage lors de l'utilisation d'une transaction explicite (restauration/validation), dirigez votre administrateur de base de données vers Internet pour trouver de bonnes idées pour résoudre les problèmes.

Voici un moyen d'aider à atténuer le blocage: https://www.sqlservercentral.com/articles/using-indexes-to-reduce-blocking-in-concurrent-transactions

Les index réduisent le nombre de recherches qui doivent se produire dans une table/page pour trouver une ligne/un ensemble de lignes. Ils sont généralement considérés comme une méthode pour réduire les temps d'exécution des requêtes SELECT * et à juste titre également. Ils ne sont pas considérés comme appropriés pour les tableaux impliqués dans un grand nombre de MISES À JOUR. En fait, les INDEX se révèlent défavorables dans ces cas car ils augmentent le temps nécessaire pour terminer les requêtes UPDATE.

Mais ce n'est pas toujours le cas. En fouillant un peu plus profondément dans l'exécution d'une instruction UPDATE, nous constatons que cela implique également d'exécuter une instruction SELECT en premier. Il s'agit d'un scénario spécial et souvent vu où les requêtes mettent à jour des ensembles de lignes mutuellement exclusifs. INDEXES ici peut conduire à une augmentation significative des performances du moteur de base de données contrairement à la croyance populaire.

5
user238855

La stratégie de fausse transaction est dangereuse car elle permet des problèmes de concurrence que les transactions empêchent spécifiquement. Considérez que dans le deuxième exemple, toutes les données peuvent être modifiées entre les instructions.

Les fausses transactions supprimées ne sont PAS GARANTIES pour s'exécuter ou réussir. Si le serveur de base de données s'éteint pendant la fausse transaction, certains effets, mais pas tous, resteront. Ils ne sont pas non plus garantis pour réussir de la même manière qu'un retour de transaction.

Cette stratégie pourrait fonctionner avec des insertions, mais ne fonctionnerait certainement pas avec des mises à jour ou des suppressions (pas d'instructions SQL de machine à remonter le temps).

Si une concurrence stricte entre les transactions entraîne un blocage, il existe de nombreuses solutions, même celles qui réduisent le niveau de protection ... ce sont les moyens corrects de résoudre le problème.

Votre DBA propose une solution qui pourrait fonctionner correctement s'il n'y avait qu'un seul utilisateur de la base de données, mais qui est absolument impropre à tout type d'utilisation sérieuse.

4
Bailey S

Ce n'est pas un problème de programmation, c'est plutôt un problème de relations interpersonnelles/de mauvaise communication. Il est fort probable que votre "DBA" se préoccupe des verrous, pas des transactions.

Les autres réponses expliquent déjà pourquoi vous devez utiliser des transactions ... Je veux dire que c'est ce que font les SGBDR, sans transactions correctement utilisées, il n'y a pas d'intégrité des données, donc je vais me concentrer sur la façon de résoudre le vrai problème, qui est: savoir pourquoi votre "DBA" a développé une allergie aux transactions et l'a convaincu de changer d'avis.

Je pense que ce gars confond "un scénario particulier où un mauvais code a entraîné des performances terribles" avec "toutes les transactions sont mauvaises". Je ne m'attendrais pas à ce qu'un DBA compétent fasse cette erreur, donc c'est vraiment bizarre. Peut-être qu'il a eu une très mauvaise expérience avec un code terrible?

Considérez un scénario comme celui-ci:

BEGIN
UPDATE or DELETE some row, which takes locks it
...do something that takes a while
...perform other queries
COMMIT

Ce style d'utilisation des transactions contient un verrou (ou plusieurs verrous), ce qui signifie que d'autres transactions touchant les mêmes lignes devront attendre. Si les verrous sont maintenus pendant longtemps, et surtout si de nombreuses autres transactions veulent verrouiller les mêmes lignes, cela peut vraiment nuire aux performances.

Ce que vous pourriez faire, c'est lui demander pourquoi il a cette idée curieusement erronée de ne pas utiliser les transactions, quels types de requêtes posaient problème, etc. Ensuite, essayez de le persuader que vous éviterez certainement les mauvais scénarios similaires, que vous surveillerez votre utilisation des verrous et performance, le rassurer, etc.

Ce qu'il vous dit, c'est "ne touchez pas le tournevis!" donc le code que vous avez posté dans votre question utilise essentiellement un marteau pour enfoncer une vis. Une bien meilleure option est de le convaincre que vous savez utiliser un tournevis ...

Je peux penser à plusieurs exemples ... eh bien, ils étaient sur MySQL mais cela devrait aussi fonctionner.

Il y avait un forum où l'index de texte intégral prenait un certain temps à mettre à jour. Lorsqu'un utilisateur soumettait une publication, la transaction mettait à jour la table des rubriques pour augmenter le nombre de publications et la dernière date de publication (verrouillant ainsi la ligne de rubrique), puis insérait la publication et la transaction maintenait le verrou jusqu'à la fin de la mise à jour de l'index de texte intégral et le COMMIT a été fait.

Comme cela fonctionnait sur un rustbucket avec beaucoup trop peu de RAM, la mise à jour dudit index fulltext entraînait souvent plusieurs secondes d'intense aléatoire IO sur le seul lecteur à rotation lente dans la boîte).

Le problème est que les personnes qui ont cliqué sur le sujet ont provoqué une requête pour augmenter le nombre de vues sur le sujet, ce qui a également nécessité un verrou sur la ligne du sujet. Ainsi, personne ne pouvait afficher le sujet pendant la mise à jour de son index de texte intégral. Je veux dire, la ligne pourrait être lue, mais la mettre à jour la verrouillerait.

Pire encore, la publication mettait à jour le nombre de messages sur la table des forums parents et maintenait également le verrou pendant la mise à jour de l'index de texte intégral ... ce qui a gelé l'ensemble du forum pendant quelques secondes et provoqué l'accumulation de tonnes de demandes dans la file d'attente du serveur Web .

La solution consistait à prendre les verrous dans le bon ordre: COMMENCER, insérer la publication et mettre à jour l'index de texte intégral sans prendre de verrous, puis mettre à jour rapidement les lignes de sujet/forum avec le nombre de publications et la dernière date de publication, et COMMIT. Cela a complètement résolu le problème. Il se déplaçait juste autour de quelques requêtes, vraiment simple.

Dans ce cas, les transactions n'étaient pas le problème ... Il s'agissait d'acquérir un verrou inutile avant une longue opération. Autres exemples de choses à éviter tout en maintenant un verrou dans une transaction: attendre la saisie de l'utilisateur, accéder à de nombreuses données non mises en cache à partir de disques à rotation lente, E/S réseau, etc.

Bien sûr, parfois, vous n'avez pas le choix et vous devez effectuer un traitement long tout en maintenant des verrous encombrants. Il existe des astuces à ce sujet (opérer sur une copie des données, etc.) mais, le plus souvent, le goulot d'étranglement des performances provient d'un verrou qui n'a pas été intentionnellement acquis, et le simple fait de réorganiser les requêtes résout le problème. Encore mieux, c'est d'être conscient des verrous pris lors de l'écriture des requêtes ...

Je ne répéterai pas les autres réponses mais vraiment ... utilise des transactions. Votre problème est de convaincre votre "DBA", de ne pas contourner la caractéristique la plus importante d'une base de données ...

4
peufeu

TLDR: Utilisez le bon niveau d'isolement.

Comme vous l'avez bien remarqué, l'approche sans transactions et avec récupération "manuelle" peut être très complexe. La grande complexité signifie normalement beaucoup plus de temps pour l'implémenter et beaucoup plus de temps pour corriger les erreurs (car la complexité entraîne plus d'erreurs dans l'implémentation). Cela signifie qu'une telle approche peut coûter beaucoup plus cher à votre client.

La principale préoccupation de votre collègue "dba" est la performance. Une des façons de l'améliorer est d'utiliser un niveau d'isolement approprié. Supposons que vous ayez une procédure qui fournit une sorte de données de vue d'ensemble à l'utilisateur. Une telle procédure ne doit pas nécessairement utiliser le niveau d'isolement SERIALIZABLE. Dans de nombreux cas, LIRE NON ENGAGÉ peut être tout à fait suffisant. Cela signifie qu'une telle procédure ne sera pas bloquée par votre transaction qui crée ou modifie certaines données.

Je vous suggère d'examiner toutes les fonctions/procédures existantes dans votre base de données, d'évaluer le niveau d'isolement raisonnable pour chacune, d'expliquer les avantages de performance à votre client. Ajustez ensuite ces fonctions/procédures en conséquence.

3
mentallurg

Vous pouvez également décider d'utiliser les tables In-Memory OLTP. Bien entendu, elles utilisent toujours des transactions, mais aucun blocage n'est impliqué.
Au lieu de bloquer toutes les opérations seront réussies, mais pendant la phase de validation, le moteur vérifiera les conflits de transactions et l'une des validations peut échouer. Microsoft utilise le terme "verrouillage optimiste".
Si le problème de mise à l'échelle est dû à un conflit entre deux opérations d'écriture, telles que deux transactions simultanées essayant de mettre à jour la même ligne, In-Memory OLTP permet à une transaction de réussir et échoue la autre transaction. La transaction ayant échoué doit être soumise de nouveau, explicitement ou implicitement, en réessayant la transaction.
Plus sur: En mémoire OLTP

2
Piotr