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秒后,数据提交,则查询结果如下:

  • 相关阅读:
    51nod 1087 1 10 100 1000(找规律+递推+stl)
    51nod 1082 与7无关的数 (打表预处理)
    51 nod 1080 两个数的平方和
    1015 水仙花数(水题)
    51 nod 1003 阶乘后面0的数量
    51nod 1002 数塔取数问题
    51 nod 1001 数组中和等于K的数对
    51 nod 1081 子段求和
    51nod 1134 最长递增子序列 (O(nlogn)算法)
    51nod 1174 区间中最大的数(RMQ)
  • 原文地址:https://www.cnblogs.com/vpersie2008/p/12913381.html
Copyright © 2011-2022 走看看