Spring事务之四大隔离级别

声明式事务隔离级别设置

@Transactional(isolation = Isolation.READ_UNCOMMITTED)

多个事务之间会产生的问题

在一个典型的应用程序中,多个事务同时运行,经常会为了完成他们的工作而操作同一个数据。数据库的隔离性与并发控制有很大关系。并发虽然是必需的,但是会导致以下问题:

  • 脏读(Dirty read)
    脏读发生在一个事务读取了被另一个事务改写但尚未提交的数据时。如果这些改变在稍后被回滚了, 那么第一个事务读取的数据就会是无效的。
  • 不可重复读(Nonrepeatable read)
    不可重复读发生在一个事务执行相同的查询两次或两次以上,但每次查询结果都不相同时。这通常是由于另一个并发事务在两次查询之间更新了数据。(不可重复读重点在修改。)
  • 幻读(Phantom reads)
    幻读和不可重复读相似。当一个事务(T1)读取几行记录后,另一个并发事务(T2)插入了一些记录时,幻读就发生了。在后来的查询中,第一个事务(T1)就会发现一些原来没有的额外记录。
    (幻读重点在新增或删除。)

在理想状态下,事务之间将完全隔离,从而可以防止这些问题发生。然而,完全隔离会影响性能,因为隔离经常涉及到锁定在数据库中的记录(甚至有时是锁表)。完全隔离要求事务相互等待来完成工作,会阻碍并发。因此,可以根据业务场景选择不同的隔离级别。

事务的隔离级别

事务设置隔离级别,就是为了解决上述产生的问题的

READ_UNCOMMITTED (未提交读)

顾名思义,READ_UNCOMMITTED意思是,一个事务可以读取到另一个事务未提交的事务记录。换句话说,a transaction can read the data that is still uncommitted by other transactions。这是Spring事务最弱的隔离级别。见下面的图,事务A开启,写入一条记录,这时候,事务B读入数据,读到了这条记录,但是,之后事务A回滚。因此,事务B读到的数据不是有效的(the database is in an invalid state)。这种情况称为 脏读(dirty read)。除了脏读的问题,READ_UNCOMMITTED还可能出现non-repeatable read(不可重复读)phantom read(幻读)的问题。
在这里插入图片描述

READ_COMMITTED (提交后读)(oracle默认级别)

READ_COMMITTED隔离级别表明,一个事务只能读取到已经提交的记录,不能读取到未提交的记录。换句话说,a transaction can only read the committed data, and it can’t read the uncommitted data.因此,脏读 的情况不再发生,但可能会出现其他问题。见下图。
在这里插入图片描述
在事务A两次读取的过程之间,事务B修改了那条记录并进行提交。因此,事务A前后两次读取的记录不一致。这个问题称为non-repeatable read(不可重复读)。(两次读取的记录不一致,重复读取就会发现问题。)

除了non-repeatable read(不可重复读)的问题,READ_COMMITTED还可能发生phantom read(幻读)的问题。

REPEATABLE_READ(可重复读)(mysql默认级别)

REPEATABLE_READ意思是,一个事务可以多次从数据库读取某条记录,而且多次读取的那条记录都是一致的,相同的。这个隔离级别可以避免dirty read和non-repeatable read的问题,但可能发生phantom read的问题。如下图。
在这里插入图片描述
事务A两次从数据库读取一系列记录,期间,事务B插入了某条记录并提交。事务A第二次读取时,会读取到事务B刚刚插入的那条记录。在事务期间,事务A两次读取的一系列记录不一致,这个问题称为 phantom read(幻读)

SERIALIZABLE

SERIALIZABLE是Spring最强的隔离级别。事务执行时,会在所有级别上加锁,比如read和write时都会加锁,仿佛事务是以串行的方式进行的,而不是一起发生的。这会防止dirty read、non-repeatable read和phantom read的出现,但是,会带来性能的下降。

DEFAULT

数据库默认的隔离级别。mysql为可重复读,oracle为提交后读

例子

下面,我们看一个例子。在数据库mysql里开启一个事务,不提交。然后,另一个事务读取记录。
我们使用mybatis和Spring。首先,新建数据库,创建PO类,DAO接口、配置文件和mapper文件,service层,配置文件等
刚开始,数据库里的记录,如图
在这里插入图片描述
接下来,在数据库mysql中开启事务A,并插入一条记录。
在这里插入图片描述
在service的业务类的事务属性配置为READ_UNCOMMITTED

@Transactional(isolation=Isolation.READ_UNCOMMITTED)
public class AccountService {
    private AccountDAO accountDAO;

    public AccountDAO getAccountDAO() {
        return accountDAO;
    }

    public void setAccountDAO(AccountDAO accountDAO) {
        this.accountDAO = accountDAO;
    }


    public void transfer(String from, String to, double money) {
        accountDAO.outMoney(from, money);
        accountDAO.inMoney(to, money);
    }

    public void readAllUser() {
        List<Account> accounts = accountDAO.getAllUser();
        for(Account account : accounts) {
            System.out.println(account);
        }

    }
}

运行下面的测试类

package com.chris.service;

import static org.junit.Assert.*;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class ReadAllUserTest {
    @Autowired
    private AccountService accountService;

    @Test
    public void test() {
        accountService.readAllUser();
    }
}

结果如下:
在这里插入图片描述
可见,这个事务读取到了未提交的数据。

这时候,将mysql中开启的事务A回滚。

mysql> rollback;

再次运行程序,结果为

Account [name=Michael, money=1000.0]
Account [name=Jane, money=1000.0]
Account [name=Kate, money=1000.0]
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值