• Java Web(八) 事务,安全问题及隔离级别


    事务

    什么是事务?

    事务就是一组原子性的SQL查询,或者说是一个独立的工作单元。

    事务的作用

    事务在我们平常的CRUD(增删改查)操作当中也许不太常用, 但是如果我们有一种需求,一组操作中必须全部成功执行,才算完成任务,只要有一个出错了,那么所有的任务都将回到最初的状况,恢复原样。那么这就需要使用事务了。如: 银行转账,购买飞机票......

    事务的特性

    事务具有4个特性(ACID):原子性(Atomicity)、一致性(Consistency)、隔离性(lsolation)、持久性(Durability)。

    • 原子性(Atomicity): 事务中的逻辑要全部执行,不可分割。(原子是物理中最小单位)
    • 一致性(Consistency): 指事务执行前和执行后, 数据的完整性保持一致
    • 隔离性(Isolation): 指一个事务在执行的过程中不应该受其他事务的影响
    • 持久性(Durability): 事务执行结束(提交或回滚), 数据都应持久化到数据中

    演示事务

    在MySQL命令行的默认设置下,事务都是自动提交的,即执行SQL语句后就会马上执行commit操作。因此要显式地开启一个事务务须使用命令begin或start transaction,或者执行命令set autocommit = 0,用来禁止使用当前会话的自动提交。

    1. 命令行中演示事务

      开启事务:start transaction;

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

      回滚事务:rollback;  数据回滚,回到最初的状态。

      开启一个事物:

      img

      我们先看看stuifo表里最初的数据:

      img

      再更新stuifo表里的派大星的性别为女并查看修改后的数据:

      img

      这里显示性别已经修改了,但其实数据没有真正的写入到硬盘中,我们去MySQL管理软件看看数据有没有被更新:

      img

      可以发现数据没有被更新,因为事务没有被提交,现在我们把事务提交。

      img

      提交后,数据才会被更新,真正的写入到硬盘里。

      img

      如果不提交,使用回滚rollback,数据就会恢复到初始状态:

      img

    2. 使用代码演示事务

      package test;
      
      import Utils.JDBCUtils;
      
      import java.sql.Connection;
      import java.sql.PreparedStatement;
      import java.sql.SQLException;
      
      public class demo {
          public static void main(String[] args) {
              Connection conn = null;
              PreparedStatement ps = null;
              try {
                  conn = JDBCUtils.getConnection();
                  conn.setAutoCommit(false);//关闭事务默认自动提交
                  String sql = "update stuifo set sex = '女' where id = 2";
                  ps = conn.prepareStatement(sql);
                  ps.executeUpdate();
                  conn.commit();//成功,提交事务
              } catch (SQLException e) {
                  try {
                      conn.rollback();//失败,回滚事务
                  } catch (SQLException e1) {
                      e1.printStackTrace();
                  }
                  e.printStackTrace();
              } finally {
                  JDBCUtils.closeStatement(ps);
                  JDBCUtils.closeConn(conn);
              }
          }
      }
      

    安全问题

    读问题

    在操作数据库时,可能会出现读问题,主要表现为脏读,不可重复读,幻读。

    1. 脏读

      脏读: 指 一个事务读到了另一个事务还未提交的数据。

      演示:

      最初stuifo表中的数据为:

      img

      在两个窗口分别开启一个事务,首先在A窗口修改派大星性别为女,但不提交事务,然后在B窗口中查询表stuifo。

      A窗口:

      img

      B窗口:

      img

      这样B窗口中的事务就读取到了A窗口中的事务未提交的数据,称之为脏读。

    2. 不可重复读

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

      最初stuifo表中的数据为:

      img

      mysql默认是可重复读的,所以我们先把A窗口的隔离级别设置为读未提交,在两个窗口分别开启一个事物,首先在A窗口查询表stuifo,然后在B窗口中修改派大星性别为女,并提交事务,再在A窗口查询表stuifo。

      A窗口:

      img

      B窗口:

      img

      A窗口:

      img

      这样A窗口中在一个事务下前后读取的数据不一致,称为不可重复读。

    3. 幻读

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

      幻读与不可重复读类似,都是前后查询结果不一致,但是不可重复读针对的是update,后者针对的是insert。

    写问题

    在操作数据库时,可能会出现写问题,主要表现为丢失更新。

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

    img

    解决丢失更新,通常有两种方法:悲观锁和乐观锁。

    • 悲观锁  

      指事务在一开始就认为丢失更新一定会发生,这是一件很悲观的事情。具体操作步骤如下:

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

      select * from student for update ; 后面的for update其实是数据库锁机制 、一种排他锁。

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

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

    这相当于上卫生间似的,谁先来谁就进去蹲着,后面来的人都只能等着。只有里面的人出来了,外面的人才能进去。

    • 乐观锁

      乐观锁是指,从来不会觉得丢失更新会发生。那么它的具体做法是什么呢?

      要求程序员在数据库中添加字段,然后在后续更新的时候,对该字段进行判定比对,如果一致才允许更新。例子如下:

      1.数据库表中,额外添加了一个version字段,用于记录版本,默认从0开始,只要有针对表中数据进行修改的,那么version就+1。

      2.开启A事务,然后开启B事务。

      3.先执行数据库表操作。因为以前都没有人修改过。所以是允许A事务修改数据库的,但是修改完毕,就把version的值变成1了。

      4.B事务,这时候如果想执行修改,那么是不允许修改的。因为B事务以前是没有查询过数据库内容的,所以它认为数据库版本还是0。但是数据库的版本经过A修改,已经是1了。所以这时候不允许修改,要求其重新查询 。

      5.B重新查询后,将会得到version为1的数据,这份数据就是之前A事务修改的数据了,B在进行修改,也是在A的基础上修改的。所以就不会有丢失更新的情况出现了。

    隔离级别

    设置事务的隔离级别可以解决上面的脏读,不可重复读,幻读问题,但是隔离级别越高,执行效率越低,mysql的隔离级别默认为可重复读。

    事务隔离分为四个级别,由低到高为读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。

    ✔代表可能发生,✖代表不会发生

    隔离级别 脏读 不可重复读 幻读
    读未提交(Read uncommitted)
    读提交(read committed)
    可重复读(repeatable read)
    串行化(Serializable)

    查看隔离级别:

    img

    设置隔离级别:

    img

    作者:kindleheart
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须在文章页面给出原文连接,否则保留追究法律责任的权利。
  • 相关阅读:
    Zabbix安装部署
    设计模式目录导航
    [内排序]八大经典排序合集
    SpringBoot集成基于tobato的fastdfs-client实现文件上传下载和删除
    Docker部署FastDFS(附示例代码)
    Docker部署Portainer搭建轻量级可视化管理UI
    Maven基础知识详解
    SpringBoot整合Swagger2详细教程
    screw一键生成数据库文档
    SpringBoot + Mybatis-Plus 实现多数据源简单示例
  • 原文地址:https://www.cnblogs.com/kindleheart/p/9781130.html
Copyright © 2020-2023  润新知