社区所有版块导航
Python
python开源   Django   Python   DjangoApp   pycharm  
DATA
docker   Elasticsearch  
aigc
aigc   chatgpt  
WEB开发
linux   MongoDB   Redis   DATABASE   NGINX   其他Web框架   web工具   zookeeper   tornado   NoSql   Bootstrap   js   peewee   Git   bottle   IE   MQ   Jquery  
机器学习
机器学习算法  
Python88.com
反馈   公告   社区推广  
产品
短视频  
印度
印度  
Py学习  »  DATABASE

MySQL事务隔离级别和Spring事务关系介绍(来自亿级QPS的东皇)

zhisheng • 7 年前 • 542 次点击  

事务隔离级别介绍

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

1、未提交读(Read Uncommitted):允许脏读,也就是可能读取到其他会话中未提交事务修改的数据

2、提交读(Read Committed):只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别 (不重复读)

3、可重复读(Repeated Read):可重复读。在同一个事务内的查询都是事务开始时刻一致的,InnoDB默认级别。在SQL标准中,该隔离级别消除了不可重复读,但是还存在幻象读,但是innoDB解决了幻读

4、串行读(Serializable):完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞

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

1CREATE TABLE account (2id int(11) NOT NULL AUTO_INCREMENT,3customer_name varchar(255) NOT NULL,4money int(11) NOT NULL,5PRIMARY KEY (id),6UNIQUE uniq_name USING BTREE (customer_name)7) 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;

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

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

小结:

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

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

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

1set 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:

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

事务2:

1select num from t_goods where id=1;2update 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改写:

1select num,version from t_goods where id=1;2update t_goods set num=num-1,version=verison+1 where id=1 and version=${version}

Spring管理事务的方式。

编程式事务

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

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

声明式事务

  1. 为了避免我们每次都手动写代码,利用Spring AOP的方式对每个方法代理环绕,利用xml配置避免了写代码。

    
    
    
        
     1<tx:advice id="txAdvice" transaction-manager="txManager">  2<tx:attributes>  <!--设置所有匹配的方法,然后设置传播级别和事务隔离--> 3          <tx:method name="save*" propagation="REQUIRED" />  4          <tx:method name="add*" propagation="REQUIRED" />  5          <tx:method name="create*" propagation="REQUIRED" />  6          <tx:method name="insert*" propagation="REQUIRED" />  7          <tx:method name="update*" propagation="REQUIRED" />  8          <tx:method name="merge*" propagation="REQUIRED" />  9          <tx:method name="del*" propagation="REQUIRED" /> 10          <tx:method name="remove*" propagation="REQUIRED" /> 11          <tx:method name="put*" propagation="REQUIRED" /> 12          <tx:method name="get*" propagation="SUPPORTS" read-only="true" /> 13          <tx:method name="count*" propagation="SUPPORTS" read-only="true" /> 14         <tx:method name="find*" propagation="SUPPORTS" read-only="true" /> 15         <tx:method name="list*" propagation="SUPPORTS" read-only="true" /> 16         <tx:method name="*" propagation="SUPPORTS" read-only="true" /> 17      </tx:attributes> 18</tx:advice> 19<aop:config> 20      <aop:pointcut id="txPointcut" expression="execution(* org.transaction..service.*.*(..))" /> 21      <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut" /> 22</aop:config>

    同时也可以用注解的方式:

    1<tx:annotation-driven transaction-manager="transactioManager" /><!--开启注解的方式-->
     1@Target({ElementType.METHOD, ElementType.TYPE}) 2@Retention(RetentionPolicy.RUNTIME) 3@Inherited 4@Documented 5public @interface Transactional { 6  @AliasFor("transactionManager") 7  String value() default ""; 8  @AliasFor("value") 9  String transactionManager() default "";10  Propagation propagation() default Propagation.REQUIRED;//传播级别1112  Isolation isolation() default Isolation.DEFAULT;//事务隔离级别13  int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;//事务超时时间14  boolean readOnly() default false;//只读事务15  Class<? extends Throwable>[] rollbackFor() default {};//抛出哪些异常 会执行回滚16  String[] rollbackForClassName() default {};17  Class<? extends Throwable>[] noRollbackFor() default {};18  String[] noRollbackForClassName() default {};//不回滚的异常名称1920}21//transaction注解可以放在方法上或者类上

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

Spring事务传播:

事务传播行为:

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

1EATE TABLE user (2    `id` int(11) NOT NULL AUTO_INCREMENT,3    `username` varchar(255) NOT NULL,4    `pwd` varchar(255) NOT NULL,5    PRIMARY KEY (`id`)6) 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插入的地方主动回滚

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

按照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默认不支持,需要设置来开启。

关注我

zhisheng

今天看啥 - 高品质阅读平台
本文地址:http://www.jintiankansha.me/t/mMRLUpWuoQ
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/23088
 
542 次点击