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隔离级别就是不需要关心这三个问题的。

文/程忠 浏览次数:0次   2020-05-14 11:42:31

相关阅读


评论:
点击刷新

↓ 广告开始-头部带绿为生活 ↓
↑ 广告结束-尾部支持多点击 ↑