使用JDBC实例理解数据库的事务隔离
数据库的事务是用来处理数据的一个机制,作为一个整体,要么全部提交,要么全部回滚。
事务的4大特性(ACID):
原子性(Atomicity):事务是数据库的逻辑工作单位,它对数据库的修改要么全部执行,要么全部不执行。
一致性(Consistemcy):事务前后,数据库的状态都满足所有的完整性约束。
隔离性(Isolation):并发执行的N个事务是隔离的,一个事务不影响另一个事务,通过设置数据库的隔离级别,一个事务在没有commit之前,被修改的数据不可能被其他事务看到。
持久性(Durability):持久性意味着当系统或介质发生故障时,确保已提交事务的更新不能丢失。持久性主要在于DBMS的恢复性能。
今天重点理解事务的隔离性。
一、事务的隔离级别
事务的隔离级别是由底层的数据库实现的,通常有如下五种:
TRANSACTION_NONE:没有事务
TRANSACTION_READ_UNCOMMITTED:读取没有提交的事务
TRANSACTION_READ_COMMITTED:读取提交的事务
TRANSACTION_REPEATABLE_READ:不可重复读事务
TRANSACTION_SERIALIZABLE:序列化事务
他们都可以通过java.sql.Connection获取,对应的整数值分别为0、1、2、4、8,级别从低到高,低级别的隔离级一般支持更高的并发处理,并拥有更低的系统开销。
设置不同的级别会出现不同的问题:
1、 READ_UNCOMMITTED:会出现脏读、不可重复读和幻读问题;
2、READ_COMMITTED:会出现不可重复读和幻读问题;
3、REPEATABLE_READ:不会出现上面的问题,且允许事务的并发执行。
4、SERIALIZABLE:串行化,不会出现上面的问题,事务执行的时候不允许别的事务并发执行。事务串行化执行,事务只能一个接着一个地执行,而不能并发执行。一个事务在执行过程中完全看不到其他事务对数据库所做的更新。
我们以JDBC为例,设置不同的隔离级别,理解上述问题。
二、JDBC的事务处理
1、 获取事务的隔离级别
System.out.println(Connection.TRANSACTION_NONE);//0
System.out.println(Connection.TRANSACTION_READ_UNCOMMITTED);//1
System.out.println(Connection.TRANSACTION_READ_COMMITTED);//2
System.out.println(Connection.TRANSACTION_REPEATABLE_READ);//4
System.out.println(Connection.TRANSACTION_SERIALIZABLE);//8
2、 JDBC的开启事务
JDBC中默认是自动提交数据,只要把Connection的对象的setAutoCommit设为false,conn.setAutoCommit(false);由自动提交变为手动提交,就开启了事务。
3、 设置事务的隔离级别
conn.setTransactionIsolation(4);//参数为整数,不同的值代表不同的级别
4、 获取事务的隔离级别
conn.getTransactionIsolation()
用这种方法,可以获取 Orace数据库默认提供的是READ_COMMITTED隔离级别,MySQL是TRANSACTION_REPEATABLE_READ。
5、 事务处理
conn.commit();//事务提交
conn.rollback();//事务回滚
6、 关闭事务
conn.setAutoCommit(true)
三、脏读、不可重复读和幻读
1、可重复读:
A事务进行的过程中进行了一次读操作,这时B事务对此数据进行了Update修改,并commit,这时事务A再一次读取此条数据,读取到的将不是B事务修改后的值,而是原值,对原值可重复读。
2、不可重复读:
A事务进行的过程中进行了一次读操作,这时B事务对此数据进行了Update修改,并commit,这时事务A再一次读取此条数据,读取到的是B事务修改后的值,而原来的值不可以再读取到,所以叫做不可重复读。
不可重复读是指在同一个事务内,两个相同的查询返回了不同的结果,不可重复读的重点是修改,对某一行或者某一条数据进行修改,避免不可重复读需要锁行就行。
3、幻读:
事务T1读取一条指定where条件的语句,返回结果集。此时事务T2插入一行新记录,恰好满足T1的where条件。然后T1使用相同的条件再次查询,结果集中可以看到T2插入的记录,这条新纪录就是幻想。幻读的重点在于在表中新增或者删除 符合条件的整行,避免幻影读则需要锁表。
4、脏读:
脏读又称无效数据读出。一个事务读取另外一个事务还没有提交的数据叫脏读。
例如:事务T1修改了一行数据,但是还没有提交,这时候事务T2读取了被事务T1修改后的数据,之后事务T1因为某种原因Rollback了,那么事务T2读取的数据就是脏的。
解决办法:把数据库的事务隔离级别调整到READ_COMMITTED
四、JDBC实例理解事务的隔离级别
1、 脏读:
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class TestTxIsolation {
public static void main(String[] args) {
/**
* 脏读:把隔离级别设为1
* 一个A事务读取另一个B事务没有提交的数据,说明库里还没有改数据,但是A事务已经查询出该数据,没有和数据库保持一致,
*/
try {
Class.forName("com.mysql.jdbc.Driver");
Connection connA = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/test", "root", "root");
connA.setAutoCommit(false);// 开启了A事务
connA.setTransactionIsolation(2);// 设置事务的隔离级别为1,说明可以读取没有提交的数据
Connection connB = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/test", "root", "root");
connB.setAutoCommit(false);// 开启了B事务
String sql = "updateJava607 set name='唐飞' whereid=1";
PreparedStatement pstmtB = connB.prepareStatement(sql);
pstmtB.executeUpdate();
// B事务没有提交
// connB.commit();
// A事务查询数据
String sqlA = "select *from Java607";
PreparedStatement pstmtA = connA.prepareStatement(sqlA);
ResultSet rs = pstmtA.executeQuery();
while (rs.next()) {
System.out.println(rs.getInt("id") + " "
+ rs.getString("name"));
}
connB.setAutoCommit(true);// 关闭B事务
connA.setAutoCommit(true);// 关闭A事务
} catch (ClassNotFoundException e) {
// TODO Auto-generatedcatch block
e.printStackTrace();
} catch (SQLException e) {
// TODO Auto-generatedcatch block
e.printStackTrace();
}
}
}
2、 幻读
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class TestTxIsolation2 {
public static void main(String[] args) {
/**
* 幻读:如果隔离级别设为1、2
* A事务对表进行某种条件的查询操作,这时B事务插入或者删除符合某种条件的操作,A事务对表进行再次查询同样的操作,发现前后两次的结果不一样
* 解决方案:设置隔离级别设为4、8
* 只要锁定整张表
*/
try {
Class.forName("com.mysql.jdbc.Driver");
Connection connA = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/test", "root", "root");
connA.setAutoCommit(false);//开启了A事务
connA.setTransactionIsolation(2);//设置事务的隔离级别为1或2,会出现幻读
//A事务查询数据
String sqlA="select * from Java607";
PreparedStatementpstmtA=connA.prepareStatement(sqlA);
ResultSet rs= pstmtA.executeQuery();
while(rs.next()){
System.out.println(rs.getInt("id")+" "+rs.getString("name"));
}
Connection connB = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/test", "root", "root");
connB.setAutoCommit(false);//开启了B事务
String sql="insert into Java607 values(9,'打分7')";
PreparedStatementpstmtB=connB.prepareStatement(sql);
pstmtB.executeUpdate();
//B事务提交
connB.commit();
connB.setAutoCommit(true);//关闭B事务
//A事务再次查询数据
String sqlA2="select * from Java607";
PreparedStatementpstmtA2=connA.prepareStatement(sqlA2);
ResultSet rs2= pstmtA2.executeQuery();
System.out.println("=======");
while(rs2.next()){
System.out.println(rs2.getInt("id")+" "+rs2.getString("name"));
}
connB.setAutoCommit(true);//关闭B事务
connA.setAutoCommit(true);//关闭A事务
} catch (ClassNotFoundException e) {
// TODO Auto-generatedcatch block
e.printStackTrace();
} catch (SQLException e) {
// TODO Auto-generatedcatch block
e.printStackTrace();
}
}
}
3、 不可重复读
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class TestTxIsolation3 {
public static void main(String[] args) {
/**
*不可重复读隔离级别=1,2
*A事务查询某条记录的某个值,然后B事务修改这个值,A事务查询再一次查询,发现的得到的值已经不是以前那个值,所以叫不可重复读
*解决方案:设置隔离级别设为4。8
*只要锁定操作的一条记录
*/
try {
Class.forName("com.mysql.jdbc.Driver");
Connection connA = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/test", "root", "root");
connA.setAutoCommit(false);//开启了A事务
connA.setTransactionIsolation(8);//设置事务的隔离级别为1,
//A事务查询数据
String sqlA="select name from java607 where id=1";
PreparedStatementpstmtA=connA.prepareStatement(sqlA);
ResultSet rs= pstmtA.executeQuery();
while(rs.next()){
System.out.println(rs.getString("name"));
}
Connection connB = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/test", "root", "root");
connB.setAutoCommit(false);//开启了B事务
String sql="update Java607 set name='new3' where id=1";
PreparedStatementpstmtB=connB.prepareStatement(sql);
pstmtB.executeUpdate();
//B事务提交
connB.commit();
//connB.close();
connB.setAutoCommit(true);//关闭B事务
//A事务查询数据
String sqlA2="select name from java607 where id=1";
PreparedStatementpstmtA2=connA.prepareStatement(sqlA2);
ResultSet rs2= pstmtA2.executeQuery();
while(rs2.next()){
System.out.println(rs2.getString("name"));
}
connB.setAutoCommit(true);//关闭A事务
connA.setAutoCommit(true);//关闭A事务
} catch (ClassNotFoundException e) {
// TODO Auto-generatedcatch block
e.printStackTrace();
} catch (SQLException e) {
// TODO Auto-generatedcatch block
e.printStackTrace();
}
}
}