java与mysql的事务隔离级别
根据经验,我多年的开发中没有用代码控制过事务的隔离级别,在生产系统也没有出任何问题。
其实答案就是,使用默认的隔离级别,没有问题。但面试就是要考你知不知道理论。
但为了说明这个答案和我将来的面试官,我要做一下试验。
首先介绍一下基本概念,工作上用不上(默认的隔离级别不会出现这些问题),理论上有用。
其中的一些概念的说明:
脏读: 事务1读数据,读到了事务2并未提交的数据。如果事务2未提交成功,那么事务1读的数据就是"脏"数据。
不可重复读: 事务1读到了id=1的name值为1,事务2修改id=1的name值为2,事务1再读id=1的name值为2。事务1两次读id=1的结果不一样,这就是不可重复读。
幻读:事务2的操作导致事务1前后两次查询到的结果数据量不同。
环境准备:
查询mysql默认的数据库隔离级别:
select @@tx_isolation;
创建测试表
CREATE TABLE `test_tran` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(45) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8
初始化一条数据(id=1):
INSERT INTO test_tran (`name`) VALUES ('init');
1.测试脏读。一个事务如何读取到另一个事务修改且未提交的数据。
代码:
import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; public class TestDirtRead { public static void main(String[] args) { try { Class.forName("com.mysql.jdbc.Driver"); Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/xx?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true", "root", "xx"); //conn.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ); //conn.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED); //1 conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); conn.setAutoCommit(false); //开启另一事务修改id=1 Connection conn2=startOtherTran(); Statement stm=conn.createStatement(); ResultSet rs=stm.executeQuery("select name from test_tran where id=1"); while(rs.next()) { String name=rs.getString("name"); System.out.println(name); } //打断点,第一种TRANSACTION_READ_UNCOMMITTED可以看到读到了"otherTran",就是另一个事务里的值。 //打断点,第一种TRANSACTION_READ_COMMITTED可以看到读到了"init",就是初始值。 conn.commit(); }catch(Exception e) { e.printStackTrace(); } } public static Connection startOtherTran() throws Exception{ Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/xx?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true", "root", "xx"); //conn.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED); conn.setAutoCommit(false); Statement stm=conn.createStatement(); stm.execute("update test_tran set name='otherTran' where id=1"); return conn; } }
结论:将第一个事务设置事务隔离级别为conn.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);即可产生脏读效果,设置为Connection.TRANSACTION_READ_COMMITTED就没有脏读效果了。
疑问,如果把另一个事务隔离级别设成上面那样,是同样效果么?
答案,否,隔离级别生效于当前(查看查询结果)的事务。
2.测试可重复读。
代码 :
import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; public class TestRepeatRead { public static void main(String[] args) { try { Class.forName("com.mysql.jdbc.Driver"); Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/xx?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true", "root", "xx"); //conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); //1 conn.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ); conn.setAutoCommit(false); Statement stm=conn.createStatement(); ResultSet rs=stm.executeQuery("select name from test_tran where id=1"); while(rs.next()) { String name=rs.getString("name"); System.out.println(name); } //开启另一事务修改id=1 Connection conn2=startOtherTran(); rs=stm.executeQuery("select name from test_tran where id=1"); while(rs.next()) { String name=rs.getString("name"); System.out.println(name); } //打断点,第一种TRANSACTION_READ_COMMITTED可以看到读到了"otherTran",就是另一个事务里的值。 //打断点,第一种TRANSACTION_REPEATABLE_READ可以看到读到了"init",就是初始值。但这时数据实际上已经是"otherTran" conn.commit(); }catch(Exception e) { e.printStackTrace(); } } public static Connection startOtherTran() throws Exception{ Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/xx?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true", "root", "xx"); conn.setAutoCommit(false); Statement stm=conn.createStatement(); stm.execute("update test_tran set name='otherTran' where id=1"); conn.commit(); return conn; } }3.测试幻读。
代码 :
import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; public class TestIllustoryRead2 { public static void main(String[] args) { try { Class.forName("com.mysql.jdbc.Driver"); Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/xx?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true", "root", "xx"); //conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); //1 conn.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ); conn.setAutoCommit(false); Statement stm=conn.createStatement(); //stm.execute("update test_tran set name='trans1' where name='init'"); ResultSet rs=stm.executeQuery("select count(*) num from test_tran where name!='trans1'"); while(rs.next()) { Integer num=rs.getInt("num"); System.out.println(num); } //开启另一事务新增数据 Connection conn2=startOtherTran(); System.out.println("-------------"); rs=stm.executeQuery("select count(*) num from test_tran where name!='trans1'"); while(rs.next()) { Integer num=rs.getInt("num"); System.out.println(num); } //打断点,第一种TRANSACTION_READ_COMMITTED可以看到读到了"otherTran",就是另一个事务里的值。 //打断点,第一种TRANSACTION_REPEATABLE_READ可以看到读到了"init",就是初始值。但这时数据实际上已经是"otherTran" conn.commit(); }catch(Exception e) { e.printStackTrace(); } } public static Connection startOtherTran() throws Exception{ Connection conn = DriverManager.getConnection("jdbc:mysql://localhost/xx?useUnicode=true&characterEncoding=UTF8&rewriteBatchedStatements=true", "root", "xx"); conn.setAutoCommit(false); Statement stm=conn.createStatement(); stm.execute("insert into test_tran(name) values('otherTran')"); conn.commit(); return conn; } }
脏读与重复读可以分别以不提交事务隔离级别、提交事务隔离级别模拟出来。但幻读并不会到REPEATABLE_READ就能模拟出来,这是因为mysql的MVCC(多版本并发控制)特性决定的,就是说设置到REPEATABLE_READ就可以防止幻读了,自然也能防止脏读与可重复读。
那么,默认mysql隔离级别就是不需要关心这三个问题的。