• 【转】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";  
        } 
  • 相关阅读:
    Istio安装配置及使用
    Istio介绍
    Rancher管理k8s集群
    EFK部署
    常见日志收集方案及相关组件
    Prometheus Pushgateway
    Prometheus监控拓展
    Prometheus PromQL语法
    开始新工作了
    SpringBlade 新系统 运行
  • 原文地址:https://www.cnblogs.com/ccgblog/p/9554219.html
Copyright © 2020-2023  润新知