事务隔离问题还是错误的做法?

时间:2008-09-29 19:09:49

标签: sql sql-server oracle transactions database

我正在帮助我的一些同事解决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存储在临时表中,然后只删除A中与临时表中的IDS匹配的行。

但是出于好奇,我们通过在INSERT和DELETE之间添加一个等待命令(不记得PL / SQL语法)来进行一些测试。从不同的连接我们将在等待时插入行

我们通过这样做观察到数据丢失。我在SQL Server中重现了整个上下文并将其全部包含在一个事务中,但在SQL Server中仍然丢失了新的新数据。这让我觉得初始方法存在系统性错误/缺陷。

但是我无法判断TRANSACTION是否(不知何故?)与新的INSERT隔离或者在WAIT命令期间INSERT出现这一事实。

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

11 个答案:

答案 0 :(得分:8)

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

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

SERIALIZABLE 指定以下内容:

  • 语句无法读取已修改但尚未由其他交易提交的数据。

  • 在当前交易完成之前,没有其他交易可以修改当前交易所读取的数据。

  • 其他事务无法插入新行,其键值将落在当前事务中任何语句读取的键范围内,直到当前事务完成为止。

答案 1 :(得分:7)

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

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

INSERT INTO B SELECT * FROM A;

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

通过这种方式,您使用关系引擎强制执行不会删除任何新数据,并且您无需在事务中执行这两个步骤。

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

答案 2 :(得分:5)

这可以通过以下方式在Oracle中实现:

Alter session set isolation_level=serializable;

可以使用EXECUTE IMMEDIATE:

在PL / SQL中设置
BEGIN
    EXECUTE IMMEDIATE 'Alter session set isolation_level=serializable';
    ...
END;

请参阅Ask Tom: On Transaction Isolation Levels

答案 3 :(得分:2)

这只是交易的工作方式。您必须为手头的任务选择正确的隔离级别。

您正在同一个事务中执行INSERT和DELETE。您没有提到事务正在使用的隔离模式,但它可能是“已提交读取”。这意味着DELETE命令将查看同时提交的记录。对于这种工作,使用'快照'类型的事务要好得多,因为INSERT和DELETE都会知道同一组记录 - 只有那些记录而不是其他记录。

答案 4 :(得分:1)

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

begin tran
....
commit

不只是'开始'

答案 5 :(得分:1)

您需要设置事务隔离级别,以便其他事务的插入不会影响您的事务。我不知道如何在Oracle中这样做。

答案 6 :(得分:1)

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

Tony与AskTom讨论的链接更详细地介绍了所有这些 - 我强烈推荐它。

答案 7 :(得分:0)

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

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

答案 8 :(得分:0)

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

要解决此问题,您可以:

  1. 将隔离级别设置为可序列化,但是您可以获得ORA-错误,您需要重试这些错误!此外,您可能会受到严重影响。
  2. 使用临时表来存储值
  3. 如果数据不是太大而无法放入内存,则可以使用RETURNING子句将BULK COLLECT IN到嵌套表中,并仅在嵌套表中存在该行时才删除。

答案 9 :(得分:0)

或者,您可以使用快照隔离来检测丢失的更新:

When Snapshot Isolation Helps and When It Hurts

答案 10 :(得分: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

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