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


43

Je développe T-SQL depuis plusieurs années et j'approfondis sans cesse, continuant à apprendre tout ce que je peux sur tous les aspects de la langue. J'ai récemment commencé à travailler dans une nouvelle société et j'ai reçu ce que je pense est une suggestion étrange concernant les transactions. Ne les utilise 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 déjà été utilisées.

Je comprends que le blocage des transactions est normal car il est de leur nature de le faire et si vous pouvez vous en tirer sans en utiliser une, faites-le. Mais il arrive souvent que chaque instruction DOIT être exécutée avec succès. Si on échoue, ils doivent tous ne pas s'engager.

J'ai toujours gardé le champ de mes transactions aussi étroit que possible, toujours utilisé 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 ont suggéré que je fasse.

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 la solution proposée, mon avis est que non, ce n'est pas une solution viable et présente de nombreux points d'échec.

Dans la solution de contournement suggérée, quatre transactions implicites se produisent. Les deux insère dans l'essai, puis deux autres transactions pour les suppressions dans la capture. Il «défait» les inserts mais sans rien annuler, donc rien n’est réellement annulé.

Ceci est un exemple très basique pour illustrer le concept proposé. Certaines des procédures stockées dans lesquelles j'ai effectué cette tâche sont extrêmement longues et difficiles à gérer, car il est très compliqué de "restaurer" plusieurs ensembles de résultats au lieu de deux valeurs de paramètre dans cet exemple. Puisque le "recul" est fait manuellement maintenant, l'occasion de rater quelque chose parce que c'est réel.

Je pense qu'un autre problème existe concerne les délais d'expiration ou les connexions rompues. Est-ce que cela est toujours annulé? C’est ainsi que je comprends pourquoi SET XACT_ABORT ON doit être utilisé pour que, dans ces cas, la transaction soit annulée.

Merci d'avance pour vos commentaires!


4
Les commentaires qui ne servent pas leur objectif déclaré ont été supprimés ou déplacés vers la réponse du wiki de la communauté.
Paul White a déclaré GoFundMonica

Réponses:


61

Vous ne pouvez 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 lance une nouvelle transaction implicitement validée (ou annulée) à la fin (ou à l'échec) de l'instruction.

La simulation de transaction suggérée par la personne qui se présente comme votre "administrateur de base de données" ne garantit pas trois des quatre propriétés requises du traitement de la transaction, car elle ne traite que des erreurs "logicielles" et n'est pas capable de traiter des erreurs "matérielles", tels que les déconnexions réseau, les coupures de courant, les pannes de disque, etc.

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

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

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

  • Durabilité: succès. Les modifications que vous apporterez 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 échouer.

Les verrous sont une méthode largement utilisée et empiriquement efficace pour garantir la conformité ACIDity des transactions de toutes sortes ou de SGBDR (ce site étant un exemple). Je trouve très peu probable qu'un administrateur de base de données aléatoire puisse proposer une meilleure solution au problème de la simultanéité 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 50 dernières années. 60 ans? (Je me rends compte que c'est un peu fallacieux comme argument "d'appel à l'autorité", mais je vais m'en tenir à cela quand même.)

En conclusion, ignorez le conseil de votre "administrateur de base de données" si vous le pouvez, combattez-le si vous en avez l'esprit, et revenez ici avec des problèmes de concurrence spécifiques, le cas échéant.


14

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

Les erreurs dont la gravité est supérieure ou égale à 20, arrêtent le traitement de la tâche du moteur de base de données SQL Server pour la session. Si une erreur de gravité supérieure ou égale à 20 survient 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 client ou les connexions client rompues.

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

...

Erreurs de compilation, telles que les erreurs de syntaxe, qui empêchent l'exécution d'un lot.

Erreurs qui se produisent ... en raison de la résolution de nom différée.

Beaucoup d'entre eux sont faciles à produire grâce au 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.


2
Bien - et si rien d’autre, le client mourant en exécutant le code constituerait une erreur "si grave que le bloc CATCH n’est jamais entré". Peu importe votre confiance en le logiciel (pas seulement votre propre code, mais CHAQUE partie de TOUTES les piles de logiciels impliquées), il y a toujours la possibilité d'une défaillance matérielle (encore une fois, potentiellement n'importe où dans la chaîne) qui vous bloque à tout moment. . Garder cela à l'esprit est un bon moyen de défense contre la pensée bienveillante qui conduit à ce type de "solution de contournement".
dgould le

2
En outre, vous pourriez être une victime de l'impasse. Vos blocs CATCH s'exécutent mais sont lancés s'ils essaient d'écrire dans la base de données.
Josué

10

i-one : La solution de contournement qui vous est suggérée rend possible (au moins) la violation de «A» de l’ ACID . Par exemple, si le SP est exécuté par un client distant et que la connexion est rompue, un "commit" / "rollback" partiel peut survenir, car le serveur peut terminer la session entre deux insertions / suppressions (et interrompre l'exécution du SP avant qu'il ne se termine) .

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

