【转】Spring MySQL 事务隔离级别,传播机制,savepoint

 MySQL的四种事务隔离级别

https://www.cnblogs.com/huanongying/p/7021555.html

spring事物的七种事物传播属性行为及五种隔离级别

https://www.cnblogs.com/yuanfy008/p/4174340.html

PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED区别

https://blog.csdn.net/u011285162/article/details/19247711

https://www.cnblogs.com/deepminer/p/12128677.html

savepoint 

https://blog.csdn.net/ytfy12/article/details/52489371

 mysql 可重复读 通过加锁解决幻读问题

https://www.cnblogs.com/cat-and-water/p/6429268.html 

手动回滚事务

https://blog.csdn.net/weixin_41141219/article/details/80751258

https://my.oschina.net/jiansin/blog/3023799


MySQL的四种事务隔离级别

一、事务的基本要素(ACID)

  1、原子性(Atomicity):事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发生一样。也就是说事务是一个不可分割的整体,就像化学中学过的原子,是物质构成的基本单位。

   2、一致性(Consistency):事务开始前和结束后,数据库的完整性约束没有被破坏 。比如A向B转账,不可能A扣了钱,B却没收到。

   3、隔离性(Isolation):同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转账。

   4、持久性(Durability):事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。

二、事务的并发问题

  1、脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据

  2、不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致。

  3、幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。

  小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表

三、MySQL事务隔离级别

   mysql默认的事务隔离级别为repeatable-read

事务隔离级别 脏读 不可重复读 幻读
读未提交(read uncommitted)
读已提交(read committed)
可重复读(repeatable read)
串行化(serializable)

补充:

  1、事务隔离级别为读已提交时,写数据只会锁住相应的行

  2、事务隔离级别为可重复读时,如果检索条件有索引(包括主键索引)的时候,默认加锁方式是next-key 锁;如果检索条件没有索引,更新数据时会锁住整张表。一个间隙被事务加了锁,其他事务是不能在这个间隙插入记录的,这样可以防止幻读。

  MYSQL可重复读的隔离级别下使用了MVCC机制,select操作不会更新版本号,是快照读(历史版本);insert、update和delete会更新版本号,是当前读(当前版本)

  如果select 操作加锁(LOCK IN SHARE MODE, for update),则不使用快照,使用最新当前版本数据,

      如果使用普通的读,会得到一致性的结果,如果使用了加锁的读,就会读到“最新的”“提交”读的结果。

      本身,可重复读和提交读是矛盾的。在同一个事务里,如果保证了可重复读,就会看不到其他事务的提交,违背了提交读;如果保证了提交读,就会导致前后两次读到的结果不一致,违背了可重复读。

      可以这么讲,InnoDB提供了这样的机制,在默认的可重复读的隔离级别里,可以使用加锁读去查询最新的数据。

http://dev.mysql.com/doc/refman/5.0/en/innodb-consistent-read.html

If you want to see the “freshest” state of the database, you should use either the READ COMMITTED isolation level or a locking read:
SELECT * FROM t_bitfly LOCK IN SHARE MODE;

      结论:MySQL InnoDB的可重复读并不保证避免幻读,需要应用使用加锁读来保证。而这个加锁度使用到的机制就是next-key locks。

  3、事务隔离级别为串行化时,读写数据都会锁住整张表

   4、隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。

   5、MYSQL MVCC实现机制参考链接:https://blog.csdn.net/whoamiyang/article/details/51901888

   6、关于next-key 锁可以参考链接:https://blog.csdn.net/bigtree_3721/article/details/73731377

Mysql操作:

select @@tx_isolation; //查询隔离级别

set session transaction isolation level read uncommitted;//设置隔离级别

start transaction;//开启事务

rollback;//回滚事务

commit;//提交事务


 spring事物的七种事物传播属性行为及五种隔离级别

首先,说说什么事务(Transaction)。
事务,就是一组操作数据库的动作集合。事务是现代数据库理论中的核心概念之一。如果一组处理步骤或者全部发生或者一步也不执行,我们称该组处理步骤为一个事务。当所有的步骤像一个操作一样被完整地执行,我们称该事务被提交。由于其中的一部分或多步执行失败,导致没有步骤被提交,则事务必须回滚到最初的系统状态。

其中spring七个事物传播属性:
 PROPAGATION_REQUIRED -- 支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
 PROPAGATION_SUPPORTS -- 支持当前事务,如果当前没有事务,就以非事务方式执行。
 PROPAGATION_MANDATORY -- 支持当前事务,如果当前没有事务,就抛出异常。
 PROPAGATION_REQUIRES_NEW -- 新建事务,如果当前存在事务,把当前事务挂起。
 PROPAGATION_NOT_SUPPORTED -- 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
 PROPAGATION_NEVER -- 以非事务方式执行,如果当前存在事务,则抛出异常。
 PROPAGATION_NESTED -- 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,
则进行与PROPAGATION_REQUIRED类似的操作。

