我正在帮助我的一些同事解决SQL问题。主要是他们想要将表A中的所有行移动到表B(两个表都具有相同的列(名称和类型))。虽然这是在Oracle 11g中完成的,但我认为这并不重要。
他们最初的天真实施就像是
BEGIN
INSERT INTO B SELECT * FROM A
DELETE FROM A
COMMIT;
END
他们担心的是,如果在从A到B复制期间对表A进行了INSERT,并且“DELETE FROM A”(或者值得的TRUNCATE)会导致数据丢失(将A中较新的插入行删除)。
当然,我很快建议将复制行的ID存储在临时表中,然后只删除A中与临时表中的IDS匹配的行。
但是出于好奇,我们通过在INSERT和DELETE之间添加一个等待命令(不记得PL / SQL语法)来进行一些测试。从不同的连接我们将在等待时插入行 。
我们通过这样做观察到数据丢失。我在SQL Server中重现了整个上下文并将其全部包含在一个事务中,但在SQL Server中仍然丢失了新的新数据。这让我觉得初始方法存在系统性错误/缺陷。
但是我无法判断TRANSACTION是否(不知何故?)与新的INSERT隔离或者在WAIT命令期间INSERT出现这一事实。
最后,它是使用我建议的临时表实现的,但我们无法得到“为何数据丢失”的答案。你知道为什么吗?
答案 0 :(得分:8)
根据您的隔离级别,从表中选择所有行不会阻止新插入,它只会锁定您读取的行。在SQL Server中,如果使用Serializable隔离级别,那么它将阻止新行(如果它们包含在您的选择查询中)。
http://msdn.microsoft.com/en-us/library/ms173763.aspx -
SERIALIZABLE 指定以下内容:
语句无法读取已修改但尚未由其他交易提交的数据。
在当前交易完成之前,没有其他交易可以修改当前交易所读取的数据。
其他事务无法插入新行,其键值将落在当前事务中任何语句读取的键范围内,直到当前事务完成为止。
答案 1 :(得分:7)
我不能谈论事务稳定性,但另一种方法是从存在的源表中删除第二步(从目标表中选择ID)。
原谅语法,我没有测试过这段代码,但你应该能够理解:
INSERT INTO B SELECT * FROM A;
DELETE FROM A WHERE EXISTS (SELECT B.<primarykey> FROM B WHERE B.<primarykey> = A.<primarykey>);
通过这种方式,您使用关系引擎强制执行不会删除任何新数据,并且您无需在事务中执行这两个步骤。
更新:更正了子查询中的语法
答案 2 :(得分:5)
这可以通过以下方式在Oracle中实现:
Alter session set isolation_level=serializable;
可以使用EXECUTE IMMEDIATE:
在PL / SQL中设置BEGIN
EXECUTE IMMEDIATE 'Alter session set isolation_level=serializable';
...
END;
答案 3 :(得分:2)
这只是交易的工作方式。您必须为手头的任务选择正确的隔离级别。
您正在同一个事务中执行INSERT和DELETE。您没有提到事务正在使用的隔离模式,但它可能是“已提交读取”。这意味着DELETE命令将查看同时提交的记录。对于这种工作,使用'快照'类型的事务要好得多,因为INSERT和DELETE都会知道同一组记录 - 只有那些记录而不是其他记录。
答案 4 :(得分:1)
我不知道这是否相关,但在SQL Server中语法是
begin tran
....
commit
不只是'开始'
答案 5 :(得分:1)
您需要设置事务隔离级别,以便其他事务的插入不会影响您的事务。我不知道如何在Oracle中这样做。
答案 6 :(得分:1)
在Oracle中,默认事务隔离级别是读取提交的。这基本上意味着Oracle会在查询开始时返回SCN(系统更改号)中存在的结果。将事务隔离级别设置为可序列化意味着在事务开始时捕获SCN,以便事务中的所有查询都返回该SCN的数据。无论其他会话和事务正在做什么,这都可确保一致的结果。另一方面,由于其他事务正在执行的活动,Oracle可能会确定它无法序列化您的事务,因此您可能需要处理这类错误。
Tony与AskTom讨论的链接更详细地介绍了所有这些 - 我强烈推荐它。
答案 7 :(得分:0)
是米兰,我没有指定事务隔离级别。我想这是默认的隔离级别,我不知道它是什么。无论是在Oracle 11g还是在SQL Server 2005中。
此外,在WAIT命令期间(在第二个连接上)进行的INSERT在事务中是 NOT 。应该是为了防止这种数据丢失吗?
答案 8 :(得分:0)
这是默认读取提交模式的标准行为,如上所述。 WAIT命令只会导致处理延迟,没有任何数据库事务处理的链接。
要解决此问题,您可以:
答案 9 :(得分:0)
或者,您可以使用快照隔离来检测丢失的更新:
答案 10 :(得分:0)
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。