事务的隔离级别和传播属性(上)

简介: 事务的隔离级别和传播属性(上)

什么是事务?


要么全部都要执行,要么就都不执行。


事务所具有的四种特性


原子性,一致性,隔离性,持久性


原子性


个人理解,就是事务执行不可分割,要么全部完成,要么全部拉倒不干。


一致性


关于一致性这个概念我们来举个例子说明吧,假设张三给李四转了100元,那么需要先从张三那边扣除100,然后李四那边增加100,这个转账的过程对于其他事务而言是无法看到的,这种状态始终都在保持一致,这个过程我们称之为一致性。


隔离性


并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据是独立的;


持久性


一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。


为什么会出现事务的隔离级别?


我们都知道,数据库都是有相应的事物隔离级别。之所以需要分成不同级别的事务,这个是因为在并发的场景下,读取数据可能会有出现脏读,不可重复读以及幻读的情况,因此需要设置相应的事物隔离级别。


网络异常,图片无法展示
|


为了方便理解,我们将使用java程序代码来演示并发读取数据时候会产生的相应场景:

环境准备:


jdk8

mysql数据库


建立测试使用表:


CREATE TABLE `money` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `money` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
复制代码


一个方便于操作mysql的简单JdbcUtil工具类:


import java.io.IOException;
import java.sql.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Properties;
/**
 * Jdbc操作数据库工具类
 *
 * @author idea
 * @version 1.0
 */
public class JdbcUtil {
    public static final String DRIVER;
    public static final String URL;
    public static final String USERNAME;
    public static final String PASSWORD;
    private static Properties prop = null;
    private static PreparedStatement ps = null;
    /**
     * 加载配置文件中的信息
     */
    static {
        prop = new Properties();
        try {
            prop.load(JdbcUtil.class.getClassLoader().getResourceAsStream("db.properties"));
        } catch (IOException e) {
            e.printStackTrace();
        }
        DRIVER = prop.getProperty("driver");
        URL = prop.getProperty("url");
        USERNAME = prop.getProperty("username");
        PASSWORD = prop.getProperty("password");
    }
    /**
     * 获取连接
     *
     * @return void
     * @author blindeagle
     */
    public static Connection getConnection() {
        try {
            Class.forName(DRIVER);
            Connection conn = DriverManager.getConnection(URL, USERNAME, PASSWORD);
            return conn;
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        }
        return null;
    }
    /**
     * 数据转换为list类型
     *
     * @param rs
     * @return
     * @throws SQLException
     */
    public static List convertList(ResultSet rs) throws SQLException {
        List list = new ArrayList();
        //获取键名
        ResultSetMetaData md = rs.getMetaData();
        //获取行的数量
        int columnCount = md.getColumnCount();
        while (rs.next()) {
            //声明Map
            HashMap<String,Object> rowData = new HashMap();
            for (int i = 1; i <= columnCount; i++) {
                //获取键名及值
                rowData.put(md.getColumnName(i), rs.getObject(i));
            }
            list.add(rowData);
        }
        return list;
    }
}
复制代码


脏读


所谓的脏读是指读取到没有提交的数据信息。模拟场景:两个线程a,b同时访问数据库进行操作,a线程需要插入数据到库里面,但是没有提交事务,这个时候b线程需要读取数据库的信息,将a里面所要插入的数据(但是没有提交)给读取了进来,造成了脏读现象。


代码如下所示:


import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
/**
 * @author idea
 * @date 2019/7/2
 * @Version V1.0
 */
