我正在尝试在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
答案 0 :(得分:0)
文档==&gt; click说:
可序列化隔离级别 ..............
..............
Oracle数据库允许可序列化事务修改行 仅当已经对其他交易所做的行进行更改时 在序列化事务开始时提交。 数据库 可序列化事务尝试更新时生成错误或 删除之后提交的由其他事务更改的数据 可序列化的交易开始了:ORA-08177:无法序列化此交易的访问权限
您的代码仅执行 INSERT 语句 它不会尝试更新或删除由其他交易更改的数据,因此ORA-08177不会发生。
----编辑--------------
你能提出建议吗,我怎样才能重写方法?
只需在value
列上创建一个独特的包含物
在代码中只需直接INSERT
语句
如果成功 - 这意味着该行尚未存在
如果if失败(重复键异常) - 这意味着该行已经存在,在这种情况下,简单地忽略错误。
SQL-92是否允许这种行为?
是的,当然。
SQL-92只定义了三种读取现象,详见此链接:Isolation (database systems)
在可序列化的隔离级别中,可能会出现无的上述现象。 就这样。交易无法看到来自其他交易的任何更改 在你的代码中,这一切都是正确的。会话无法看到由另一个会话插入的行,因为在此隔离级别中不会出现幻像现象和脏读现象。