代码试验,观察oracle不同事务隔离级别的影响现象与库中真实数据

注:这里只观察表象和库中真实数据,没有深究不同的事务间隔离级别是怎样影响锁机制的,也没有深究oracle是怎么使用在undo表空间中构造并使用旧的数据块的。

注:Oracle通过在undo表空间中构造多版本数据块来实现读一致性,支持serializable的隔离级别,可以实现最高级别的读一致性。每个session 查询时,如果对应的数据块发生变化,Oracle会在undo表空间中为这个session构造它查询时的旧的数据块。oracle数据库通过这种能力,来保证在serializable级别的时候,不是锁表不让其他事务插入或删除,而是其他事务能操作,本事务中的多次查询还是使用旧数据块,以此达到事务间多次查询的记录的一致性。

注:oracle数据库只支持读已提交Read Commited 和串行化Serializable这两个隔离级别,且oracle默认的隔离级别就是Read Commited。

测试准备:数据库配置文件、得到connction的工具类、写一个java类、已经存在3条数据的表person、使用plsql Dev工具。

Druid.properties

#driverClassName=com.mysql.jdbc.Driver
#url=jdbc:mysql:///day15
driverClassName=oracle.jdbc.driver.OracleDriver
url=jdbc:oracle:thin:@127.0.0.1:1521:orcl
username=XXX
password=XXX
initialSize=5
maxActive=10
maxWait=3000

JDBCUtils.java

package com.amar.utils;

import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Properties;
import javax.sql.DataSource;


import com.alibaba.druid.pool.DruidDataSourceFactory;

/**
 * Druid连接池的工具类
 */
public class JDBCUtils {

    //1.定义成员变量 DataSource
    private static DataSource ds ;

