Dois-je valider ou annuler une transaction de lecture?


95

J'ai une requête de lecture que j'exécute dans une transaction afin de pouvoir spécifier le niveau d'isolement. Une fois la requête terminée, que dois-je faire?

  • Valider la transaction
  • Annuler la transaction
  • Ne rien faire (ce qui entraînera l'annulation de la transaction à la fin du bloc d'utilisation)

Quelles sont les implications de chacun?

using (IDbConnection connection = ConnectionFactory.CreateConnection())
{
    using (IDbTransaction transaction = connection.BeginTransaction(IsolationLevel.ReadUncommitted))
    {
        using (IDbCommand command = connection.CreateCommand())
        {
            command.Transaction = transaction;
            command.CommandText = "SELECT * FROM SomeTable";
            using (IDataReader reader = command.ExecuteReader())
            {
                // Read the results
            }
        }

        // To commit, or not to commit?
    }
}

MODIFIER: La question n'est pas de savoir si une transaction doit être utilisée ou s'il existe d'autres moyens de définir le niveau de transaction. La question est de savoir si cela fait une différence qu'une transaction qui ne modifie rien est validée ou annulée. Y a-t-il une différence de performance? Cela affecte-t-il d'autres connexions? Y a-t-il d'autres différences?


1
Vous le savez probablement déjà, mais étant donné l'exemple que vous avez fourni, vous pouvez avoir des résultats équivalents en exécutant simplement la requête: SELECT * FROM SomeTable with NOLOCK
JasonTrue

@Stefan, il semble que la plupart d'entre nous se demandent pourquoi vous vous embêtez à effectuer une opération en lecture seule. Pouvez-vous nous dire si vous connaissez NOLOCK et si vous le faites, pourquoi vous n'avez pas emprunté cette voie.
StingyJack

Je connais NOLOCK, mais ce système fonctionne avec différentes bases de données ainsi que SQL Server, j'essaie donc d'éviter les indices de verrouillage spécifiques à SQL Server. C'est une question plus par curiosité qu'autre chose car l'application fonctionne bien avec le code ci-dessus.
Stefan Moser

Ah, dans ce cas, je supprime la balise sqlserver, car cela désigne MSSqlServer comme produit cible.
StingyJack

@StingyJack - Vous avez raison, je n'aurais pas dû utiliser la balise sqlserver.
Stefan Moser

Réponses:


51

Vous vous engagez. Période. Il n'y a pas d'autre alternative sensée. Si vous avez commencé une transaction, vous devez la clôturer. La validation libère tous les verrous que vous avez pu avoir et est tout aussi judicieuse avec les niveaux d'isolement ReadUncommitted ou Serializable. S'appuyer sur un retour en arrière implicite - bien que peut-être techniquement équivalent - n'est que médiocre.

Si cela ne vous a pas convaincu, imaginez le prochain type qui insère une instruction de mise à jour au milieu de votre code et doit retrouver la restauration implicite qui se produit et supprime ses données.


44
Il existe une alternative judicieuse: la restauration. Restauration explicite, c'est-à-dire. Si vous ne vouliez rien changer, la restauration garantit que tout est annulé. Bien sûr, il n'aurait dû y avoir aucun changement; rollback garantit cela.
Jonathan Leffler

2
Différents SGBD peuvent avoir différentes sémantiques de «réalisation de transaction implicite». IBM Informix (et je crois que DB2) effectue une restauration implicite; par rumeur, Oracle fait un commit implicite. Je préfère la restauration implicite.
Jonathan Leffler

8
Supposons que je crée une table temporaire, que je la remplisse avec des identifiants, que je la joins à une table de données pour sélectionner les données qui accompagnent les identifiants, puis que je supprime la table temporaire. Je ne fais que lire des données, et je me fiche de ce qui arrive à la table temporaire, car elle est temporaire ... mais du point de vue des performances, serait-il plus coûteux d'annuler la transaction ou de la valider? Quel est l'effet d'un commit / rollback quand rien d'autre que des tables temporaires et des opérations de lecture sont impliqués?
Triynko

