Question

J'aidais certains de mes collègues à résoudre un problème de SQL. Ils souhaitaient principalement déplacer toutes les lignes de la table A vers la table B (les deux tables ayant les mêmes colonnes (noms et types)). Même si cela a été fait dans Oracle 11g, je ne pense pas que cela compte vraiment.

Leur implémentation naïve initiale ressemblait à

BEGIN
  INSERT INTO B SELECT * FROM A
  DELETE FROM A
  COMMIT;
END

Leur préoccupation était de savoir s’il y avait des INSERTIONS faites à la table A lors de la copie de A à B et de la mention "SUPPRIMER DE A". (ou TRUNCATE pour ce qui valait la peine) causerait une perte de données (les nouvelles lignes insérées dans A étant supprimées).

Bien sûr, j'ai rapidement recommandé de stocker les ID des lignes copiées dans une table temporaire, puis de supprimer uniquement les lignes de A correspondant à l'IDS de la table temporaire.

Cependant, pour des raisons de curiosité, nous avons mis un peu de test en ajoutant une commande wait (ne vous souvenez pas de la syntaxe PL / SQL) entre INSERT et DELETE. Ensuite, à partir d’une connexion différente, nous insérerions des lignes DURANT L’ATTENTE .

Nous avons constaté que cela constituait une perte de données. J'ai reproduit le contexte entier dans SQL Server et l'ai enveloppé dans une transaction, mais les nouvelles données fraîches ont également été perdues dans SQL Server. Cela m'a fait penser qu'il y a une erreur / un défaut systématique dans l'approche initiale.

Cependant, je ne peux pas dire s'il s'agissait du fait que la TRANSACTION n'était pas (d'une manière ou d'une autre?) isolée des nouveaux INSERT ou du fait que les INSERT sont venus pendant la commande WAIT.

À la fin, il a été mis en œuvre à l'aide de la table temporaire suggérée par moi, mais nous n'avons pas pu obtenir la réponse à "Pourquoi la perte de données". Savez-vous pourquoi?

Était-ce utile?

La solution

En fonction de votre niveau d'isolation, la sélection de toutes les lignes d'un tableau n'empêche pas les nouvelles insertions. Elle verrouille simplement les lignes que vous avez lues. Dans SQL Server, si vous utilisez le niveau d’isolation Serializable, il empêchera les nouvelles lignes si elles avaient été incluses dans votre requête select.

http://msdn.microsoft.com/en-us/library /ms173763.aspx -

SERIALISABLE Spécifie les éléments suivants:

  • Les instructions ne peuvent pas lire les données modifiées mais non encore validées par d'autres transactions.

  • Aucune autre transaction ne peut modifier les données lues par la transaction en cours tant que la transaction en cours n'est pas terminée.

  • Les autres transactions ne peuvent pas insérer de nouvelles lignes avec des valeurs de clé comprises dans la plage de clés lues par les instructions de la transaction en cours tant que la transaction en cours n'est pas terminée.

Autres conseils

Je ne peux pas parler de la stabilité des transactions, mais une autre approche consisterait à supprimer la deuxième étape de la table source, le cas échéant (sélectionnez des identifiants dans la table cible).

Pardonnez la syntaxe, je n’ai pas testé ce code, mais vous devriez pouvoir vous faire une idée:

INSERT INTO B SELECT * FROM A;

DELETE FROM A WHERE EXISTS (SELECT B.<primarykey> FROM B WHERE B.<primarykey> = A.<primarykey>);

De cette façon, vous utilisez le moteur relationnel pour faire en sorte qu'aucune donnée plus récente ne soit supprimée, et vous n'avez pas besoin de suivre les deux étapes d'une transaction.

Mise à jour: syntaxe corrigée dans la sous-requête

Ceci peut être réalisé dans Oracle en utilisant:

Alter session set isolation_level=serializable;

Ceci peut être défini dans PL / SQL avec EXECUTE IMMEDIATE:

BEGIN
    EXECUTE IMMEDIATE 'Alter session set isolation_level=serializable';
    ...
END;

Voir Demandez à Tom: Sur les niveaux d'isolation de transaction

C’est la façon dont les transactions fonctionnent. Vous devez choisir le niveau d'isolation correct pour la tâche à accomplir.

Vous faites INSERT et DELETE dans la même transaction. Vous ne mentionnez pas le mode d'isolation utilisé par la transaction, mais il s'agit probablement d'une "lecture validée". Cela signifie que la commande DELETE verra les enregistrements validés entre-temps. Pour ce type de travail, il est bien préférable d'utiliser le type de transaction "instantané", car alors INSERT et DELETE sauraient à propos du même ensemble d'enregistrements - uniquement ceux-là et rien d'autre.