dan-guzman : Non, leCATCHbloc n'est jamais exécuté en cas de dépassement du délai de requête, car l'API client a annulé le lot. Sans transaction,SET XACT_ABORT ONvous ne pouvez pas annuler autre chose que l'instruction en cours.

tibor-karaszi : Vous avez 4 transactions, ce qui signifie davantage de journalisation dans le fichier journal des transactions. N'oubliez pas que chaque transaction nécessite une écriture synchrone des enregistrements de journal jusqu'à ce point, ce qui signifie que les performances sont également pires si vous utilisiez de nombreuses transactions.

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

En outre, ce qu'ils tentent de mettre en œuvre manuellement est effectivement une concurrence optimiste pour les hommes pauvres. Ce qu’ils devraient faire à la place, c’est d’utiliser l’une des meilleures concurrences optimistes au monde, déjà intégrée à SQL Server. Cela va au point d'isolement ci-dessus. Selon toute vraisemblance, ils doivent passer d’un niveau d’isolement de concurrence pessimiste qu’ils utilisent actuellement à l’un des niveaux d’isolement optimistes de concurrence, SNAPSHOTou READ_COMMITTED_SNAPSHOT. Ceux-ci feront effectivement la même chose que leur code manuel, à la différence qu'ils le feront correctement.

ross-presser : Si vous avez des processus extrêmement longs - comme si quelque chose se passait aujourd'hui et la semaine prochaine, quelque chose devait suivre, et si le problème de la semaine prochaine échouait, alors celui d'aujourd'hui doit échouer rétroactivement - vous voudrez peut-être examiner les sagas . À proprement parler, cela se trouve en dehors de la base de données, car il nécessite un bus de service.


5

Le code de mauvaise idée va juste être plus coûteux à réparer en bout de ligne.

S'il y a des problèmes de blocage lors de l'utilisation d'une transaction explicite (annulation / validation), pointez votre administrateur de base de données sur Internet pour trouver d'excellentes idées pour résoudre ces 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 devant 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 permettant de réduire les temps d’exécution des requêtes SELECT * et à juste titre également. Ils ne sont pas considérés comme adaptés aux tables impliquées dans un grand nombre de MISES À JOUR. En fait, les INDEX sont jugés défavorables dans ces cas car ils augmentent le temps requis pour compléter les requêtes UPDATE.

Mais ce n'est pas toujours le cas. En approfondissant légèrement l'exécution d'une instruction UPDATE, nous constatons qu'elle implique également l'exécution d'une instruction SELECT en premier. Il s'agit d'un scénario spécial et souvent observé dans lequel les requêtes mettent à jour des ensembles de lignes mutuellement exclusifs. Les index ici peuvent entraîner une augmentation significative des performances du moteur de base de données, contrairement à la croyance populaire.


4

La stratégie de fausse transaction est dangereuse car elle autorise les problèmes de simultanéité que les transactions empêchent spécifiquement. Considérez que dans le deuxième exemple, n'importe laquelle des données peut être modifiée entre les instructions.

Les suppressions de fausse transaction ne sont pas garanties pour être exécutées ou réussir. Si le serveur de base de données est désactivé pendant la fausse transaction, certains effets subsisteront, mais pas tous. Ils ne sont pas non plus garantis de réussir, comme le dit l'annulation d'une transaction.

