试图理解 oracle TRANSACTION_SERIALIZABLE 级别

Trying to understand oracle TRANSACTION_SERIALIZABLE level

我正在尝试在 SQL 数据库上编写方法,它的工作方式类似于 ConcurrentMap.putIfAbsent,其中 'value' 作为键,'id' 作为值。此方法的主要约束是在整个 table.

中保持值的唯一性

下面是我的这个方法的例子。 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 方法为空时 table 我得到这个控制台输出:

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

我可以解释前五行。但不能第六。 当线程 1 执行更新时,它依赖于 SELECT id from test WHERE value = ? 有空结果。此结果与当前数据库状态不一致。所以,我预计 ORA-08177: Cannot serialize access for this transaction.

我正在使用 Sync class(它保持 link 对线程对象的引用):

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();
        }
    }
}

创建语句 table:

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

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

文档 ==> click 说:

Serializable Isolation Level
..............
..............
Oracle Database permits a serializable transaction to modify a row only if changes to the row made by other transactions were already committed when the serializable transaction began. The database generates an error when a serializable transaction tries to update or delete data changed by a different transaction that committed after the serializable transaction began:

ORA-08177: Cannot serialize access for this transaction

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


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


do you can give advice, how I can rewrite method?

只需在 value 列上创建一个唯一的容器。
在代码中直接执行 INSERT 语句。
如果成功 - 这意味着该行还不存在
If if 失败(重复键异常)——这意味着该行已经存在,在这种情况下简单地忽略错误。

does SQL-92 allow this behavior?

当然可以。
SQL-92只定义三种读现象,详见这个link:Isolation (database systems)

  • 脏读(又名未提交的依赖项)发生在允许事务从已被另一个 运行 事务修改但尚未提交的行中读取数据时.
  • 发生不可重复读取,当在 事务,一行被检索两次,行中的值 读数之间存在差异。
  • 幻读发生在事务处理过程中,两个 执行相同的查询,并返回行的集合 第二个查询与第一个不同。

在serializable isolation levels none中可能会出现上述现象。 就这样。事务看不到其他事务的任何更改。
在您的代码中,这无论如何都是正确的。本会话看不到另一个会话插入的行,因为幻读现象和脏读现象都不能在这个隔离级别发生。