zoukankan      html  css  js  c++  java
  • 事务

    事务

    1.事务概述

    • 什么是事务

      Transaction 其实指的是一组操作,里面包含许多个单一的逻辑。只要有一个逻辑没有执行成功,那么都算失败。 所有的数据都回归到最初的状态(回滚)。

    • 为社么要有事务

      为了确保逻辑的成功。如银行的转账。

    • 事务的特性

      • 原子性

        指事务中包含的逻辑不可分割。

      • 一致性

        指事务执行前后数据的完整性。

      • 隔离性

        指事务在执行期间不应该受到其它事务的影响。

      • 持久性

        指事务执行成功后,数据应持久保存到磁盘上。

    2.演示事务

    2.1使用命令行方式演示事务

    • 开启事务

      start transaction;

    • 提交事务

      commit;提交事务后,数据将会写到磁盘上的数据库

    • 回滚事务

      rollback;数据回滚,回到最初的状态

    1. 关闭自动提交功能

    1. 开启事务

    2. 演示事务

      原来zhangsan和lisi账户各有1000块,现修改lisi的账户为1100,在事务没有提交的情况下,查出lisi账户为1100,但此时数据并没有写入到磁盘,可使用rollback回滚。

    当提交事务后,再查询发现lisi账户确为1100,且无法回滚到1000。

    2.2使用代码方式演示事务

    模拟转账:

    • 不使用事务的情况下:
    @Test
    public void testTransaction() throws Exception {
        Connection con=null;
        PreparedStatement ps=null;
        ResultSet rs=null;
        con=JDBCUtils.getConnection();
    
        String sql="update account set money=money-? where id=?";
        ps=con.prepareStatement(sql);
    
        //扣ID为1的100元
        ps.setDouble(1, 100);
        ps.setInt(2, 1);
        ps.executeUpdate();
    
        //模拟出错
        int a=10/0;
    
        //加ID为2的100元
        ps.setDouble(1, -100);
        ps.setInt(2, 2);
        ps.executeUpdate();
    }
    

    转账出错后,1的钱少了100,2的钱却没有多:

    • 使用事务的情况下:
    @Test
    public void testTransaction2() throws Exception {
        Connection con=null;
        PreparedStatement ps=null;
        ResultSet rs=null;
        con=JDBCUtils.getConnection();
        try {
            con=JDBCUtils.getConnection();
            //关闭自动提交。事务默认是自动提交的
            con.setAutoCommit(false);
            String sql="update account set money=money-? where id=?";
            ps=con.prepareStatement(sql);
    
            //扣ID为1的100元
            ps.setDouble(1, 100);
            ps.setInt(2, 1);
            ps.executeUpdate();
    
            //模拟出错
            int a=10/0;
    
            //加ID为2的100元
            ps.setDouble(1, -100);
            ps.setInt(2, 2);
            ps.executeUpdate();
    
            //成功,提交事务
            con.commit();
        }catch(SQLException e) {
            //失败,回滚事务
            con.rollback();
        }
    }
    

    转账出错后,1的钱没有少,2的钱也没有多:

    3.事务的安全隐患

    3.1读

    • 脏读

      一个事务读到另外一个事务还未提交的数据。

    • 幻读

      一个事务读到了另一个事务已提交的插入的或删除的数据,导致多次查询结果不一致。

      幻读是当事务不是独立执行时发生的一种现象。

    • 不可重复读

      一个事务读到了另一个事务提交的数据, 导致多次查询结果不一致。

    3.2写

    • 丢失更新

      指一个事务去修改数据库, 另一个事务也修改数据库,最后的那个事务,不管是提交还是回滚都会造成前面一个事务的数据更新丢失。

    4.隔离级别

    • 读未提交(Read Uncommitted)

      一个事务可以读取到另一个事务还未提交的数据。 这就会引发 “脏读” 。读取到的是数据库内存中的数据,而并非真正磁盘上的数据。

      演示:

      1. 开启A窗口,设置隔离级别为读未提交

      1. 开启B窗口,并分别开启两个连接的事务

      1. 在B窗口执行sql语句但不提交,在A窗口重新执行查询,看到B窗口没有提交的数据

    • 读已提交(Read Committed)

      与前面的读未提交刚好相反,这个隔离级别是 ,只能读取到其他事务已经提交的数据,那些没有提交的数据读不出来。但这会造成一个问题: 前后读取到的结果不一样。 发生了不可重复读, 所谓不可重复读,就是不能执行多次读取,否则出现结果不一 。

      演示:

      1. 开启A窗口,设置A窗口的事务隔离级别为读未提交,命令如下:

        set session transaction isolation level read committed;

      2. 开启B窗口,在两个窗口中选择数据库并开启事务,在A窗口执行查询;

      3. 在B窗口执行sql语句, 但是不提交,在A窗口重新执行查询, 是不会看到B窗口刚才执行sql 语句的结果,因为它还没有提交。在B窗口执行提交,在A窗口中执行查看 这时候才会看到B窗口已经修改的结果。

    • 重复读(Repeatable Read)

      MySQL默认隔离级别。

      可以让事务在自己的会话中重复读取数据,并且不会出现结果不一样的情况,即使其他事务已经提交了,也依然还是显示以前的数据。

      演示:

      1. 开启A窗口,设置当前窗口的事务隔离级别为读未提交,命令如下:

        set session transaction isolation level repeatable read;

      2. 开启B窗口, 在两个窗口中选择数据库并开启事务,在A窗口执行查询;

      3. 在B窗口执行sql语句,但是不提交,在A窗口重新执行查询,结果不变;在B窗口提交事务后,再在A窗口重新查询,发现结果也不变,多次执行查询依然不变。

    • 可串行化(Serializable)

      最高级的事务级别,比前面几种都要强大一点,可以解决脏读、不可重复读、幻读。缺点是造成并发的性能问题。 其他的事务必须得等当前正在操作表的事务先提交,才能接着往下,否则只能一直在等着。

      演示:

      1. 开启A窗口,设置当前窗口的事务隔离级别为可串行化,命令如下:

        set session transaction isolation level serializable;

      2. 开启B窗口, 在两个窗口中选择数据库并开启事务;

        开启事务的顺序不同,会导致效果也不同:

        • A先开事务,B再开事务

          在A中执行查询,B中执行修改操作,B会卡住,因为A先开启事务,B要等A提交事务后才能执行成功。

      • B先开事务,A再开事务

        在B中执行修改操作,A中执行查询,A会卡住,A要等B提交事务后才能执行成功。

    5.四种隔离级别的比较

    • 效率

      读未提交>读已提交>可重复读>可串行化

    • 拦截程度

      可串行化>可重复读>读已提交>读未提交

    • 比较

      读未提交,引发脏读。

      读已提交,引发不可重复读,解决脏读。

      可重复读,解决脏读,不可重复读,未解决幻读。

      可串行化,解决脏读,幻读,不可重复读。

    5.写问题解决方法

    • 悲观锁

      指事务在一开始就认为丢失更新一定会发生, 这是一件很悲观的事情。

      具体操作步骤如下:

      1. 所有事务在执行操作前,先查询一次数据, 查询语句如下:

        select * from student for update ;

        for update 其实是数据库锁机制 、 一种排他锁;

      2. 哪个事务先执行这个语句, 哪个事务就持有了这把锁, 可以查询出来数据, 后面的事务再执行这条语句,不会有任何数据显示,就只能等着。

      3. 一直等到前面的那个事务提交数据后, 后面的事务数据才会出来,那么才可以往下接着操作。

    • 乐观锁

      指从来不会觉得丢失更新会发生。

      要求程序员在数据库中添加字段,然后在后续更新的时候,对该字段进行判定比对, 如果一致才允许更新。乐观锁的机制 ,其实是通过比对版本或者比对字段的方式来实现的。

      具体操作步骤如下:

      1. 数据库表中,额外添加了一个version字段, 用于记录版本, 默认从0 开始, 只要有针对表中数据进行修改的,那么version就+1;
      2. 开启A事务, 然后开启B事务 ;
      3. A 先执行数据库表操作。 因为以前都没有人修改过。 所以是允许A事务修改数据库的,但是修改完毕,就把version的值变成 1 了;
      4. B事务, 这时候如果想执行修改,那么是不允许修改的。 因为B事务以前是没有查询过数据库内容的,所以它认为数据库版本还是0 ,但是数据库的版本经过A修改,已经是1了。
        所以这时候不允许修改, 要求其重新查询 ;
      5. B重新查询后, 将会得到version 为 1的数据,这份数据就是之前A 事务修改的数据了, B 再进行修改,也是在A的基础上修改的,所以就不会有丢失更新的情况出现了。
  • 相关阅读:
    uva 10491 Cows and Cars
    uva 10910 Marks Distribution
    uva 11029 Leading and Trailing
    手算整数的平方根
    uva 10375 Choose and divide
    uva 10056 What is the Probability?
    uva 11027 Palindromic Permutation
    uva 10023 Square root
    Ural(Timus) 1081. Binary Lexicographic Sequence
    扩展欧几里得(求解线性方程)
  • 原文地址:https://www.cnblogs.com/ALiWang/p/13726568.html
Copyright © 2011-2022 走看看