五个隔离级别:
ISOLATION_DEFAULT 这是一个PlatfromTransactionManager默认的隔离级别,使用数据库默认的事务隔离级别.
另外四个与JDBC的隔离级别相对应;
ISOLATION_READ_UNCOMMITTED 这是事务最低的隔离级别,它充许别外一个事务可以看到这个事务未提交的数据。
这种隔离级别会产生脏读,不可重复读和幻像读。

ISOLATION_READ_COMMITTED 保证一个事务修改的数据提交后才能被另外一个事务读取。另外一个事务不能读取
该事务未提交的数据。这种事务隔离级别可以避免脏读出现,但是可能会出现不可重复读和幻像读。

ISOLATION_REPEATABLE_READ 这种事务隔离级别可以防止脏读,不可重复读。但是可能出现幻像读。它除了保证
一个事务不能读取另一个事务未提交的数据外,还保证了避免下面的情况产生(不可重复读)。

ISOLATION_SERIALIZABLE 这是花费最高代价但是最可靠的事务隔离级别。事务被处理为顺序执行。除了防止脏读,
不可重复读外,还避免了幻像读。

关键词:
1)幻读:事务1读取记录时事务2增加了记录并提交,事务1再次读取时可以看到事务2新增的记录;
2)不可重复读取:事务1读取记录时,事务2更新了记录并提交,事务1再次读取时可以看到事务2修改后的记录;
3)脏读:事务1更新了记录,但没有提交,事务2读取了更新后的行,然后事务T1回滚,现在T2读取无效。




 savepoint 

NESTED 嵌套事务里用的,spring 动态拦截器 进入被NESTED事务标记的方法之前,会设置保存点 SAVEPOINT ·savepoint_1`;

如果方法出现异常执行回滚到savepoint,ROLLBACK TO SAVEPOINT;

如果没有异常,则方法执行完后,释放保存点,RELEASE SAVEPOINT;

嵌套的外层事务commit时,一起提交到数据库;

NESTED 嵌套事务,使用存在的事务,并且使用存在的数据库连接,conn,sqlsession


 事务套事务Bug记录分析:  

package com.jd.project.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service
public class ServiceA {
    
    @Autowired
    private ServiceB serviceB;
    
    @Transactional(propagation = Propagation.REQUIRED)
    public void doSometing() {
        
        saveToDb1();
        
        try {
            serviceB.doSometing();
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        saveToDb2();
    }

    private void saveToDb1() {
        System.out.println("saveToDb1");
    }
    private void saveToDb2() {
        System.out.println("saveToDb2");
    }
}

package com.jd.project.service;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

@Service
public class ServiceB {
    
    @Transactional(propagation = Propagation.REQUIRED)
    public void doSometing() {
        //插入数据到数据库
        insertData();
        
        throw new RuntimeException("抛出异常,触发事务回滚");
    }

    private void insertData() {
        System.out.println("insertData");
    }
}

  现象描述: 调用类调用ServiceA的doSomething()方法, saveToDb1()、saveToDb2()没有发生异常,serviceB.doSometing() 异常也被捕获,却发生了回滚,saveToDb1(), saveToDb2()没有生效。

  原因分析:serviceB.doSometing()的事务隔离级别为Propagation.REQUIRED,使用了外层已经的存在的事务,serviceB.doSometing()发生异常,事务被标记为rollback-only,等saveToDb2()执行完,事务触发了回滚,导致saveToDb1(), saveToDb2(),没有生效。

  修复方法:serviceB.doSometing()的事务隔离级别为Propagation.REQUIRED_NEW,或者Propagation.PROPAGATION_NESTED ;

Propagation.REQUIRED_NEW为新建事务,新建会话sqlsession,

Propagation.PROPAGATION_NESTED,使用当前事务,当前会话sqlsession, 但是提供savepoint,发生异常时,回滚到savepoint, serviceA的后面的saveToDb2()方法会继续执行。


 

手动回滚事务

https://blog.csdn.net/weixin_41141219/article/details/80751258

手动回滚:

方法1:在service层方法的catch语句中增加:TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();语句,手动回滚,这样上层就无需去处理异常(现在项目的做法)

 方法2:例如service层处理事务,那么service中的方法中不做异常捕获,或者在catch语句中最后增加throw new RuntimeException()语句,以便让aop捕获异常再去回滚,并且在service上层(webservice客户端,view层action)要继续捕获这个异常并处理

@Transactional  
    public String commonMoney(Receipt rpt,Moneyrecord mors){  
        rpt.setState(1);  
        int a=dao.insert(rpt);  
        if(a<=0) return"缴费失败";  
        mors.setPric(rpt.getPic());  
        mors.setExid(rpt.getPid());  
        mors.setState(1);  
        boolean tf=mrs.custom(mors);  
        if(!tf){  
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();  
            return"余额不足";  
        }  
        return "OK";  
    } 
原文地址:https://www.cnblogs.com/ccgblog/p/9554219.html