Cette stratégie peut fonctionner avec des insertions, mais ne fonctionnerait certainement pas avec les mises à jour ou les suppressions (pas d'instructions SQL time-machine).

Si le blocage de transactions est provoqué par un blocage strict, il existe de nombreuses solutions, même celles qui réduisent le niveau de protection ... C’est la bonne façon de résoudre le problème.

Votre administrateur de base de données 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 à toute utilisation sérieuse.


4

Ce n'est pas un problème de programmation, mais plutôt un problème interpersonnel / de mauvaise communication. Très probablement, votre "DBA" s'inquiète des verrous, pas des transactions.

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

Je pense que ce gars est en train de confondre "un scénario particulier dans lequel un code incorrect a entraîné une performance épouvantable" avec "toutes les transactions sont mauvaises". Je ne m'attendrais pas à ce qu'un administrateur de base de données compétent fasse cette erreur, c'est donc vraiment étrange. Peut-être qu'il a eu une très mauvaise expérience avec un code terrible?

Prenons 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 de transaction contient un verrou (ou plusieurs verrous), ce qui signifie que les autres transactions qui atteignent les mêmes lignes devront attendre. Si les verrous sont conservés pendant une longue période, et en particulier si de nombreuses autres transactions souhaitent verrouiller les mêmes lignes, cela peut nuire gravement aux performances.

Ce que vous pouvez faire est de lui demander pourquoi il a cette curieuse idée erronée de ne pas utiliser de transactions, quels types de requêtes posent problème, etc. Essayez ensuite de le persuader que vous éviterez de manière similaire les mauvais scénarios similaires, que vous surveillerez votre utilisation du verrou et performance, rassurez-le, etc.

Ce qu'il vous dit, c'est "ne touchez pas le tournevis!" le code que vous avez posté dans votre question utilise donc essentiellement un marteau pour entraîner une vis. La 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 fonctionner aussi.

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

Étant donné que cela fonctionnait sur un Rustbucket avec trop peu de RAM, la mise à jour dudit index de texte intégral entraînait souvent plusieurs secondes d'E / S aléatoires intenses sur le lecteur à rotation lente unique dans la boîte.

Le problème était que les personnes ayant cliqué sur le sujet avaient demandé à une requête d'augmenter le nombre de vues sur le sujet, ce qui nécessitait également un verrou sur la ligne du sujet. Ainsi, personne ne pouvait voir le sujet pendant la mise à jour de son index de texte intégral. Je veux dire, la ligne pourrait être lue, mais sa mise à jour serait verrouillée.

Pire encore, la publication mettrait à jour le décompte des publications sur la table des forums parents tout en maintenant le verrou pendant la mise à jour de l'index du texte intégral ..., ce qui a gelé l'ensemble du forum pendant quelques secondes et provoqué une accumulation de demandes dans la file d'attente du serveur Web. .

La solution consistait à prendre les verrous dans le bon ordre: COMMENCER, insérer le message et mettre à jour l'index de texte intégral sans verrouiller, puis mettre à jour rapidement les lignes de sujet / forum avec le nombre d'articles et la date de dernier message et COMMIT. Cela a complètement résolu le problème. Il s'agissait simplement de déplacer quelques requêtes, très simples.

Dans ce cas, les transactions ne constituaient pas le problème ... Il s’agissait d’acquérir un verrou inutile avant une opération longue. Autres exemples d'éléments à éviter lors du verrouillage d'une transaction: attente de la saisie de l'utilisateur, accès à de nombreuses données non mises en cache à partir de disques à rotation lente, d'E / S réseau, etc.

Bien sûr, parfois, vous n'avez pas le choix et vous devez effectuer un traitement long en tenant des verrous encombrants. Il y a des astuces à ce sujet (utiliser une copie des données, etc.), mais très souvent, le goulot d'étranglement des performances provient d'un verrou qui n'a pas été acquis intentionnellement et le simple fait de réorganiser des requêtes résout le problème. Mieux encore, il faut être conscient des verrous pris lors de l'écriture des requêtes ...

Je ne vais pas répéter les autres réponses mais vraiment ... utiliser des transactions. Votre problème est de convaincre votre "DBA", pas de travailler sur la fonctionnalité la plus importante d'une base de données ...


3

TLDR: Utilisez le niveau d'isolation approprié .

Comme vous l'avez bien remarqué, l'approche sans transaction et avec une 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 pour réparer les erreurs (car la complexité engendre 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. L’un des moyens de l’améliorer consiste à utiliser un niveau d’isolement approprié. Supposons que vous ayez une procédure fournissant à l'utilisateur certaines sortes de données d'ensemble. Une telle procédure ne doit pas nécessairement utiliser le niveau d'isolement SERIALIZABLE. Dans de nombreux cas, READ UNCOMMITTED peut être 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érerais de passer en revue toutes les fonctions / procédures existantes dans votre base de données, d'évaluer le niveau d'isolement raisonnable de chacune d'elles, d'expliquer les avantages en termes de performances pour votre client. Ajustez ensuite ces fonctions / procédures en conséquence.


2

Vous pouvez également décider d'utiliser des tables OLTP en mémoire. Bien sûr, ils utilisent toujours des transactions, mais il n’ya pas de blocage.
Au lieu de bloquer, toutes les opérations aboutiront, mais pendant la phase de validation, le moteur vérifiera les conflits de transactions et l'une des validations pourrait échouer. Microsoft utilise le terme "verrouillage optimiste".
Si le problème de dimensionnement est dû à un conflit entre deux opérations d'écriture, telles que deux transactions simultanées essayant de mettre à jour la même ligne, OLTP en mémoire permet qu'une transaction aboutisse et échoue avec l'autre transaction. La transaction ayant échoué doit être soumise de nouveau, explicitement ou implicitement, en essayant à nouveau la transaction.
Plus sur: En mémoire OLTP


-5

Il existe un moyen d’utiliser les transactions dans une mesure limitée: modifier votre modèle de données pour qu’il soit davantage orienté objet. Ainsi, plutôt que de stocker, par exemple, des données démographiques concernant une personne dans plusieurs tables et de les relier les unes aux autres et nécessitant des transactions, vous pouvez avoir un seul document JSON qui stocke tout ce que vous savez de cette personne dans un seul champ. Bien sûr, travailler loin du domaine est un autre défi de conception, mieux fait par les développeurs que par les administrateurs de base de données

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.