数据库隔离级别学习示例

数据库隔离级别有四种,每种隔离级别网上的解释已经很多了,这里就不多说了,我们可以看一下spring中对四种隔离级别的定义,其中DEFAULT是默认隔离级别,使用数据库的默认隔离级别。这篇文章主要是想通过一些例子来直观感受一下各种隔离级别。
在这里插入图片描述
本文示例主要使用到springboot,mybatis-plus和lombok。
使用的工具:sqlyog, idea, ScreenToGif(录制屏幕动图)

编写测试代码

我们在service层编写如下代码,addUser方法用来添加用户,getUser方法用来获取用户,其中addUser方法让线程sleep 10秒(原谅我手速不行,多sleep一会),用来模拟事务长时间未提交。

@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void addUser(User user) {
    this.save(user);
    try {
        Thread.sleep(10000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public List<User> getUser() {
    return this.lambdaQuery().eq(User::getName, "zzj").list();
}

controller层通过两个接口分别调用addUser和getUser

@RequestMapping("addUser")
public String addUser() {
    userService.addUser(User.builder().name("zzj").age(18).build());
    return "success";
}

@RequestMapping("getUser")
public List<User> getUser() {
    return userService.getUser();
}

情况1:不加@Transactional

第一种情况,我们先不加@Transactional看看效果,也就是把上面service类中的@Transactional注解去掉。
可以看到,addUser方法还没返回时,数据库就已经有数据了,getUser也能查询到。这也很好理解,因为没有开启事物,addUser方法执行完就会立马提交,不会等后面的sleep执行完再提交。
在这里插入图片描述

情况2:Isolation.READ_UNCOMMITTED

这种情况是最低的隔离级别,读未提交,也就是一个事务还没有结束,另一个事务可以读取到事务没提交的数据。
从下图可以直观地看到,addUser事务还没结束,数据库是查不到数据的,但是我们的getUser方法却获取到了这条数据!也就是读到了另一个事务未提交的数据。
在这里插入图片描述

情况3:Isolation.READ_COMMITTED

我们再把隔离级别设置为读已提交,从名字就可以看出来,这种情况下只能读取到事物已经提交的数据。但是读已提交可能会出现不可重复读的情况。也就是说在同一个事务中两次读取到的数据可能不一样。为了模拟这种情况,我们需要把addUser和getUser方法改变一下。这次让addUser sleep5秒,getUser sleep 8秒。另外有一点要特别注意,因为我用的是mybatis-plus做的测试,要把mybatis-plus的一二级缓存都关闭!不然在同一个方法中用同一个mapper去读的时候会读取缓存,可能会看不到我们想要的效果。


@Transactional(isolation = Isolation.READ_COMMITTED)
public void addUser(User user) {
    this.save(user);
    try {
        Thread.sleep(5000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}
@Transactional(isolation = Isolation.READ_COMMITTED)
public List<User> getUser(User user) {
    List<User> list = this.lambdaQuery().eq(User::getName, "zzj").list();
    System.out.println("第一次尝试读取数据");
    System.out.println(list);
    try {
        Thread.sleep(8000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    list = this.lambdaQuery().eq(User::getName, "zzj").list();
    System.out.println("第二次尝试读取数据");
    System.out.println(list);
    return list;
}

从下图可以明显看出结果,在读已提交隔离级别下,getUser在8秒内的同一个事务中读取两次,读取到的结果不一样,这种情况称之为不可重复读。
在这里插入图片描述

情况4:Isolation.REPEATABLE_READ

还是和情况3一样的例子,我们把隔离级别设置为可重复读再试验一下(REPEATABLE_READ)。
在可重复读的隔离级别下,可以看出,即使另一个事务已经提交了数据,并且数据库中可以查询到,当前事务在没结束前,两次的查询结果是一样的,从而实现了在重复读取数据时不会出现读取到不一致数据的情况。
在这里插入图片描述
不可重复读的隔离级别下会出现的问题是幻读,也就是说数据明明没有读到,但是却可以更新数据,并且更新数据后又可以读到这条数据了。为了试验这种情况,我们把getUser方法再修改一下。

@Transactional(isolation = Isolation.REPEATABLE_READ)
public List<User> getUser() {
    List<User> list = this.lambdaQuery().eq(User::getName, "zzj").list();
    System.out.println("第一次尝试读取数据,读取不到");
    System.out.println(list);
    try {
        Thread.sleep(10000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    list = this.lambdaQuery().eq(User::getName, "zzj").list();
    System.out.println("第二次尝试读取数据,仍然读取不到");
    System.out.println(list);
    
    System.out.println("尝试把name为zzj的数据的age改为20");
    this.lambdaUpdate().eq(User::getName, "zzj").set(User::getAge, 20).update();
    
    list = this.lambdaQuery().eq(User::getName, "zzj").list();
    System.out.println("第三次尝试读取数据,读到了刚才更新的数据");
    System.out.println(list);
    return list;
}

在这里插入图片描述

情况5:Isolation.SERIALIZABLE

这种情况就很简单了,在这种隔离级别下,所有事务串行执行,只有等一个事务结果了另一个事务才会开始执行。把getUser修改为如下,看一下试验。

    @Transactional(isolation = Isolation.SERIALIZABLE)
    public List<User> getUser() {
        List<User> list = this.lambdaQuery().eq(User::getName, "zzj").list();
        System.out.println("getUser读取数据,在addUser5秒结束后才执行,并且读取到了addUser添加的数据");
        System.out.println(list);
    }

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring事务隔离级别默认是Isolation.DEFAULT,即采用数据库默认的事务隔离级别。但是Spring支持设置多种隔离级别,包括: - Isolation.READ_UNCOMMITTED:读取未提交数据,具有最低的隔离级别,存在脏读、不可重复读和幻读的问题。 - Isolation.READ_COMMITTED:读取已提交数据,解决了脏读问题,但仍然存在不可重复读和幻读的问题。 - Isolation.REPEATABLE_READ:可重复读,解决了脏读和不可重复读问题,但仍然存在幻读问题。 - Isolation.SERIALIZABLE:串行化,隔离级别最高,避免了所有并发问题。 Spring使用@Transactional注解来设置事务隔离级别示例如下: ```java @Service @Transactional(isolation = Isolation.READ_COMMITTED) public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; @Override public void transfer(String fromUser, String toUser, int amount) throws Exception { User from = userDao.findByName(fromUser); User to = userDao.findByName(toUser); if (from.getBalance() < amount) { throw new RuntimeException("余额不足"); } from.setBalance(from.getBalance() - amount); to.setBalance(to.getBalance() + amount); userDao.update(from); userDao.update(to); } } ``` 在上述代码中,@Transactional注解的isolation属性设置为Isolation.READ_COMMITTED,表示采用读取已提交数据的隔离级别。在transfer方法中,多个线程同时进行转账操作时,会采用该事务隔离级别来保证数据的一致性和正确性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值