繁体   English   中英

试图了解Oracle TRANSACTION_SERIALIZABLE级别

[英]Trying to understand oracle TRANSACTION_SERIALIZABLE level

我正在尝试在SQL DB上编写类似于ConcurrentMap.putIfAbsent的方法,其中“值”用作键,“ 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

我可以解释前五行。 但不能第六。 当线程1执行更新时,它依赖SELECT id from test WHERE value = ? 结果为空。 该结果与当前的数据库状态不一致。 因此,我期望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

文档==> 单击表示:

可序列化的隔离级别
..............
..............
只有在可序列化事务开始时已经提交了对其他事务所做的行更改,Oracle数据库才允许可序列化事务修改行。 当可序列化事务尝试更新或删除由可序列化事务开始后提交的另一个事务更改的数据时,数据库会生成错误

ORA-08177:无法序列化对此事务的访问

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


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


你能给我建议,我该如何改写方法?

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

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

当然是。
SQL-92仅定义三种读取现象,有关详细信息,请参见此链接: 隔离(数据库系统)

  • 当允许一个事务从已经被另一个正在运行的事务修改但尚未提交的行中读取数据时,就会发生脏读 (也称为未提交的依赖关系)。
  • 发生不可重复的读取 ,这是因为在事务过程中,一行被检索两次,并且两次读取之间的值不同。
  • 当在事务过程中执行两个相同的查询,并且第二个查询返回的行集合与第一个查询不同时,将发生幻像读取

在可序列化的隔离级别中, 不会发生上述现象。 就这样。 该事务看不到其他事务的任何更改。
在您的代码中,这绝对是正确的。 该会话看不到另一个会话插入的行,因为在此隔离级别中,幻象现象和脏读取现象均不会发生。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM