zoukankan      html  css  js  c++  java
  • MySQL事务隔离级别和Spring事务关系介绍(转载)

    转自:https://blog.csdn.net/hellozhxy/article/details/81081187

    非常好的文章,学习一下

    事务隔离级别介绍

    隔离级别脏读不可重复读幻读
    未提交读(Read uncommitted) 可能 可能 可能
    已提交读(Read committed) 不可能 可能 可能
    可重复读(Repeatable read) 不可能 不可能 可能
    可串行化(Serializable ) 不可能 不可能 不可能
    1. 未提交读(Read Uncommitted):允许脏读,也就是可能读取到其他会话中未提交事务修改的数据
    2. 提交读(Read Committed):只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别 (不重复读)
    3. 可重复读(Repeated Read):可重复读。在同一个事务内的查询都是事务开始时刻一致的,InnoDB默认级别。在SQL标准中,该隔离级别消除了不可重复读,但是还存在幻象读,但是innoDB解决了幻读
    4. 串行读(Serializable):完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞

    接下来一次来验证每个隔离级别的特性,首先我们先建一张表,我们建立账户表account用来测试我们的事务隔离级别:

    1
    2
    3
    4
    5
    6
    7
    
    CREATE TABLE account (
        `id` int(11) NOT NULL AUTO_INCREMENT,
        `customer_name` varchar(255) NOT NULL,
        `money` int(11) NOT NULL,
        PRIMARY KEY (`id`),
        UNIQUE `uniq_name` USING BTREE (customer_name)
    ) ENGINE=`InnoDB` AUTO_INCREMENT=10 DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci ROW_FORMAT=COMPACT CHECKSUM=0 DELAY_KEY_WRIT
    

    RU (read uncommitted)读未提交隔离级别

    首先我们开启Console A,然后设置session事务隔离级别为read uncommitted; 然后同样开启Console B,设置成read uncommitted;

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    
    mysql> set session transaction isolation level read uncommitted;
    Query OK, 0 rows affected (0.03 sec)
     
    mysql> select @@session.tx_isolation;
    +------------------------+
    | @@session.tx_isolation |
    +------------------------+
    | READ-UNCOMMITTED       |
    +------------------------+
    1 rows in set (0.03 sec)
    

    我们两个console的事务隔离级别都是read uncommitted,下面测试RU级别会发生的情况

    小结:

    可以发现RU模式下,一个事务可以读取到另一个未提交(commit)的数据,导致了脏读。如果B事务回滚了,就会造成数据的不一致。RU是事务隔离级别最低的。

    RC (read committed)读提交隔离级别

    现在我们将事务隔离级别设置成RC (read committed)

    1
    
    set session transaction isolation level read uncommitted;
    


    小结

    我们在RC模式下,可以发现。在console B没有提交数据修改的commit的时候,console A是读不到修改后的数据的,这就避免了在RU模式中的脏读,但是有一个问题我们会发现,在console A同一个事务中。两次select的数据不一样,这就存在了不可重复读的问题.PS:RC事务隔离级别是Oracle数据库的默认隔离级别.

    RR (Repeatable read)可重复读隔离级别


    小结:

    在RR级别中,我们解决了不可重复读的问题,即在这种隔离级别下,在一个事务中我们能够保证能够获取到一样的数据(即使已经有其他事务修改了我们的数据)。但是无法避免幻读,幻读简单的解释就是在数据有新增的时候,也无法保证两次得到的数据不一致,但是不同数据库对不同的RR级别有不同的实现,有时候或加上间隙锁来避免幻读。

    innoDB 解决了幻读

    前面的定义中RR级别是可能产生幻读,这是在传统的RR级别定义中会出现的。但是在innoDB引擎中利用MVCC多版本并发控制解决了这个问题


    这算是幻读吗?在标准的RR隔离级别定义中是无法解决幻读问题的,比如我要保证可重复读,那么我们可以在我们的结果集的范围加一个锁(between 1 and 11),防止数据更改.但是我们毕竟不是锁住真个表,所以insert数据我们并不能保证他不插入。所以是有幻读的问题存在的。但是innodb引擎解决了幻读的问题,基于MVCC(多版本并发控制):在InnoDB中,会在每行数据后添加两个额外的隐藏的值来实现MVCC,这两个值一个记录这行数据何时被创建,另外一个记录这行数据何时过期(或者被删除)。 在实际操作中,存储的并不是时间,而是事务的版本号,每开启一个新事务,事务的版本号就会递增。所以当我们执行update的时候,当前事务的版本号已经更新了?所以也算是幻读??(存疑)主要是gap间隙锁+MVCC解决幻读问题?

    串行化隔离级别:

    所有事物串行,最高隔离级别,性能最差

    存在的问题?

    在RR模型,我们虽然避免了幻读,但是存在一个问题,我们得到的数据不是数据中实时的数据,如果是对实时数据比较敏感的业务,这是不现实的。
    对于这种读取历史数据的方式,我们叫它快照读 (snapshot read),而读取数据库当前版本数据的方式,叫当前读 (current read)。很显然,在MVCC中:

    • 快照读:就是select
      • select * from table ….;
    • 当前读:特殊的读操作,插入/更新/删除操作,属于当前读,处理的都是当前的数据,需要加锁。
      • select * from table where ? lock in share mode;
      • select * from table where ? for update;
      • insert;
      • update ;
      • delete;

    事务的隔离级别实际上都是定义了当前读的级别,MySQL为了减少锁处理(包括等待其它锁)的时间,提升并发能力,引入了快照读的概念,使得select不用加锁。而update、insert这些“当前读”,就需要另外的模块来解决了。

    比如,我们有以下的订单业务场景,我们队一个商品下单的操作,我们得首先检查这个订单的数量还剩多少,然后下单。

    事务1:

    1
    2
    
    select num from t_goods where id=1;
    update t_goods set num=num-$mynum where id=1;
    

    事务2:

    1
    2
    
    select num from t_goods where id=1;
    update t_goods set num=num-$mynum where id=1;
    

    假设这个时候数量只有1,我们下单也是只有1.如果在并发的情况下,事务1查询到还有一单准备下单,但是这个时候事务2已经提交了。订单变成0.这个事务1在执行update,就会造成事故。

    1. 解决问题方法1(悲观锁):就是利用for update对着个商品加锁,事务完成之后释放锁。切记where条件的有索引,否则会锁全表。
    2. 解决方法2(乐观锁):给数据库表加上个version字段。然后SQL改写:
    1
    2
    
    select num,version from t_goods where id=1;
    update t_goods set num=num-1,version=verison+1 where id=1 and version=${version}
    

    spring事务机制

    spring针对事务进行一系列的处理

    事务隔离级别

    在多个事务进行处理的时候锁住数据,保证统一时间只有一个事务对数据进行处理,针对不同的情况有不同的锁

    DEFAULT(默认值):使用底层数据库事务隔离级别,MySql中使用select @@tx_isolation可以查询当前隔离级别

    READ_COMMITTED:读已提交,读到已经提交的数据,可以防止脏读,但是对不可重复读和幻读

    READ_UNCOMMITTED:读未提交,可以读取没有提交的数据,较少用

    REPEATABLE_READ:重复读取,读出去后自动加锁,其他事务不能修改,解决脏读,不可重复读

    SERIALIZABLE:串行化,事务一个排一个执行,一个事务执行完成后执行下一个

    事务传播机制

    在服务中进行数据库操作,两个操作中事务如何管理

    REQUIRED(默认值):如果当前没有事务开启一个事务,有的话自动加入到这个事务中

    REQUIRES_NEW :针对被调用者,不管调用者是否存在事务,被调用者创建一个新事务。

    MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常。

    NESTED:如果存在事务,嵌套事务中执行,如果没有则新建

    SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行。

    NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

    NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。

    Spring管理事务的方式。

    编程式事务

    编程式事务就是利用手动代码编写事务相关的业务逻辑,这种方式比较复杂、啰嗦,但是更加灵活可控制(个人比较喜欢)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    
    public void testTransactionTemplate() {
     
      TransactionTemplate transactionTemplate = new TransactionTemplate(txManager); 
      transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED); //设置事务隔离级别
      transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);//设置为required传播级别
      ....
      transactionTemplate.execute(new TransactionCallbackWithoutResult() { 
          @Override 
          protected void doInTransactionWithoutResult(TransactionStatus status) {  //事务块
             jdbcTemplate.update(INSERT_SQL, "test"); 
      }}); 
      
    }
    

    声明式事务

    1. 为了避免我们每次都手动写代码,利用Spring AOP的方式对每个方法代理环绕,利用xml配置避免了写代码。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    <tx:advice id="txAdvice" transaction-manager="txManager"> 
    <tx:attributes>  <!--设置所有匹配的方法,然后设置传播级别和事务隔离-->
               <tx:method name="save*" propagation="REQUIRED" /> 
               <tx:method name="add*" propagation="REQUIRED" /> 
               <tx:method name="create*" propagation="REQUIRED" /> 
               <tx:method name="insert*" propagation="REQUIRED" /> 
               <tx:method name="update*" propagation="REQUIRED" /> 
               <tx:method name="merge*" propagation="REQUIRED" /> 
               <tx:method name="del*" propagation="REQUIRED" /> 
               <tx:method name="remove*" propagation="REQUIRED" /> 
               <tx:method name="put*" propagation="REQUIRED" /> 
               <tx:method name="get*" propagation="SUPPORTS" read-only="true" /> 
               <tx:method name="count*" propagation="SUPPORTS" read-only="true" /> 
              <tx:method name="find*" propagation="SUPPORTS" read-only="true" /> 
              <tx:method name="list*" propagation="SUPPORTS" read-only="true" /> 
              <tx:method name="*" propagation="SUPPORTS" read-only="true" /> 
           </tx:attributes> 
    </tx:advice> 
    <aop:config> 
           <aop:pointcut id="txPointcut" expression="execution(* org.transaction..service.*.*(..))" /> 
           <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut" /> 
    </aop:config>
    
    1. 同时也可以用注解的方式
    1
    
    <tx:annotation-driven transaction-manager="transactioManager" /><!--开启注解的方式-->
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    @Documented
    public @interface Transactional {
       @AliasFor("transactionManager")
       String value() default "";
       @AliasFor("value")
       String transactionManager() default "";
       Propagation propagation() default Propagation.REQUIRED;//传播级别
      
       Isolation isolation() default Isolation.DEFAULT;//事务隔离级别
       int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;//事务超时时间
       boolean readOnly() default false;//只读事务
       Class<? extends Throwable>[] rollbackFor() default {};//抛出哪些异常 会执行回滚
       String[] rollbackForClassName() default {};
       Class<? extends Throwable>[] noRollbackFor() default {};
       String[] noRollbackForClassName() default {};//不回滚的异常名称
     
    }
    //transaction注解可以放在方法上或者类上
    

    我们在这里不对两种事务编程做过多的讲解

    Spring事务传播:

    事务传播行为:

    Spring管理的事务是逻辑事务,而且物理事务和逻辑事务最大差别就在于事务传播行为,事务传播行为用于指定在多个事务方法间调用时,事务是如何在这些方法间传播的,Spring共支持7种传播行为
    为了演示事务传播行为,我们新建一张用户表

    1
    2
    3
    4
    5
    6
    
    EATE TABLE user (
        `id` int(11) NOT NULL AUTO_INCREMENT,
        `username` varchar(255) NOT NULL,
        `pwd` varchar(255) NOT NULL,
        PRIMARY KEY (`id`)
    ) ENGINE=`InnoDB` AUTO_INCREMENT=10 DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci ROW_FORMAT=COMPACT CHECKSUM=0 DELAY_KEY_WRITE=0;
    

    Required:

    必须有逻辑事务,否则新建一个事务,使用PROPAGATION_REQUIRED指定,表示如果当前存在一个逻辑事务,则加入该逻辑事务,否则将新建一个逻辑事务,如下图所示;

    测试的代码如下,在account插入的地方主动回滚

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    
    public int insertAccount(final String customer, final int money) {
        transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);//设置为required传播级别
       int re= transactionTemplate.execute(new TransactionCallback<Integer>() {
            public Integer doInTransaction( TransactionStatus status) {
                int i = accountDao.insertAccount(customer, money);
                status.setRollbackOnly();//主动回滚
                return i;
            }
        });
        return re;
    }
     public int inertUser(final String username, final String password) {
            transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);//设置为required传播级别
            transactionTemplate.execute(new TransactionCallbackWithoutResult() {
                @Override
                protected void doInTransactionWithoutResult(TransactionStatus status) {
                    int i = userDao.inertUser(username, password);
                    int hahha = accountService.insertAccount("hahha", 2222);
    //                status.setRollbackOnly();
                    System.out.println("user==="+i);
                    System.out.println("account===="+hahha);
                }
            });
            return 0;
        }
    

    按照required的逻辑,代码执行的逻辑如下:

    1. 在调用userService对象的insert方法时,此方法用的是Required传播行为且此时Spring事务管理器发现还没开启逻辑事务,因此Spring管理器觉得开启逻辑事务
    2. 在此逻辑事务中调用了accountService对象的insert方法,而在insert方法中发现同样用的是Required传播行为,因此直接使用该已经存在的逻辑事务;
    3. 返回userService,执行完并关闭事务

    所以在这种情况下,两个事务属于同一个事务,一个回滚则两个任务都回滚。

    RequiresNew:

    创建新的逻辑事务,使用PROPAGATION_REQUIRES_NEW指定,表示每次都创建新的逻辑事务(物理事务也是不同的)如下图所示:

    Supports:

    支持当前事务,使用PROPAGATION_SUPPORTS指定,指如果当前存在逻辑事务,就加入到该逻辑事务,如果当前没有逻辑事务,就以非事务方式执行,如下图所示:

    NotSupported:

    不支持事务,如果当前存在事务则暂停该事务,使用PROPAGATION_NOT_SUPPORTED指定,即以非事务方式执行,如果当前存在逻辑事务,就把当前事务暂停,以非事务方式执行。

    Mandatory:

    必须有事务,否则抛出异常,使用PROPAGATION_MANDATORY指定,使用当前事务执行,如果当前没有事务,则抛出异常(IllegalTransactionStateException)。当运行在存在逻辑事务中则以当前事务运行,如果没有运行在事务中,则抛出异常

    Never

    不支持事务,如果当前存在是事务则抛出异常,使用PROPAGATION_NEVER指定,即以非事务方式执行,如果当前存在事务,则抛出异常(IllegalTransactionStateException)

    Nested:

    嵌套事务支持,使用PROPAGATION_NESTED指定,如果当前存在事务,则在嵌套事务内执行,如果当前不存在事务,则创建一个新的事务,嵌套事务使用数据库中的保存点来实现,即嵌套事务回滚不影响外部事务,但外部事务回滚将导致嵌套事务回滚。

    Nested和RequiresNew的区别:

    1. RequiresNew每次都创建新的独立的物理事务,而Nested只有一个物理事务;
    2. Nested嵌套事务回滚或提交不会导致外部事务回滚或提交,但外部事务回滚将导致嵌套事务回滚,而 RequiresNew由于都是全新的事务,所以之间是无关联的;
    3. Nested使用JDBC 3的保存点(save point)实现,即如果使用低版本驱动将导致不支持嵌套事务。

      使用嵌套事务,必须确保具体事务管理器实现的nestedTransactionAllowed属性为true,否则不支持嵌套事务,如DataSourceTransactionManager默认支持,而HibernateTransactionManager默认不支持,需要设置来开启。

  • 相关阅读:
    RabbitMq windows 安装
    JQuery.Ajax()的data参数传递方式
    [转载]ASP.NET中TextBox控件设立ReadOnly="true"后台取不到值
    vue-cli 3.0脚手架搭建项目
    二、操作符
    一、JavaScript基础
    html苹方字体
    js十大排序算法收藏
    iframe高度自适应的6个方法
    CSS3:不可思议的border属性
  • 原文地址:https://www.cnblogs.com/libin2015/p/12556163.html
Copyright © 2011-2022 走看看