zoukankan      html  css  js  c++  java
  • 事务的ACID属性、解决脏读、不可重复读、幻读

    事务的ACID属性

    1. 原子性(Atomictiy)原子性是指事务是一个不可分割的单位,事务中的操作要么都发生,要么都不发生。

      简单的来说就是在事务操作中,比如我通过两条SQL 改两条数据,要么这两个操作都完成,要么都不完成就回滚。

    2. 一致性(Consistency)事务必须从一个一致性状态变换到另一个一致性状态。

      比如转账操作,从A转给B 100元钱,那么A 少100,B 收到100,即这个转账操作就是从未转账状态到转账成功状态,类似于事务一致性的体现。

    3. 隔离性(Isolation)事务的隔离性,是指一个事务的执行不被其他事务干扰,即一个事务内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

      类似于多线程中的线程资源竞争问题。

    4. 持久性(Durability)一个事务一旦被提交,他对数据库中数据的改变是永久性的,接下来的其他操作对数据库故障不应该对其有任何影响。

      事务操作完数据,这个数据就被持久化了,无论数据库断电、还是故障、等都不应该再改变操作完的数据,体现其持久性。

    事务并发引起的问题

    事务并发一般会引起如下三个问题,分别是 脏读不可重复读幻读,这三个里面,脏读是我们必须要解决的。

    1.脏读

    • 对于两个事务,T1 和 T2, T1 读取了已被T2 更新,但是还没有被提交的字段,之后,若T2 回滚,T1 读取的数据就是临时的且无效的数据。

      • 比方说,改一条数据,事务T1 用于操作改数据,T2 用于读取该后的数据,当T1 在改完还未提交事务时,T2 这个时候去读取T1 的数据,居然读出来时改之后的,这显然是有问题的,因为T1 有可能操作失败回滚,或者说T1 还未改完数据,这时T2 去读未改完的数据显然是不合理的。

    2.不可重复读

    • 对于两个事务,T1,T2, T1 读取了一个字段,然后T2 更新了该字段,之后T1 再去读取同一个字段,值就不一样了。

      • 例如,我在买东西的时候,看到库存不足了,这个时候我没有关浏览器,再次刷新了浏览器,结构看到有库存了,这时因为后台数据库增加了库存,而我又没有断开此次事务连接,这样就读取到了最新的数据。(这种情况就是不可重复读问题

      • 可重复读,意思是说我一个数据库事务连接 没有断开,读取的就是我这个事务之前读取的数据,数据更新数据(事务提交),我也不应该读取事务提交后的数据,因为我这都是在一个事务中的。就说不管更新数据的事务有没有提交,只要我当前查询的事务没有关闭,就会读取之前我查询出来的状态,不管另一个更新的事务操作。

    • 我们为了解决不可重复读问题,需要让他可重复读,然而实际上大多数情况 不可重复读问题是 不需要解决的

    什么又叫可重复读呢?

    • 意思就算另一个事务,改了数据(即使他commit了),如果 当前 读取数据的事务操作没有关闭,则读取的数据就还是之前的数据,如图:

    • 这种模式下,想要读取提交后的数据,必须重新起一个事务。

    3.幻读

    • 对于两个事务 T1,T2 T1从一张表中读取了一个字段后,然后T2在表中 插入了一些新的行,之后,如果T1 再次读取同一个表,就会多出几行。

      • 这里它强调的是插入操作,例如我一个事务T1读数据读出了100条,另一事务T2此时正在向此表插入数据,这时我第一个事务T1读出来就比100条多了,这种情况就是幻读

    总结

    注意: 不可重复读,指的是 更新 操作,而幻读,指的是 插入 操作。

    一个事务与其他事务隔离的程度称为隔离级别。数据库规定了多种事务的隔离级别,不同隔离级别应对不同的干扰程度。

    隔离级别越高,数据一致性就越好,但并发性就越弱,类似于多线程加锁,隔离性好但性能差。

    一般情况下,不可重复读问题是可以接受的,一般我们只需要解决脏读的问题。

    四种隔离级别

    隔离级别 描述
    READ UNCOMMITED 读未提交数据 允许事务读取未被其他事务提交的变更,脏读,不可重复读和幻读问题都会出现
    READ COMMITED读已提交数据 只允许事务读取已被其他事务提交的变更,可以避免脏读,但不可重复读和幻读问题依然存在
    REPEATABLE READ 可重复读 确保事务可以多次从一个字段中读取相同的值,在这个事务持续期间,禁止其他事务对这个字段进行更新,可以避免脏读和不可重复读问题,但幻读问题依然存在。
    SERIALLZABLE 串行化 确保事务可以从一个表中读取相同约行,在这个事务持续期间,禁止其他事务对表执行插入更新删除操作,所有问题都可避免,但性能十分低下
    • Oracle 支持2种事务隔离级别,READ COMMITED, SERIALIZABLE. 它默认隔离级别为 READ COMMITED.

    • 隔离级别越往下,则 一致性越好,并发性越差

    • 一般情况下,脏读问题必须解决,不可重复读和幻读问题是可以接受的。

    Java 隔离级别演示

    如下代码

    一段查询代码,根据设置的隔离级别查询数据,隔离级别如下:

    TRANSACTION_REPEATABLE_READ 为可重复读,避免了脏读和可不可重复读问题.
    TRANSACTION_READ_UNCOMMITTED 读未提交数据,这种隔离级别存在脏读,会读取未提交的数据
    TRANSACTION_READ_COMMITTED 读已提交的数据,如果修改的数据未提交,则读出来还是老的修改之前的数据
    TRANSACTION_SERIALIZABLE 串行化,解决了脏读,不可重复读,幻读。 一般不用,因为并发效率低。
    

    查询更新的数据

    一般我们都会将隔离级别设置为 TRANSACTION_READ_COMMITTED, 这样更新的事务如果没有提交,则读取的都是之前的值,这里只会读取已提交的数据。

    @Test
    public void testTransactionSelect() {
    
        Connection conn = JDBCUtils.GetDBConnection();
        try {
    
            //获取取数据库的隔离级别,当前为TRANSACTION_REPEATABLE_READ
            //TRANSACTION_REPEATABLE_READ 为可重复读,避免了脏读和可不可重复读问题.
            //TRANSACTION_READ_UNCOMMITTED 读未提交数据,这种隔离级别存在脏读,会读取未提交的数据
            //TRANSACTION_READ_COMMITTED 读已提交的数据,如果修改的数据未提交,则读出来还是老的修改之前的数据
            conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED);
            System.out.println(conn.getTransactionIsolation());
    
            //取消自动提交数据
            conn.setAutoCommit(false);
            String sql = "SELECT `user`,`password`,balance FROM balance WHERE `user` = ?";
    
            DBCommand dbCommand = new DBCommand(conn);
            BalanceEntity balance1 = dbCommand.queryMethod(BalanceEntity.class, sql, "jerry");
            System.out.println(balance1);
    
        } catch (Exception ex) {
            ex.printStackTrace();
        }finally {
            try {
                conn.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
    

    更新数据操作

    如下更新操作中,将数据库中的balance改为6000,中途等待15秒后提交,那么在这15秒内,查询数据库中的数据依然是之前的值。

    @Test
    public void testTransactionUpdate() {
        Connection conn = JDBCUtils.GetDBConnection();
        DBCommand dbCommand = new DBCommand(conn);
    
        try {
            String sql = "UPDATE balance SET `balance` = ? WHERE  `user`= ?";
            conn.setAutoCommit(false);
    
            int updateResult = dbCommand.updateMethod(conn, sql, 6000, "jerry");
            if (updateResult > 0) {
                System.out.println("更新数据成功!");
            }
    
            Thread.sleep(15000);
    
            conn.commit();
    
        } catch (Exception ex) {
            ex.printStackTrace();
        }finally {
            try {
                conn.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
    }
    

    如之前的值为 2000 ,隔离级别设置为 TRANSACTION_READ_COMMITTED,那么在这15秒未提交的时间里,数据库查询结果和代码结果都是2000

    数据库中查询结果

    代码中查询结果

    15秒后,数据提交,则查询结果如下:

  • 相关阅读:
    [读书笔记] 代码整洁之道(五): 系统
    [读书笔记] 代码整洁之道(四): 类
    [读书笔记] 代码整洁之道(三): 错误处理及边界接口处理
    [读书笔记] 代码整洁之道(二):对象和数据结构
    程序猿的书单
    selenium自动化-java-封装断言
    java环境变量详细配置步骤
    Selenium-java-TestNg-的运行
    quicktest Professional下载地址,无限制使用方法
    常用网站收集
  • 原文地址:https://www.cnblogs.com/vpersie2008/p/12913381.html
Copyright © 2011-2022 走看看