Je ne sais pas si cela est pertinent, mais dans SQL Server, la syntaxe est

begin tran
....
commit

pas seulement 'commencer'

Vous devez définir le niveau d'isolation de votre transaction pour que les insertions d'une autre transaction n'affectent pas votre transaction. Je ne sais pas comment faire cela dans Oracle.

Dans Oracle, le niveau d'isolation de transaction par défaut est lu comme validé. Cela signifie essentiellement que Oracle renvoie les résultats tels qu'ils existaient sur le SCN (numéro de modification du système) au démarrage de votre requête. Définir le niveau d'isolation de transaction sur sérialisable signifie que le SCN est capturé au début de la transaction. Par conséquent, toutes les requêtes de votre transaction renvoient des données à partir de ce SCN. Cela garantit des résultats cohérents indépendamment de ce que font les autres sessions et transactions. D'autre part, il se peut que Oracle ait un coût à déterminer s'il ne peut pas sérialiser votre transaction en raison d'activités exécutées par d'autres transactions. Vous devrez donc gérer ce type d'erreur.

Le lien de Tony à la discussion sur AskTom contient beaucoup plus de détails sur tout cela, je le recommande vivement.

Oui Milan, je n’ai pas précisé le niveau d’isolation de la transaction. Je suppose que c'est le niveau d'isolation par défaut dont je ne sais pas quel est le niveau. Ni dans Oracle 11g, ni dans SQL Server 2005.

De plus, le INSERT créé lors de la commande WAIT (sur la 2e connexion) était NOT dans une transaction. Aurait-il dû empêcher cette perte de données?

Il s'agit du comportement standard du mode par défaut à validation de lecture, tel que mentionné ci-dessus. La commande WAIT provoque simplement un retard dans le traitement, il n’existe aucun lien avec le traitement des transactions de base de données.

Pour résoudre le problème, vous pouvez soit:

  1. définissez le niveau d'isolement sur sérialisable, mais vous pouvez alors obtenir des erreurs ORA que vous devez gérer avec des tentatives! De plus, vous risquez d’avoir de graves problèmes de performances.
  2. utilise une table temporaire pour stocker les valeurs en premier
  3. si les données ne sont pas trop volumineuses pour tenir dans la mémoire, vous pouvez utiliser une clause RETURNING pour BULK COLLECT INTO dans une table imbriquée et supprimer uniquement si la ligne est présente dans la table imbriquée.

Vous pouvez également utiliser l'isolation de capture instantanée pour détecter les mises à jour perdues:

Lorsque l'isolement d'instantanés est utile et qu'il est douloureux

    I have written a sample code:-

    First run this on Oracle DB:-


     Create table AccountBalance
        (
              id integer Primary Key,
              acctName varchar2(255) not null,
              acctBalance integer not null,
              bankName varchar2(255) not null
        );

        insert into AccountBalance values (1,'Test',50000,'Bank-a');

    Now run the below code 





 package com.java.transaction.dirtyread;
        import java.sql.Connection;
        import java.sql.DriverManager;
        import java.sql.SQLException;

        public class DirtyReadExample {

         /**
          * @param args
         * @throws ClassNotFoundException 
          * @throws SQLException 
          * @throws InterruptedException 
          */
         public static void main(String[] args) throws ClassNotFoundException, SQLException, InterruptedException {

             Class.forName("oracle.jdbc.driver.OracleDriver");
             Connection connectionPayment = DriverManager.getConnection(
                        "jdbc:oracle:thin:@localhost:1521:xe", "hr",
                        "hr");
             Connection connectionReader = DriverManager.getConnection(
                        "jdbc:oracle:thin:@localhost:1521:xe", "hr",
                        "hr");

          try {
              connectionPayment.setAutoCommit(false);
              connectionPayment.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);


          } catch (SQLException e) {
           e.printStackTrace();
          }


          Thread pymtThread=new Thread(new PaymentRunImpl(connectionPayment));
          Thread readerThread=new Thread(new ReaderRunImpl(connectionReader));

          pymtThread.start();
          Thread.sleep(2000);
          readerThread.start();

         }

        }



        package com.java.transaction.dirtyread;

        import java.sql.Connection;
        import java.sql.PreparedStatement;
        import java.sql.ResultSet;
        import java.sql.SQLException;

        public class ReaderRunImpl  implements Runnable{

         private Connection conn;

         private static final String QUERY="Select acctBalance from AccountBalance where id=1";

         public ReaderRunImpl(Connection conn){
          this.conn=conn;
         }

         @Override
         public void run() {
          PreparedStatement stmt =null; 
          ResultSet rs =null;

          try {
           stmt = conn.prepareStatement(QUERY);
           System.out.println("In Reader thread --->Statement Prepared");
           rs = stmt.executeQuery();
           System.out.println("In Reader thread --->executing");
           while (rs.next()){

            System.out.println("Balance is:" + rs.getDouble(1));

           }
           System.out.println("In Reader thread --->Statement Prepared");
           Thread.sleep(5000);
           stmt.close();
           rs.close();
           stmt = conn.prepareStatement(QUERY);
           rs = stmt.executeQuery();
           System.out.println("In Reader thread --->executing");
           while (rs.next()){

            System.out.println("Balance is:" + rs.getDouble(1));

           }
           stmt.close();
           rs.close();
           stmt = conn.prepareStatement(QUERY);
           rs = stmt.executeQuery();
           System.out.println("In Reader thread --->executing");
           while (rs.next()){

            System.out.println("Balance is:" + rs.getDouble(1));

           }
          } catch (SQLException | InterruptedException e) {
           e.printStackTrace();
          }finally{
           try {
            stmt.close();
            rs.close();
           } catch (SQLException e) {
            e.printStackTrace();
           }   
          }
         }

        }

        package com.java.transaction.dirtyread;
        import java.sql.Connection;
        import java.sql.PreparedStatement;
        import java.sql.SQLException;

        public class PaymentRunImpl implements Runnable{

         private Connection conn;

         private static final String QUERY1="Update AccountBalance set acctBalance=40000 where id=1";
         private static final String QUERY2="Update AccountBalance set acctBalance=30000 where id=1";
         private static final String QUERY3="Update AccountBalance set acctBalance=20000 where id=1";
         private static final String QUERY4="Update AccountBalance set acctBalance=10000 where id=1";

         public PaymentRunImpl(Connection conn){
          this.conn=conn;
         }

         @Override
         public void run() {
          PreparedStatement stmt = null;

          try {   
           stmt = conn.prepareStatement(QUERY1);
           stmt.execute();
           System.out.println("In Payment thread --> executed");
           Thread.sleep(3000);
           stmt = conn.prepareStatement(QUERY2);
           stmt.execute();
           System.out.println("In Payment thread --> executed");
           Thread.sleep(3000);
           stmt = conn.prepareStatement(QUERY3);
           stmt.execute();
           System.out.println("In Payment thread --> executed");
           stmt = conn.prepareStatement(QUERY4);
           stmt.execute();
           System.out.println("In Payment thread --> executed");

           Thread.sleep(5000);
            //case 1
           conn.rollback();
           System.out.println("In Payment thread --> rollback");
          //case 2
           //conn.commit();
          // System.out.println("In Payment thread --> commit");
          } catch (SQLException e) {
           e.printStackTrace();
          } catch (InterruptedException e) {    
           e.printStackTrace();
          }finally{
           try {
            stmt.close();
           } catch (SQLException e) {
            e.printStackTrace();
           }
          }
         }

        }

    Output:-
    In Payment thread --> executed
    In Reader thread --->Statement Prepared
    In Reader thread --->executing
    Balance is:50000.0
    In Reader thread --->Statement Prepared
    In Payment thread --> executed
    In Payment thread --> executed
    In Payment thread --> executed
    In Reader thread --->executing
    Balance is:50000.0
    In Reader thread --->executing
    Balance is:50000.0
    In Payment thread --> rollback

Vous pouvez le tester en insérant de nouvelles lignes, comme défini par oracle: -  Une lecture fantôme se produit lorsque la transaction A récupère un ensemble de lignes satisfaisant une condition donnée, que la transaction B insère ou met à jour ultérieurement une ligne de sorte que la ligne remplisse maintenant la condition de la transaction A et que la transaction A répète ultérieurement l'extraction conditionnelle. La transaction A voit maintenant une ligne supplémentaire. Cette ligne est appelée fantôme. Cela évitera le scénario ci-dessus, ainsi que j'ai utilisé TRANSACTION_SERIALIZABLE. Il définira le verrou le plus strict sur Oracle. Oracle ne prend en charge que deux types d'isolation de transaction: - TRANSACTION_READ_COMMITTED et TRANSACTION_SERIALIZABLE.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top