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


小结

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


小结:

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


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

测试的代码如下,在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() {
        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默认不支持,需要设置来开启。