交易隔离问题或错误的方法?
-
02-07-2019 - |
题
我是帮助了一些同事的矿SQL问题。主要是他们想要移动的所有行从表A至表B(两个表格的具有相同的列(姓名和类型)).虽然这样做是在Oracle11g我不认为它的真正问题。
他们最初的天真实的东西喜欢
BEGIN
INSERT INTO B SELECT * FROM A
DELETE FROM A
COMMIT;
END
他们的关切是,如果有插入表一期间复制的,从A到B和",删除从"(或截断什么是值得)会造成数据丢失(具有较新插入的行中删除)。
当然,我迅速建议存放身份证的复制的行在临时的表,然后删除的仅仅是行匹配ID在临时表格。
然而,对于好奇心的缘故我们提出了一个小测试通过加入一个等待命令(不记得PL/SQL法)之间插入和删除。然后从一个不同方面,我们将插入行 在等待的.
我们观察到,这是一个数据丢失通过这样做。我再现整个上下文中SQL服务器和包裹它的所有交易但仍然发新的数据丢失了太在SQL服务器。这使我认为是有系统错误/缺陷在最初的方法。
但是我不能告诉我们,如果它是一个事实,即交易是不是(不知?) 隔离新插入或事实,即插入了在等待命令。
在结束,这是实现使用的临时表建议通过我但是我们不能得到的回答"为什么数据的损失"。你知道为什么吗?
解决方案
根据你的隔离水平,选择的所有行表不防止新插入,则将只是锁定的行阅读。在SQL服务器上,如果您使用的序列化的隔离水平,那么它将防止新的行如果他们已经包括在你选择的查询。
http://msdn.microsoft.com/en-us/library/ms173763.aspx -
序列化 指定如下:
发言无法读取的数据,已被修改,但尚未致力于通过其他交易。
没有任何其他交易可以修改数据,已经读通过的当前事务,直到当前的事务完成。
其他交易中不能插入新的排与关键的价值观,将属范围内的钥匙读通过的任何陈述的当前事务,直到当前的事务完成。
其他提示
我不能谈论事务稳定性,但另一种方法是从存在的源表中删除第二步(从目标表中选择ID)。
原谅语法,我没有测试过这段代码,但你应该能够理解:
INSERT INTO B SELECT * FROM A;
DELETE FROM A WHERE EXISTS (SELECT B.<primarykey> FROM B WHERE B.<primarykey> = A.<primarykey>);
通过这种方式,您使用关系引擎强制执行不会删除任何新数据,并且您无需在事务中执行这两个步骤。
更新:更正了子查询中的语法
这可以通过以下方式在Oracle中实现:
Alter session set isolation_level=serializable;
可以使用EXECUTE IMMEDIATE:
在PL / SQL中设置BEGIN
EXECUTE IMMEDIATE 'Alter session set isolation_level=serializable';
...
END;
这只是交易的工作方式。您必须为手头的任务选择正确的隔离级别。
您正在同一个事务中执行INSERT和DELETE。您没有提到事务正在使用的隔离模式,但它可能是“已提交读取”。这意味着DELETE命令将查看同时提交的记录。对于这种工作,使用'快照'类型的事务要好得多,因为INSERT和DELETE都会知道同一组记录 - 只有那些而不是其他。
我不知道这是否相关,但在SQL Server中语法是
begin tran
....
commit
不只是'开始'
您需要设置事务隔离级别,以便其他事务的插入不会影响您的事务。我不知道如何在Oracle中这样做。
在Oracle中,默认事务隔离级别是读取提交的。这基本上意味着Oracle会在查询开始时返回SCN(系统更改号)中存在的结果。将事务隔离级别设置为可序列化意味着在事务开始时捕获SCN,以便事务中的所有查询都返回该SCN的数据。无论其他会话和事务正在做什么,这都可确保一致的结果。另一方面,由于其他事务正在执行的活动,Oracle可能会确定它无法序列化您的事务,因此您可能需要处理这类错误。
Tony与AskTom讨论的链接更详细地介绍了所有这些 - 我强烈推荐它。
是米兰,我没有指定事务隔离级别。我想这是默认的隔离级别,我不知道它是什么。无论是在Oracle 11g还是在SQL Server 2005中。
此外,在WAIT命令期间(在第二个连接上)进行的INSERT在事务中是 NOT 。应该是为了防止这种数据丢失吗?
这是默认读取提交模式的标准行为,如上所述。 WAIT命令只会导致处理延迟,没有任何数据库事务处理的链接。
要解决此问题,您可以:
- 将隔离级别设置为可序列化,但是您可以获得ORA-错误,您需要重试这些错误!此外,您可能会受到严重影响。
- 使用临时表来存储值
- 如果数据不是太大而无法容纳到内存中,则可以使用RETURNING子句将BULK COLLECT IN到嵌套表中,并仅在嵌套表中存在该行时才删除。 醇>
或者,您可以使用快照隔离来检测丢失的更新:
I have written a sample code:-
First run this on Oracle DB:-
Create table AccountBalance
(
id integer Primary Key,
acctName varchar2(255) not null,
acctBalance integer not null,
bankName varchar2(255) not null
);
insert into AccountBalance values (1,'Test',50000,'Bank-a');
Now run the below code
package com.java.transaction.dirtyread;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class DirtyReadExample {
/**
* @param args
* @throws ClassNotFoundException
* @throws SQLException
* @throws InterruptedException
*/
public static void main(String[] args) throws ClassNotFoundException, SQLException, InterruptedException {
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection connectionPayment = DriverManager.getConnection(
"jdbc:oracle:thin:@localhost:1521:xe", "hr",
"hr");
Connection connectionReader = DriverManager.getConnection(
"jdbc:oracle:thin:@localhost:1521:xe", "hr",
"hr");
try {
connectionPayment.setAutoCommit(false);
connectionPayment.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
} catch (SQLException e) {
e.printStackTrace();
}
Thread pymtThread=new Thread(new PaymentRunImpl(connectionPayment));
Thread readerThread=new Thread(new ReaderRunImpl(connectionReader));
pymtThread.start();
Thread.sleep(2000);
readerThread.start();
}
}
package com.java.transaction.dirtyread;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class ReaderRunImpl implements Runnable{
private Connection conn;
private static final String QUERY="Select acctBalance from AccountBalance where id=1";
public ReaderRunImpl(Connection conn){
this.conn=conn;
}
@Override
public void run() {
PreparedStatement stmt =null;
ResultSet rs =null;
try {
stmt = conn.prepareStatement(QUERY);
System.out.println("In Reader thread --->Statement Prepared");
rs = stmt.executeQuery();
System.out.println("In Reader thread --->executing");
while (rs.next()){
System.out.println("Balance is:" + rs.getDouble(1));
}
System.out.println("In Reader thread --->Statement Prepared");
Thread.sleep(5000);
stmt.close();
rs.close();
stmt = conn.prepareStatement(QUERY);
rs = stmt.executeQuery();
System.out.println("In Reader thread --->executing");
while (rs.next()){
System.out.println("Balance is:" + rs.getDouble(1));
}
stmt.close();
rs.close();
stmt = conn.prepareStatement(QUERY);
rs = stmt.executeQuery();
System.out.println("In Reader thread --->executing");
while (rs.next()){
System.out.println("Balance is:" + rs.getDouble(1));
}
} catch (SQLException | InterruptedException e) {
e.printStackTrace();
}finally{
try {
stmt.close();
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
package com.java.transaction.dirtyread;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class PaymentRunImpl implements Runnable{
private Connection conn;
private static final String QUERY1="Update AccountBalance set acctBalance=40000 where id=1";
private static final String QUERY2="Update AccountBalance set acctBalance=30000 where id=1";
private static final String QUERY3="Update AccountBalance set acctBalance=20000 where id=1";
private static final String QUERY4="Update AccountBalance set acctBalance=10000 where id=1";
public PaymentRunImpl(Connection conn){
this.conn=conn;
}
@Override
public void run() {
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement(QUERY1);
stmt.execute();
System.out.println("In Payment thread --> executed");
Thread.sleep(3000);
stmt = conn.prepareStatement(QUERY2);
stmt.execute();
System.out.println("In Payment thread --> executed");
Thread.sleep(3000);
stmt = conn.prepareStatement(QUERY3);
stmt.execute();
System.out.println("In Payment thread --> executed");
stmt = conn.prepareStatement(QUERY4);
stmt.execute();
System.out.println("In Payment thread --> executed");
Thread.sleep(5000);
//case 1
conn.rollback();
System.out.println("In Payment thread --> rollback");
//case 2
//conn.commit();
// System.out.println("In Payment thread --> commit");
} catch (SQLException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
Output:-
In Payment thread --> executed
In Reader thread --->Statement Prepared
In Reader thread --->executing
Balance is:50000.0
In Reader thread --->Statement Prepared
In Payment thread --> executed
In Payment thread --> executed
In Payment thread --> executed
In Reader thread --->executing
Balance is:50000.0
In Reader thread --->executing
Balance is:50000.0
In Payment thread --> rollback
你可以通过插入oracle定义的新行来测试它: - 当事务A检索满足给定条件的一组行时,发生虚拟读取,事务B随后插入或更新行,使得该行现在满足事务A中的条件,并且事务A稍后重复条件检索。事务A现在看到一个额外的行。这一行被称为幻像。它将避免上述场景以及我使用TRANSACTION_SERIALIZABLE。它将对Oracle设置最严格的锁定。 Oracle仅支持2种类型的事务隔离级别: - TRANSACTION_READ_COMMITTED和TRANSACTION_SERIALIZABLE。