Bootstrap

SQL事物的概念

一、事务的概念

  事务指逻辑上的一组操作,组成这组操作的各个单元,要不全部成功,要不全部不成功。
  例如:A——B转帐,对应于如下两条sql语句
  update from account set money=money+100 where name=’B’;
   update from account set money=money-100 where name=’A’;
  那么逻辑上来说,转账就是一个事务的概念,在组成这个事务的各个单元,要么全部成功,要么全部不成功。

二、事务的四大特性(ACID)

原子性(Atomicity):
原子性是指事务是一个不可分割的工作单位,事务中的操作要么全部成功,要么全部失败。比如在同一个事务中的SQL语句,要么全部执行成功,要么全部执行失败。
一致性(Consistency):使用事务时,必须使数据库从一个一致性状态变换到另一个一致性状态。以转账为例子,A向B转账,假设转账之前这两个用户的钱加起来总共是2000,那么A向B转账之后,不管这两个账户怎么转,A用户的钱和B用户的钱加起来的总额还是2000,这个就是事务的一致性。
隔离性(Isolation):事务的隔离性指当多个用户并发访问数据库时,数据库为每一个用户开启的事物,不能被其他事物的操作所干扰。多个并发事务之间要相互隔离,因此也出现了不同的隔离级别。
持久性(Durability):持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响

三、事务的隔离级别

3.1、事务不考虑隔离性可能会引发的问题 
1.脏读:脏读指一个事务读取了另外一个事务未提交的数据。解决思路:规定一个事务中不能读取未提交事务的数据。
2.不可重复读在一个事务内读取数据表的某一行记录,多次读取的结果不同。例如银行想查询A帐户余额,第一次查询A帐户为200元,此时A向帐户内存了100元并提交了,银行接着又进行了一次查询,此时A帐户为300元了。银行两次查询不一致,可能就会很困惑,不知道哪次查询是准的。
与脏读的区别是:脏读是读取另一个事务未提交的数据,不可重复读是第一次读取数据,第二次读取另一个事务已提交的数据,两次的结果不同。
解决思路:规定在一个事务内,按照第一次读取的数为标准。
3.虚读(幻读):*指在一个事务内,读取到了其他事务(插入,insert)的数据,导致前后读取不一致*。如丙存款100元未提交,这时银行做报表统计account表中所有用户的总额为500元,然后丙提交了,这时银行再统计发现帐户为600元了,造成虚读同样会使银行不知所措,到底以哪个为准

不可重复读的重点是修改 : 同样的条件 , 你读取过的数据 , 再次读取出来发现值不一样了 。
幻读的重点在于新增或者删除 : 同样的条件 , 第 1 次和第 2 次读出来的记录数不一样。

3.2 MySQL数据库共定义了四种隔离级别:
Serializable(串行化):可避免脏读、不可重复读、虚读情况的发生。
Repeatable read(可重复读):可避免脏读、不可重复读情况的发生。
Read committed(读已提交):可避免脏读情况发生。
Read uncommitted(读未提交):最低级别,以上情况均无法保证。
mysql数据库默认的事务隔离级别是:Repeatable read(可重复读)。
Spring管理事务中,Isolation 属性增加了一个字段:DEFAULT 使用数据库设置的隔离级别 ( 默认 ) ,由 DBA 默认的设置来决定隔离级别 .即将事务的隔离交给数据库(这里为MySQL)来设定。

四 、JDBC中使用事务

Connection.setAutoCommit(false);//开启事务(start transaction)
Connection.rollback();//回滚事务(rollback)
Connection.commit();//提交事务(commit)
    Connection conn = null;
    PreparedStatement st = null;
        ResultSet rs = null;
    Savepoint sp = null;  //事务回滚点

  try{
            conn = JdbcUtils.getConnection();
            conn.setAutoCommit(false);//通知数据库开启事务(start transaction)
            String sql1 = "update account set money=money-100 where name='A'";
            st = conn.prepareStatement(sql1);
            st.executeUpdate();

        sp = conn.setSavepoint(); //设置回滚点

            //用这句代码模拟执行完SQL1之后程序出现了异常而导致后面的SQL无法正常执行,事务也无法正常提交
            int x = 1/0;
            String sql2 = "update account set money=money+100 where name='B'";
            st = conn.prepareStatement(sql2);
            st.executeUpdate();
            conn.commit();//上面的两条SQL执行Update语句成功之后就通知数据库提交事务(commit)
            System.out.println("成功!!!");
        }catch (Exception e) {
            try {
                //捕获到异常之后手动通知数据库执行回滚事务的操
                conn.rollback(sp);//回滚到设置的事务回滚点,本例中只回滚到sql2之前,所以sql1还是执行了
                //回滚了要记得通知数据库提交事务
                conn.commit();
            } catch (SQLException e1) {
                e1.printStackTrace();
            }
            e.printStackTrace();
        }finally{
            JdbcUtils.release(conn, st, rs);
        }