    static{
        try {
            //1.加载配置文件
            Properties pro = new Properties();
            pro.load(JDBCUtils.class.getClassLoader().getResourceAsStream("druid.properties"));
            //2.获取DataSource
            ds = DruidDataSourceFactory.createDataSource(pro);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取连接
     */
    public static Connection getConnection() throws SQLException {
//        return ds.getConnection();
    	Connection conn = ds.getConnection();
    	conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);//读已提交的事务间隔离级别,也是默认的。注:不能是sys用户
//    	conn.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);//读未提交的事务间隔离级别。注:oracle不支持此隔离级别,会报nullPointEx
    	return conn;
    }

    /**
     * 释放资源
     */
    public static void close(ResultSet rs , Statement stmt, Connection conn){

        if(rs != null){
            try {
                rs.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }


        if(stmt != null){
            try {
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }

        if(conn != null){
            try {
                conn.close();//归还连接
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 获取连接池方法
     */

    public static DataSource getDataSource(){
        return  ds;
    }

}

testTxInsulate.java

package com.amar.oracleTx;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;


import com.amar.utils.JDBCUtils;

public class testTxInsulate {
public static void main(String[] args) {
	System.out.println("mian线程的名字:==="+Thread.currentThread().getName());
//	再造一个线程
	Thread myThread1=new Thread(new Runnable() {
		Connection conn;
		PreparedStatement pre;
		ResultSet result;
		
		@Override
		public void run() {
			System.out.println("自己定义一个线程的名字是:==="+Thread.currentThread().getName());
			try {
				 conn = JDBCUtils.getConnection();
				 conn.setAutoCommit(false);//设置手动管理事务
				 String sql = "select * from person ";
			        pre = conn.prepareStatement(sql);
			        result = pre.executeQuery();
			        while (result.next()){
			            System.out.println("线程1---人编号:" + result.getInt("id") + "   "+
			            		"姓名:"+ result.getString("name")+"   "+
			            		"年龄:"+ result.getString("age")
			            		);}
			        System.out.println("美丽的分割线------------------------");
			        System.out.println("美丽的分割线------------------------");
			        System.out.println("美丽的分割线------------------------");
			        Thread.sleep(15000);
			        //休眠15秒后第二次查询。
			        sql = "select * from person ";
			        pre = conn.prepareStatement(sql);
			        result = pre.executeQuery();
			        while (result.next()){
			            System.out.println("线程1---人编号:" + result.getInt("id") + "   "+
			            		"姓名:"+ result.getString("name")+"   "+
			            		"年龄:"+ result.getString("age")
			            		);}
			      conn.commit();  
			} catch (Exception e) {
				try {
					conn.rollback();
				} catch (SQLException e1) {
					e1.printStackTrace();
				}
				e.printStackTrace();
			}finally {
				JDBCUtils.close(result, pre, conn);
			}
		}
	});
	
	myThread1.start();
	
	
	
	
}
}

       ID    NAME    AGE    BIRTHDAY
1    1    哈1    25    2020/10/20 14:00:08
2    2    哈2    25    2020/10/20 14:00:12
3    3    哈3    25    2020/10/20 14:00:15
 

 

上述代码已知,我们的级别是Read Commited;我们的java类里面有两次查询,由同一个事务控制,两次查询中间间隔15秒。下面我们测试它能不能解决读未提交的问题。

启动main方法,在第一次查询后15秒内操作plsql工具执行update person p set p.name='哈3,大傻子' where p.id='3';且不提交,用来模拟并发的事务,分析两次查询对应的输出

线程1---人编号:1   姓名:哈1   年龄:25
线程1---人编号:2   姓名:哈2   年龄:25
线程1---人编号:3   姓名:哈3   年龄:25
美丽的分割线------------------------
美丽的分割线------------------------
美丽的分割线------------------------
线程1---人编号:1   姓名:哈1   年龄:25
线程1---人编号:2   姓名:哈2   年龄:25
线程1---人编号:3   姓名:哈3   年龄:25

 所以:

隔离级别是读已提交时,一次事务内的两次查询结果一样,不会被两次查询间隔时间内的并发事务的未提交的修改操作影响,即避免脏读(读未提交)。下面测试提交情况

启动main方法,在第一次查询后15秒内操作plsql工具执行update person p set p.name='哈3,大傻子' where p.id='3';并提交,用来模拟并发的事务,分析两次查询对应的输出

线程1---人编号:1   姓名:哈1   年龄:25
线程1---人编号:2   姓名:哈2   年龄:25
线程1---人编号:3   姓名:哈3   年龄:25
美丽的分割线------------------------
美丽的分割线------------------------
美丽的分割线------------------------
线程1---人编号:1   姓名:哈1   年龄:25
线程1---人编号:2   姓名:哈2   年龄:25
线程1---人编号:3   姓名:哈3,傻   年龄:25

所以:

隔离级别是读已提交时,一次事务内的两次查询结果不一样,会被两次查询间隔时间内的并发事务的提交的修改操作影响,即解决不了不可重复读问题。(不可重复读,即原始读取不可重复)。下面测试事务间隔离级别是串行化Serializable的时候( conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);)(数据要回到原始状态再测试)还执行上面的修改并提交操作。输出如下:

线程1---人编号:1   姓名:哈1   年龄:25
线程1---人编号:2   姓名:哈2   年龄:25
线程1---人编号:3   姓名:哈3  年龄:25
美丽的分割线------------------------
美丽的分割线------------------------
美丽的分割线------------------------
线程1---人编号:1   姓名:哈1   年龄:25
线程1---人编号:2   姓名:哈2   年龄:25
线程1---人编号:3   姓名:哈3   年龄:25

所以,串行化解决了不可重复读问题。那么此时我数据库中真实数据是什么呢?当然是被修改后的啦,毕竟你的修改操作都能提交呀。

下面测插入操作并提交:insert into person values('4','哈4','24',sysdate);  commit;用来测试串行化能不能解决幻读的问题。(幻读:即第二次读取到的数据相比较第第次少了几条或者多了几条就好像产生了幻觉一样。就是说咦,我没新插入或删除记录啊,这次眼睛看到的多了或少了几条记录一定是幻觉。 )

线程1---人编号:1   姓名:哈1   年龄:25
线程1---人编号:2   姓名:哈2   年龄:25
线程1---人编号:3   姓名:哈3,傻   年龄:25
美丽的分割线------------------------
美丽的分割线------------------------
美丽的分割线------------------------
线程1---人编号:1   姓名:哈1   年龄:25
线程1---人编号:2   姓名:哈2   年龄:25
线程1---人编号:3   姓名:哈3,傻   年龄:25

那么此时我数据库中真实数据有几条?当然是4条啦,毕竟你的插入操作都能提交的呀。

总结:

oracle在读已提交的事务间隔离级别下,能解决脏读问题,不能解决不可重复读的问题,本身就符合一般业务要求。

oracle在串行化的事务间隔离级别下,并不会限制并发事务去修改或插入记录,而是以旧数据块的功能,来解决不可重复读或幻读的现象。

(拓展:mysql在不同隔离级别下使用的锁机制就不同,比如串行化就会加表级共享锁,直到此事务结束才释放,这样其他事务就不能修改或增删记录了(因为要CRUD之前都要先能够持有某种锁),既然这样也就解决了脏读、不可重复读、幻读等问题。

所以:oracle数据库隔离级别不仅要考虑不同级别对锁机制的影响,还要考虑oracle具有的旧数据块的影响。mysql数据库隔离级别只考虑不同级别对锁机制的影响即可

 

为什么银行使用 oracle、大厂使用mysql                  :    https://new.qq.com/omn/20201004/20201004A0AKMR00.html

深入分析mysql事务的隔离级别与锁机制之间关系    :   https://www.hollischuang.com/archives/943

 

 


版权声明:本文为weixin_44900158原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。