7

私は同僚の SQL の問題を手伝っていました。主に、すべての行をテーブル A からテーブル B (両方のテーブルが同じ列 (名前と型) を持つ) に移動したいと考えていました。これは Oracle 11g で行われましたが、あまり重要ではないと思います。

彼らの最初の素朴な実装は次のようなものでした

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

彼らの懸念は、AからBへのコピー中にテーブルAにINSERTが行われ、「DELETE FROM A」(または価値がある場合はTRUNCATE)によってデータが失われる(Aに新しく挿入された行が削除される)ことでした。

もちろん、コピーした行の ID を一時テーブルに格納してから、一時テーブルの IDS に一致する A の行だけを削除することをすぐに推奨しました。

ただし、好奇心のために、INSERT と DELETE の間に待機コマンド (PL/SQL 構文を覚えていないでください) を追加して、ちょっとしたテストを行いました。次に、別の接続からDURING THE WAITに行を挿入します。

そうすることで、それがデータの損失であることがわかりました。SQL Server でコンテキスト全体を再現し、すべてをトランザクションにラップしましたが、それでも SQL Server で新しいデータが失われました。これにより、最初のアプローチに体系的なエラー/欠陥があると思いました。

ただし、TRANSACTION が新しい INSERT から (どういうわけか?) 分離されていなかったという事実なのか、それとも WAIT コマンド中に INSERT が発生したという事実なのかはわかりません。

結局、私が提案した一時テーブルを使用して実装しましたが、「なぜデータが失われたのか」という答えを得ることができませんでした。なぜなのかご存知ですか?

4

11 に答える 11

8

分離レベルによっては、テーブルからすべての行を選択しても、新しい挿入が妨げられることはなく、読み取った行がロックされるだけです。SQL Server では、Serializable 分離​​レベルを使用すると、select クエリに新しい行が含まれていた場合、新しい行が妨げられます。

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

SERIALIZABLE 以下を指定します。

  • ステートメントは、他のトランザクションによって変更されたがまだコミットされていないデータを読み取ることはできません。

  • 現在のトランザクションが完了するまで、他のトランザクションは現在のトランザクションによって読み取られたデータを変更できません。

  • 他のトランザクションは、現在のトランザクションが完了するまで、現在のトランザクションのステートメントによって読み取られるキーの範囲に入るキー値を持つ新しい行を挿入できません。

于 2008-09-29T19:26:06.533 に答える
7

トランザクションの安定性について話すことはできませんが、別のアプローチとして、存在するソース テーブルから 2 番目のステップを削除することもできます (ターゲット テーブルから ID を選択します)。

構文を許してください、私はこのコードをテストしていませんが、アイデアを得ることができるはずです:

INSERT INTO B SELECT * FROM A;

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

そうすれば、リレーショナル エンジンを使用して新しいデータが削除されないようにすることができ、トランザクションで 2 つの手順を実行する必要がなくなります。

更新: サブクエリの構文を修正

于 2008-09-29T19:17:56.053 に答える
5

これは、Oracle で次を使用して実現できます。

Alter session set isolation_level=serializable;

これは、EXECUTE IMMEDIATE を使用して PL/SQL で設定できます。

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

Ask Tom: トランザクション分離レベルについてを参照してください。

于 2008-09-29T19:19:30.620 に答える
2

それはまさにトランザクションが機能する方法です。手元のタスクに適した分離レベルを選択する必要があります。

同じトランザクションで INSERT と DELETE を実行しています。トランザクションが使用している分離モードについては言及していませんが、おそらく「読み取りコミット」です。これは、DELETE コマンドがその間にコミットされたレコードを参照することを意味します。この種のジョブでは、「スナップショット」タイプのトランザクションを使用する方がはるかに優れています。これは、INSERT と DELETE の両方が同じレコード セットについて認識し、それ以外は何も認識しないためです。

于 2008-09-29T19:20:05.860 に答える
1

Oracle では、デフォルトのトランザクション分離レベルはコミット読み取りです。これは基本的に、クエリが開始されたときに SCN (システム変更番号) に存在していたとおりに、Oracle が結果を返すことを意味します。トランザクション分離レベルをシリアル化可能に設定すると、トランザクションの開始時に SCN がキャプチャされるため、トランザクション内のすべてのクエリがその SCN の時点でデータを返します。これにより、他のセッションやトランザクションが何をしているかに関係なく、一貫した結果が保証されます。一方で、他のトランザクションが実行しているアクティビティのためにトランザクションをシリアル化できないと Oracle が判断する可能性があるため、そのようなエラーを処理する必要があるというコストが発生する可能性があります。

Tony の AskTom ディスカッションへのリンクは、これらすべてについてかなり詳細に説明されています。私はそれを強くお勧めします。

于 2008-09-29T20:27:41.690 に答える
1

これが関連しているかどうかはわかりませんが、SQL Server の構文は次のとおりです。

begin tran
....
commit

ただ「始める」だけでなく

于 2008-09-29T19:13:01.557 に答える
1

別のトランザクションからの挿入がトランザクションに影響を与えないように、トランザクション分離レベルを設定する必要があります。Oracleでそれを行う方法がわかりません。

于 2008-09-29T19:14:10.727 に答える
0

または、スナップショットアイソレーションを使用して、失われた更新を検出できます。

スナップショットアイソレーションが役立つ場合と痛い場合

于 2009-07-13T14:16:29.040 に答える
0
    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

オラクルで定義されているように、新しい行を挿入してテストできます。- トランザクション A が特定の条件を満たす行のセットを取得し、その後トランザクション B が行を挿入または更新して、行がトランザクション A の条件を満たしている場合に、ファントム読み取りが発生します。 、トランザクション A は後で条件付き検索を繰り返します。トランザクション A には追加の行が表示されます。この行はファントムと呼ばれます。上記のシナリオを回避し、TRANSACTION_SERIALIZABLE を使用しました。オラクルに最も厳格なロックを設定します。Oracle がサポートするトランザクション分離レベルは、TRANSACTION_READ_COMMITTED と TRANSACTION_SERIALIZABLE の 2 種類のみです。

于 2015-08-04T19:27:52.570 に答える
0

これは、前述のように、デフォルトの read-committed モードの標準的な動作です。WAIT コマンドは処理の遅延を引き起こすだけで、DB トランザクション処理へのリンクはありません。

問題を解決するには、次のいずれかを実行できます。

  1. 分離レベルをシリアル化可能に設定しますが、再試行で処理する必要がある ORA- エラーが発生する可能性があります。また、重大なパフォーマンス ヒットが発生する可能性があります。
  2. 最初に一時テーブルを使用して値を保存します
  3. データが大きすぎてメモリに収まらない場合は、RETURNING 句を使用してネストしたテーブルに BULK COLLECT INTO し、ネストしたテーブルに行が存在する場合にのみ削除できます。
于 2008-09-30T07:27:28.717 に答える
0

はいミラノ、トランザクションの分離レベルを指定していません。私はそれがどれであるか分からないデフォルトの分離レベルだと思います。Oracle 11g でも SQL Server 2005 でもありません。

さらに、WAIT コマンド中に (2 番目の接続で) 行われた INSERT は、トランザクション内ではありませんでした。このデータ損失を防ぐためにそうすべきでしたか?

于 2008-09-29T19:25:54.363 に答える