试图了解oracle TRANSACTION_SERIALIZABLE级别

时间:2016-06-27 15:27:08

标签: java sql multithreading oracle transaction-isolation

我正在尝试在SQL DB上编写方法,其工作方式与ConcurrentMap.putIfAbsent相同,其中'value'用作键,'id'用作值。这种方法的主要限制是保持整个表中的值唯一。

以下是此方法的示例。调用sync.yield()将控制传递给其他线程。它被添加以实现必要的并行线程执行。

import java.sql.*;
import java.util.concurrent.atomic.AtomicInteger;


public class Main {

private static final String USER = "";
private static final String PASS = USER;
private static final String URL = "jdbc:oracle:thin:@192.168.100.160:1521:main";

private static final AtomicInteger id = new AtomicInteger();
private static final Sync sync = new Sync(1);

static Connection getConnection() throws Exception {
    Class.forName("oracle.jdbc.driver.OracleDriver");
    Connection c = DriverManager.getConnection(URL, USER, PASS);
    c.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
    return c;
}

static long putIfAbsent(String value) throws Exception {
    Connection c = getConnection();

    PreparedStatement checkSt = c.prepareStatement("SELECT id from test WHERE value = ?");
    checkSt.setString(1, value);
    ResultSet rs = checkSt.executeQuery();

    if (rs.next())
        return rs.getLong(1);

    System.out.println(Thread.currentThread() + " did not find value");

    sync.yield();

    long id = getId();
    System.out.println(Thread.currentThread() + " prepare to insert value with id " + id);
    PreparedStatement updateSt = c.prepareStatement("INSERT INTO test VALUES (?, ?)");
    updateSt.setLong(1, id);
    updateSt.setString(2, value);

    updateSt.executeQuery();
    c.commit();
    c.close();

    return id;
}

public static void main(String[] args) {

    Runnable r = () -> {
        try {
            System.out.println(Thread.currentThread() + " commit success and return id = " + putIfAbsent("val"));
            sync.yield();
        } catch (Exception e) {
            e.printStackTrace();
        }
    };

    Thread t1 = new Thread(r);
    Thread t2 = new Thread(r);

    t1.start();
    t2.start();
}

static long getId() {
    return id.incrementAndGet();
}

}

当我在空表上运行main方法时,我得到了这个控制台输出:

Thread[Thread-0,5,main] did not find value
Thread[Thread-1,5,main] did not find value
Thread[Thread-0,5,main] prepare to insert value with id 1
Thread[Thread-0,5,main] commit success and return id = 1
Thread[Thread-1,5,main] prepare to insert value with id 2
Thread[Thread-1,5,main] commit success and return id = 2

我可以解释前五行。但不能第六。 当thread-1执行更新时,它依赖于SELECT id from test WHERE value = ?具有空结果。此结果与当前的DB状态不一致。所以,我希望ORA-08177: Cannot serialize access for this transaction

我正在使用Sync类的这种命令(它在线程的对象上保留链接引用):

import java.util.ArrayDeque;
import java.util.HashSet;
import java.util.Queue;
import java.util.Set;

public class Sync {
    private final Object lock = new Object();
    private final Queue<Thread> sleepingTh = new ArrayDeque<>();
    private final Set<Thread> activeTh = new HashSet<>();

    private final int threads;

    public Sync(int threads) {
        this.threads = threads;
    }

    public void yield() {
        final Thread ct = Thread.currentThread();

        synchronized (lock) {
            sleepingTh.add(ct);
            activeTh.remove(ct);

            if (sleepingTh.size() > threads) {
                Thread t = sleepingTh.poll();
                activeTh.add(t);
                lock.notifyAll();
            }

            while (!activeTh.contains(ct)) {
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                }
            }
        }
    }

    public void wakeUpAll() {
        synchronized (lock) {
            activeTh.addAll(sleepingTh);
            sleepingTh.clear();
            lock.notifyAll();
        }
    }
}

创建表格的声明:

create table test(
id number(16),
value varchar(50)
);

我使用jdk1.8.0_60,Oracle JDBC 10.2.0.4.0和Oracle DB 11g2

1 个答案:

答案 0 :(得分:0)

文档==&gt; click说:

  

可序列化隔离级别   ..............   
  ..............   
  Oracle数据库允许可序列化事务修改行   仅当已经对其他交易所做的行进行更改时   在序列化事务开始时提交。 数据库   可序列化事务尝试更新时生成错误或   删除之后提交的由其他事务更改的数据   可序列化的交易开始了:

     

ORA-08177:无法序列化此交易的访问权限

您的代码仅执行 INSERT 语句 它不会尝试更新删除由其他交易更改的数据,因此ORA-08177不会发生。

----编辑--------------

  

你能提出建议吗,我怎样才能重写方法?

只需在value列上创建一个独特的包含物 在代码中只需直接INSERT语句 如果成功 - 这意味着该行尚未存在 如果if失败(重复键异常) - 这意味着该行已经存在,在这种情况下,简单地忽略错误。

  

SQL-92是否允许这种行为?

是的,当然。
SQL-92只定义了三种读取现象,详见此链接:Isolation (database systems)

    当允许事务从已被另一个正在运行的事务修改但尚未提交的行读取数据时,会发生
  • 脏读(也称为未提交的依赖关系)。
  • 在课程期间发生不可重复的阅读 事务,检索两次行和行内的值 阅读之间有所不同。
  • 在交易过程中发生两次虚拟读取 执行相同的查询,并返回行集合 第二个查询与第一个查询不同。

在可序列化的隔离级别中,可能会出现的上述现象。 就这样。交易无法看到来自其他交易的任何更改 在你的代码中,这一切都是正确的。会话无法看到由另一个会话插入的行,因为在此隔离级别中不会出现幻像现象和脏读现象。