试图理解 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中可能会出现上述现象。
就这样。事务看不到其他事务的任何更改。
在您的代码中,这无论如何都是正确的。本会话看不到另一个会话插入的行,因为幻读现象和脏读现象都不能在这个隔离级别发生。
我正在尝试在 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中可能会出现上述现象。
就这样。事务看不到其他事务的任何更改。
在您的代码中,这无论如何都是正确的。本会话看不到另一个会话插入的行,因为幻读现象和脏读现象都不能在这个隔离级别发生。