4
@Triynko - Intuitivement, je suppose que ROLLBACK est plus cher. COMMIT est le cas d'utilisation normal et ROLLBACK est le cas exceptionnel. Mais, sauf sur le plan académique, qui s'en soucie? Je suis sûr qu'il y a 1000 meilleurs points d'optimisation pour votre application. Si vous êtes vraiment curieux, vous pouvez trouver le code de gestion des transactions mySQL sur bazaar.launchpad.net/~mysql/mysql-server/mysql-6.0/annotate/…
Mark Brackett

3
@Triynko - La seule façon d'optimiser est de profiler. C'est un changement de code si simple, il n'y a aucune raison de ne pas profiler les deux méthodes si vous voulez vraiment l'optimiser. Assurez-vous de nous mettre à jour avec les résultats!
Mark Brackett

28

Si vous n'avez rien changé, vous pouvez utiliser un COMMIT ou un ROLLBACK. L'un ou l'autre libère tous les verrous de lecture que vous avez acquis et puisque vous n'avez effectué aucune autre modification, ils seront équivalents.


2
Merci de m'avoir fait savoir qu'ils sont équivalents. À mon avis, cela répond le mieux à la question réelle.
chowey

cela donnerait que la transaction est inactive si nous utilisons la validation sans mise à jour réelle. je viens de le faire face sur mon site en direct
Muhammad Omer Aslam

6

Si vous commencez une transaction, la meilleure pratique consiste toujours à la valider. Si une exception est levée dans votre bloc d'utilisation (transaction), la transaction sera automatiquement annulée.


3

À mon humble avis, il peut être judicieux d'encapsuler les requêtes en lecture seule dans les transactions car (en particulier en Java), vous pouvez dire que la transaction est en «lecture seule», ce qui à son tour, le pilote JDBC peut envisager d'optimiser la requête (mais ce n'est pas obligatoire, donc personne vous empêchera INSERTnéanmoins d'émettre un ). Par exemple, le pilote Oracle évitera complètement les verrous de table sur les requêtes dans une transaction marquée en lecture seule, ce qui gagne beaucoup de performances sur les applications fortement axées sur la lecture.


3

Considérez les transactions imbriquées .

La plupart des SGBDR ne prennent pas en charge les transactions imbriquées ou tentent de les émuler de manière très limitée.

Par exemple, dans MS SQL Server, une restauration dans une transaction interne (qui n'est pas une transaction réelle, MS SQL Server ne compte que les niveaux de transaction!) Annulera tout ce qui s'est passé dans la transaction la plus externe (qui est la transaction réelle).

Certains wrappers de base de données peuvent considérer une annulation dans une transaction interne comme un signe qu'une erreur s'est produite et annuler tout dans la transaction la plus externe, que la transaction la plus éloignée ait été validée ou annulée.

Ainsi, un COMMIT est le moyen le plus sûr, lorsque vous ne pouvez pas exclure que votre composant soit utilisé par un module logiciel.

Veuillez noter qu'il s'agit d'une réponse générale à la question. L'exemple de code résout intelligemment le problème avec une transaction externe en ouvrant une nouvelle connexion à la base de données.

Concernant les performances: en fonction du niveau d'isolement, les SELECT peuvent nécessiter un degré variable de LOCK et de données temporaires (instantanés). Ceci est nettoyé lorsque la transaction est fermée. Peu importe que cela se fasse via COMMIT ou ROLLBACK. Il peut y avoir une différence insignifiante dans le temps CPU passé - un COMMIT est probablement plus rapide à analyser qu'un ROLLBACK (deux caractères de moins) et d'autres différences mineures. Évidemment, cela n'est vrai que pour les opérations en lecture seule!

Pas du tout demandé: un autre programmeur qui pourrait lire le code pourrait supposer qu'un ROLLBACK implique une condition d'erreur.


2

Juste une note latérale, mais vous pouvez également écrire ce code comme ceci:

using (IDbConnection connection = ConnectionFactory.CreateConnection())
using (IDbTransaction transaction = connection.BeginTransaction(IsolationLevel.ReadUncommitted))
using (IDbCommand command = connection.CreateCommand())
{
    command.Transaction = transaction;
    command.CommandText = "SELECT * FROM SomeTable";
    using (IDataReader reader = command.ExecuteReader())
    {
        // Do something useful
    }
    // To commit, or not to commit?
}

Et si vous restructurez un peu les choses, vous pourrez peut-être aussi déplacer le bloc using pour l'IDataReader vers le haut.


1

Si vous mettez le SQL dans une procédure stockée et ajoutez ceci au-dessus de la requête:

set transaction isolation level read uncommitted

alors vous n'avez pas à sauter à travers les cercles dans le code C #. La définition du niveau d'isolation des transactions dans une procédure stockée ne provoque pas l'application du paramètre à toutes les utilisations futures de cette connexion (ce dont vous devez vous soucier avec d'autres paramètres puisque les connexions sont regroupées). À la fin de la procédure stockée, il revient simplement à celui avec lequel la connexion a été initialisée.


