7

我正在帮助我的一些同事解决 SQL 问题。主要是他们想将所有行从表 A 移动到表 B(两个表具有相同的列(名称和类型))。尽管这是在 Oracle 11g 中完成的,但我认为这并不重要。

他们最初的幼稚实现类似于

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

他们担心的是,在从 A 复制到 B 的过程中是否对表 A 进行了 INSERT,并且“从 A 中删除”(或 TRUNCATE 是值得的)会导致数据丢失(删除 A 中较新的插入行)。

当然,我很快建议将复制行的 ID 存储在临时表中,然后仅删除 A 中与临时表中的 IDS 匹配的行。

然而,为了好奇,我们通过在 INSERT 和 DELETE 之间添加一个等待命令(不记得 PL/SQL 语法)进行了一个小测试。然后从不同的连接中,我们将在 WAIT 期间插入行。

我们观察到这样做会导致数据丢失。我在 SQL Server 中复制了整个上下文,并将其全部包装在一个事务中,但新数据仍然在 SQL Server 中丢失了。这让我认为最初的方法存在系统性错误/缺陷。

但是,我不知道是因为 TRANSACTION 没有(以某种方式?)与新的 INSERT 隔离,还是因为 INSERT 在 WAIT 命令期间出现。

最后它是使用我建议的临时表实现的,但我们无法得到“为什么数据丢失”的答案。你知道为什么吗?

4

11 回答 11

8

根据您的隔离级别,从表中选择所有行不会阻止新的插入,它只会锁定您读取的行。在 SQL Server 中,如果您使用 Serializable 隔离级别,那么它将阻止新行(如果它们已包含在您的选择查询中)。

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

SERIALIZABLE 指定以下内容:

  • 语句不能读取已被其他事务修改但尚未提交的数据。

  • 在当前事务完成之前,任何其他事务都不能修改当前事务已读取的数据。

  • 在当前事务完成之前,其他事务不能插入键值落在当前事务中的任何语句读取的键范围内的新行。

于 2008-09-29T19:26:06.533 回答
7

我不能谈论事务的稳定性,但另一种方法是从存在的源表中删除第二步(从目标表中选择 id)。

原谅语法,我没有测试过这段代码,但你应该能明白:

INSERT INTO B SELECT * FROM A;

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

这样,您就可以使用关系引擎来强制不会删除较新的数据,并且您不需要在事务中执行这两个步骤。

更新:子查询中的更正语法

于 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

我不知道这是否相关,但在 SQL Server 中,语法是

begin tran
....
commit

不只是“开始”

于 2008-09-29T19:13:01.557 回答
1

您需要设置事务隔离级别,以便来自另一个事务的插入不会影响您的事务。我不知道如何在 Oracle 中做到这一点。

于 2008-09-29T19:14:10.727 回答
1

在 Oracle 中,默认的事务隔离级别是读提交。这基本上意味着当您的查询开始时,Oracle 会返回 SCN(系统更改号)中存在的结果。将事务隔离级别设置为可序列化意味着 SCN 在事务开始时被捕获,因此事务中的所有查询都返回该 SCN 的数据。无论其他会话和事务正在做什么,这都能确保一致的结果。另一方面,由于其他事务正在执行的活动,Oracle 可能会确定它无法序列化您的事务,因此您将不得不处理此类错误,这可能会产生成本。

Tony 与 AskTom 讨论的链接提供了更多关于这一切的详细信息——我强烈推荐它。

于 2008-09-29T20:27:41.690 回答
0

是的,米兰,我没有指定事务隔离级别。我想这是默认的隔离级别,我不知道它是什么。在 Oracle 11g 和 SQL Server 2005 中都没有。

此外,在 WAIT 命令期间(在第二个连接上)进行的 INSERT 不在事务中。应该是为了防止这种数据丢失吗?

于 2008-09-29T19:25:54.363 回答
0

如上所述,这是默认已提交读模式的标准行为。WAIT 命令只会导致处理延迟,没有任何数据库事务处理的链接。

要解决此问题,您可以:

  1. 将隔离级别设置为可序列化,但随后您会收到 ORA- 错误,您需要通过重试来处理这些错误!此外,您可能会受到严重的性能影响。
  2. 首先使用临时表存储值
  3. 如果数据不是太大而无法放入内存,则可以使用 RETURNING 子句将 BULK COLLECT INTO 嵌套表中,并且仅当嵌套表中存在该行时才删除。
于 2008-09-30T07:27:28.717 回答
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

U 可以通过插入由 oracle 定义的新行来测试它:- 当事务 A 检索一组满足给定条件的行时发生幻读,事务 B 随后插入或更新一行,使得该行现在满足事务 A 中的条件, 事务 A 稍后重复条件检索。事务 A 现在看到一个额外的行。该行称为幻像。它将避免上述情况以及我使用了 TRANSACTION_SERIALIZABLE。它将对 Oracle 设置最严格的锁定。Oracle 仅支持 2 种类型的事务隔离级别:- TRANSACTION_READ_COMMITTED 和 TRANSACTION_SERIALIZABLE。

于 2015-08-04T19:27:52.570 回答