五、Spring五个事务隔离级别和七个事务传播行为

隔离级别:基于一个事务与另一个事务的隔离。
传播行为:多个事务方法间调用时,事务如何在方法间传播的。

Spring 声明性事务 中Isolation 属性一共支持五种事务设置,具体介绍如下:

DEFAULT: 使用数据库设置的隔离级别 ( 默认 ) ,由 DBA 默认的设置来决定隔离级别 .
READ_UNCOMMITTED :会出现脏读、不可重复读、幻读 ( 隔离级别最低,并发性能高 )
READ_COMMITTED :会出现不可重复读、幻读问题(锁定正在读取的行)
REPEATABLE_READ :会出幻读(锁定所读取的所有行)
SERIALIZABLE :保证所有的情况不会发生(锁表)

Spring中事务的定义:
Propagation(key属性确定代理应该给哪个方法增加事务行为。这样的属性最重要的部份是传播行为。)有以下选项可供使用:

PROPAGATION_REQUIRED--支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
PROPAGATION_SUPPORTS--支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY--支持当前事务,如果当前没有事务,就抛出异常。 
PROPAGATION_REQUIRES_NEW--新建事务,如果当前存在事务,把当前事务挂起。 
PROPAGATION_NOT_SUPPORTED--以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 
PROPAGATION_NEVER--以非事务方式执行,如果当前存在事务,则抛出异常。

只需要了解第一个,第四个和第七个(这三个就足够平常业务开发)。

1.PROPAGATION_REQUIRED 特点记忆:属于同一个事务。
假如当前正要执行的事务不在另外一个事务里,那么就起一个新的事务
比如说,ServiceB.methodB的事务级别定义为PROPAGATION_REQUIRED, 那么由于执行ServiceA.methodA的时候,ServiceA.methodA已经起了事务,这时调用ServiceB.methodB,ServiceB.methodB看到自己已经运行在ServiceA.methodA的事务内部,就不再起新的事务。而假如ServiceA.methodA运行的时候发现自己没有在事务中,他就会为自己分配一个事务。这样,在ServiceA.methodA或者在ServiceB.methodB内的任何地方出现异常,事务都会被回滚。即使ServiceB.methodB的事务已经被提交,但是ServiceA.methodA在接下来fail要回滚,ServiceB.methodB也要回滚

4.PROPAGATION_REQUIRES_NEW 特点记忆:不同的事务,两个事务完全独立,不相关。
比如我们设计ServiceA.methodA的事务级别为PROPAGATION_REQUIRED,ServiceB.methodB的事务级别为PROPAGATION_REQUIRES_NEW,那么当执行到ServiceB.methodB的时候,ServiceA.methodA所在的事务就会挂起,ServiceB.methodB会起一个新的事务,等待ServiceB.methodB的事务完成以后,他才继续执行。他与PROPAGATION_REQUIRED 的事务区别在于事务的回滚程度了。因为ServiceB.methodB是新起一个事务,那么就是存在两个不同的事务。如果ServiceB.methodB已经提交,那么ServiceA.methodA失败回滚,ServiceB.methodB是不会回滚的。如果ServiceB.methodB失败回滚,如果他抛出的异常被ServiceA.methodA捕获,ServiceA.methodA事务仍然可能提交。

7.PROPAGATION_NESTED 特点记忆:父子事务,属于两个事务的同时,又相互依赖。
理解Nested的关键是savepoint。他与PROPAGATION_REQUIRES_NEW的区别是,PROPAGATION_REQUIRES_NEW另起一个事务,将会与他的父事务相互独立,而Nested的事务和他的父事务是相依的,他的提交是要等和他的父事务一块提交的。也就是说,如果父事务最后回滚,他也要回滚的,但是子事务回滚,不会导致外部事务回滚。
而Nested事务的好处是他有一个savepoint。

;