1

ROLLBACK est principalement utilisé en cas d'erreur ou de circonstances exceptionnelles, et COMMIT en cas de réussite.

Nous devrions fermer les transactions avec COMMIT (pour le succès) et ROLLBACK (pour l'échec), même dans le cas de transactions en lecture seule où cela ne semble pas avoir d'importance. En fait, c'est important, pour la cohérence et la pérennité.

Une transaction en lecture seule peut logiquement «échouer» de plusieurs manières, par exemple:

  • une requête ne renvoie pas exactement une ligne comme prévu
  • une procédure stockée lève une exception
  • les données récupérées sont incohérentes
  • l'utilisateur abandonne la transaction car elle prend trop de temps
  • blocage ou délai d'expiration

Si COMMIT et ROLLBACK sont utilisés correctement pour une transaction en lecture seule, il continuera à fonctionner comme prévu si du code d'écriture DB est ajouté à un moment donné, par exemple pour la mise en cache, l'audit ou les statistiques.

Le ROLLBACK implicite ne doit être utilisé que pour les situations d '"erreur fatale", lorsque l'application plante ou se ferme avec une erreur irrécupérable, une panne de réseau, une panne de courant, etc.


0

Étant donné qu'un READ ne change pas d'état, je ne ferais rien. Effectuer un commit ne fera rien, sauf gaspiller un cycle pour envoyer la requête à la base de données. Vous n'avez pas effectué d'opération dont l'état a changé. De même pour le rollback.

Vous devez cependant vous assurer de nettoyer vos objets et de fermer vos connexions à la base de données. Ne pas fermer vos connexions peut entraîner des problèmes si ce code est appelé à plusieurs reprises.


3
Selon le niveau d'isolement, une sélection PEUT obtenir des verrous qui bloqueront d'autres transactions.
Graeme Perrow

La connexion sera fermée à la fin du bloc d'utilisation - c'est pour cela qu'elle est là. Mais bon point que le trafic réseau est probablement la partie la plus lente de l'équation.
Joel Coehoorn

1
La transaction sera validée ou annulée d'une manière ou d'une autre, donc la meilleure pratique serait de toujours émettre une validation si elle réussit.
Neil Barnwell

0

Si vous définissez AutoCommit sur false, alors OUI.

Dans une expérience avec JDBC (pilote Postgresql), j'ai trouvé que si la requête de sélection est interrompue (en raison du délai d'expiration), vous ne pouvez pas lancer de nouvelle requête de sélection à moins de revenir en arrière.


-2

