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的基础上修改的,所以就不会有丢失更新的情况出现了。
  • 相关阅读:
    12.使用正则表达式
    12/12
    thinkphp 5 及一下或php项目里实现模糊查询
    mysql中文乱码--存入mysql里的中文变成问号的解决办法
    ATOM使用的一点心得与技巧——在一个窗口打开多个项目
    php里的$this的 含义
    pycharm2017.3专业版激活注册码
    thinkphp3.2.3的使用心得之i方法(零)
    thinkphp3.2.3的使用心得(零)
    linux系统下phpstudy里的mysql使用方法
  • 原文地址:https://www.cnblogs.com/ALiWang/p/13726568.html
Copyright © 2011-2022 走看看