public class DirtyReadDemo {
    public static final String READ_SQL = "SELECT * FROM money";
    public static final String WRITE_SQL = "INSERT INTO `money` (`id`, `money`) VALUES ('3', '350')";
    public Object lock = new Object();
    /**
     * 脏读模拟(注意:需要设置表的存储引擎为innodb类型)
     */
    public static void dirtyRead() {
        try {
            Connection conn = JdbcUtil.getConnection();
            conn.setAutoCommit(false);
            PreparedStatement writePs = conn.prepareStatement(WRITE_SQL);
            writePs.executeUpdate();
            System.out.println("执行写取数据操作----");
            Thread.sleep(500);
            //需要保证连接不同
            Connection readConn = JdbcUtil.getConnection();
            //注意这里面需要保证提交的事物等级为:未提交读
            readConn.setTransactionIsolation(Connection.TRANSACTION_READ_UNCOMMITTED);
            PreparedStatement readPs = readConn.prepareStatement(READ_SQL);
            ResultSet rs = readPs.executeQuery();
            System.out.println("执行读取数据操作----");
            List list = JdbcUtil.convertList(rs);
            for (Object o : list) {
                System.out.println(o);
            }
            readConn.close();
        } catch (SQLException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        dirtyRead();
    }
}
复制代码


由于这个案例里面的事物隔离级别知识设置在了TRANSACTION_READ_UNCOMMITTED层级,因此对于没有提交事务的数据也会被读取进来。造成了脏数据读取的情况。

因此程序运行之后的结果如下:


网络异常,图片无法展示
|


为了预防脏读的情况发生,我们通常需要提升事务的隔离级别,从原先的TRANSACTION_READ_UNCOMMITTED提升到TRANSACTION_READ_COMMITTED,这个时候我们再来运行一下程序,会发现原先有的脏数据读取消失了:


网络异常,图片无法展示
|


不可重复读


所谓的不可重复读,我的理解是,多个线程a,b同时读取数据库里面的数据,a线程负责插入数据,b线程负责写入数据,b线程里面有两次读取数据库的操作,分别是select1和select2,由于事务的隔离级别设置在了TRANSACTION_READ_COMMITTED,所以当select1执行了之后,a线程插入了新的数据,再去执行select2操作的时候会读取出新的数据信息,导致出现了不可重复读问题。


演示代码:


import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
/**
 * 不可重复读案例
 * @author idea
 * @date 2019/7/2
 * @Version V1.0
 */
public class NotRepeatReadDemo {
    public static final String READ_SQL = "SELECT * FROM money";
    public static final String WRITE_SQL = "INSERT INTO `money` (`id`, `money`) VALUES ('3', '350')";
    public Object lock = new Object();
    /**
     * 不可重复读模拟
     */
    public  void notRepeatRead() {
        Thread writeThread = new Thread(new Runnable() {
            @Override
            public void run() {
                try (Connection conn = JdbcUtil.getConnection();) {
                    //堵塞等待唤醒
                    synchronized (lock) {
                        lock.wait();
                    }
                    conn.setAutoCommit(true);
                    PreparedStatement ps = conn.prepareStatement(WRITE_SQL);
                    ps.executeUpdate();
                    System.out.println("执行写取数据操作----");
                    ps.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        Thread readThread = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Connection readConn = JdbcUtil.getConnection();
                    readConn.setAutoCommit(false);
                    readConn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
                    PreparedStatement readPs = readConn.prepareStatement(READ_SQL);
                    ResultSet rs = readPs.executeQuery();
                    System.out.println("执行读取数据操作1----");
                    List list = JdbcUtil.convertList(rs);
                    for (Object obj : list) {
                        System.out.println(obj);
                    }
                    synchronized (lock){
                        lock.notify();
                    }
                    Thread.sleep(1000);
                    ResultSet rs2 = readPs.executeQuery();
                    System.out.println("执行读取数据操作2----");
                    List list2 = JdbcUtil.convertList(rs2);
                    for (Object obj : list2) {
                        System.out.println(obj);
                    }
                    readConn.commit();
                    readConn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        writeThread.start();
        readThread.start();
    }
    public static void main(String[] args) {
        NotRepeatReadDemo notRepeatReadDemo=new NotRepeatReadDemo();
        notRepeatReadDemo.notRepeatRead();
    }
}
复制代码


在设置了TRANSACTION_READ_COMMITTED隔离级别的情况下,上述程序的运行结果为:


网络异常,图片无法展示
|


为了避免这种情况的发生,需要保证在同一个事务里面,多次重复读取的数据都是一致的,因此需要将事务的隔离级别从TRANSACTION_READ_COMMITTED提升到TRANSACTION_REPEATABLE_READ级别,这种情况下,上述程序的运行结果为:


网络异常,图片无法展示
|


幻读


官方文档对于幻读的定义如下:


The so-called phantom problem occurs within a transaction when the same query produces different sets of rows at different times. For example, if a SELECT is executed twice, but returns a row the second time that was not returned the first time, the row is a “phantom” row.


读到上一次没有返回的记录,看起来是幻影一般。


幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。为了解决这种情况,可以选择将事务的隔离级别提升到TRANSACTION_SERIALIZABLE。

目录
相关文章
|
缓存 网络协议 前端开发
深入了解常见的应用层网络协议
深入了解常见的应用层网络协议
深入了解常见的应用层网络协议
|
8月前
|
存储 机器学习/深度学习 人工智能
TaoAvatar:手机拍出电影级虚拟人!阿里3D高斯黑科技让动捕设备下岗
阿里巴巴最新推出的TaoAvatar技术,通过3D高斯溅射实现照片级虚拟人实时渲染,支持多信号驱动与90FPS流畅运行,将彻底改变电商直播与远程会议体验。
563 8
TaoAvatar:手机拍出电影级虚拟人!阿里3D高斯黑科技让动捕设备下岗
|
8月前
|
人工智能 算法 语音技术
学什么能不被AI取代?探索生成式人工智能认证的价值
在AI快速发展的时代,生成式人工智能(Generative AI)成为关键竞争力。掌握其技能可拓宽职业空间,而生成式人工智能认证(如GAI认证)提供了系统学习框架,涵盖核心知识、实用工具及道德法律内容,获国际认可。选择此认证,不仅能提升个人竞争力,还能应对AI带来的职业挑战,开启未来职场新篇章。无论学生还是在职人士,都可通过学习迎接AI时代的机遇。
|
算法 Python
Python面向对象oop编程(二)
Python面向对象oop编程(二)
TU^
|
存储 C语言
C语言浮点数在内存中的存储
在C语言中,浮点数类型用float和double表示。float类型使用4个字节(32位),而double类型使用8个字节(64位)。浮点数表示的范围:float.h中定义
TU^
1210 0
|
前端开发 fastjson
mvc配置fastjson序列化枚举
mvc配置fastjson序列化枚举
221 0
|
JavaScript
【js中鼠标点击、移动和光标移动的事件触发】
【js中鼠标点击、移动和光标移动的事件触发】
405 0
|
运维 机器人 开发工具
全平台VoIP SIP SDK
全平台VoIP SIP SDK
|
存储 运维 数据建模
《全链路数据治理-智能数据建模 》——数仓建模理论与规范(1)
《全链路数据治理-智能数据建模 》——数仓建模理论与规范(1)
803 0