Dans votre exemple de code, où vous avez

  1. // Faites quelque chose d'utile

    Exécutez-vous une instruction SQL qui modifie les données?

Sinon, il n'y a pas de transaction "Read" ... Seules les modifications d'une instruction Insert, Update et Delete (instructions qui peuvent changer des données) sont dans une transaction ... Ce dont vous parlez, ce sont les verrous que SQL Le serveur met sur les données que vous lisez, en raison d'AUTRES transactions qui affectent ces données. Le niveau de ces verrous dépend du niveau d'isolation SQL Server.

Mais vous ne pouvez pas valider ou retourner quoi que ce soit si votre instruction SQL n'a rien changé.

Si vous modifiez des données, vous pouvez modifier le niveau d'isolement sans démarrer explicitement une transation ... Chaque instruction SQL individuelle est implicitement dans une transaction. démarrer explicitement une transaction n'est nécessaire que pour s'assurer que 2 ou plusieurs instructions se trouvent dans la même transaction.

Si tout ce que vous voulez faire est de définir le niveau d'isolement des transactions, définissez simplement CommandText d'une commande sur "Définir le niveau d'isolement des transactions en lecture répétable" (ou quel que soit le niveau souhaité), définissez le CommandType sur CommandType.Text et exécutez la commande. (vous pouvez utiliser Command.ExecuteNonQuery ())

REMARQUE: Si vous effectuez des instructions de lecture MULTIPLES et que vous souhaitez qu'elles "voient" toutes le même état de la base de données que la première, vous devez définir le niveau d'isolement supérieur Lecture répétable ou sérialisable ...


// Faire quelque chose d'utile ne change aucune donnée, il suffit de lire. Tout ce que je veux faire est de spécifier le niveau d'isolement de la requête.
Stefan Moser

Ensuite, vous pouvez le faire sans démarrer explicitement une transaction à partir du client ... Exécutez simplement la chaîne sql "Set Transaction Isolation Level ReadUncommitted", "... Read Committed", "... RepeatableRead", "... Snapshot" , ou "... Serializable" "Set Isolation Level Read Committed"
Charles Bretana

3
Les transactions comptent toujours, même si vous ne faites que lire. Si vous souhaitez effectuer plusieurs opérations de lecture, les faire dans une transaction garantira la cohérence. Les faire sans un ne sera pas.
MarkR

oui désolé, vous avez raison, au moins c'est vrai si le niveau d'isolement est défini sur Lecture répétable ou supérieur.
Charles Bretana

-3

Devez-vous empêcher les autres de lire les mêmes données? Pourquoi utiliser une transaction?

@Joel - Ma question serait mieux formulée comme "Pourquoi utiliser une transaction sur une requête de lecture?"

@Stefan - Si vous envisagez d'utiliser AdHoc SQL et non un processus stocké, ajoutez simplement le WITH (NOLOCK) après les tables de la requête. De cette façon, vous n'encourez pas la surcharge (quoique minime) dans l'application et la base de données pour une transaction.

SELECT * FROM SomeTable WITH (NOLOCK)

EDIT @ Commentaire 3: Puisque vous aviez "sqlserver" dans les balises de question, j'avais supposé que MSSQLServer était le produit cible. Maintenant que ce point a été clarifié, j'ai modifié les balises pour supprimer la référence de produit spécifique.

Je ne sais toujours pas pourquoi vous souhaitez effectuer une transaction sur une opération de lecture en premier lieu.


1
Au niveau d'isolement défini en une seule fois. Vous pouvez utiliser la transaction pour réduire réellement le niveau de verrouillage de la requête.
Joel Coehoorn

1
J'utilise la transaction afin de pouvoir utiliser un niveau d'isolement inférieur et réduire le verrouillage.
Stefan Moser

@StingyJack - Ce code peut s'exécuter sur un certain nombre de bases de données différentes, donc NOLOCK n'est pas une option.
Stefan Moser
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.