博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
MyBatis 一级缓存在分布式下的坑你知道吗?
阅读量:3914 次
发布时间:2019-05-23

本文共 6510 字,大约阅读时间需要 21 分钟。

前言:

最近生产环境的余额系统在扣减余额时经常出现余额够但是提示余额不足无法扣减的情况。查看代码逻辑,发现会先查询一次判断余额是否够,再实际扣减,之后查看日志发现并没有执行查询语句就返回错误了,推测可能是 MyBatis 一级缓存没有关闭引起的脏数据问题。关闭一级缓存后果然恢复正常了。

所以有了这篇文章,验证下 MyBatis 一级缓存的生效条件。

一级缓存

MyBatis 默认会开启一级缓存,在同一次会话中,如果执行多次查询条件相同的 SQL,会进行优化,优先命中一级缓存,避免多次查询数据库。

这样在单机环境下是没有问题的,可以减少于数据库的交互。但是生成环境一般都是多机部署,这样一级缓存开启的情况下就容易出现脏数据。

接下来做两个实验验证下。

测试 1

public void test1() {
SqlSession sqlSession = sqlSessionFactory.openSession(true); UserWalletMapperExt mapper = sqlSession.getMapper(UserWalletMapperExt.class); System.out.println(mapper.queryUserBalance(10L, 1)); System.out.println(mapper.queryUserBalance(10L, 1)); System.out.println(mapper.queryUserBalance(10L, 1)); mapper.addBalanceByType(10L, 1, 100L); System.out.println(mapper.queryUserBalance(10L, 1)); System.out.println(mapper.queryUserBalance(10L, 1));}
...queryUserBalance:debug:181 ==>  Preparing: select balance from user_wallet WHERE user_id = ? and type = ? ...queryUserBalance:debug:181 ==> Parameters: 10(Long), 1(Integer)...queryUserBalance:debug:181 <==      Total: 1200200200...addBalanceByType:debug:181 ==>  Preparing: update user_wallet set balance = balance + ? where user_id = ? and type = ? ...addBalanceByType:debug:181 ==> Parameters: 100(Long), 10(Long), 1(Integer)...addBalanceByType:debug:181 <==    Updates: 1...queryUserBalance:debug:181 ==>  Preparing: select balance from user_wallet WHERE user_id = ? and type = ? ...queryUserBalance:debug:181 ==> Parameters: 10(Long), 1(Integer)...queryUserBalance:debug:181 <==      Total: 1300300

可以看到在同一个 session 中,相同的查询在调用更新前只会执行一次。

测试 2

public void test2() {
SqlSession sqlSession1 = sqlSessionFactory.openSession(true); UserWalletMapperExt mapper1 = sqlSession1.getMapper(UserWalletMapperExt.class); System.out.println("session1: " + mapper1.queryUserBalance(10L, 1)); SqlSession sqlSession2 = sqlSessionFactory.openSession(true); UserWalletMapperExt mapper2 = sqlSession2.getMapper(UserWalletMapperExt.class); System.out.println("session2: " + mapper2.queryUserBalance(10L, 1)); mapper2.addBalanceByType(10L, 1, 100L); System.out.println("session2: " + mapper2.queryUserBalance(10L, 1)); System.out.println("session1: " + mapper1.queryUserBalance(10L, 1));}
...queryUserBalance:debug:181 ==>  Preparing: select balance from user_wallet WHERE user_id = ? and type = ? ...queryUserBalance:debug:181 ==> Parameters: 10(Long), 1(Integer)...queryUserBalance:debug:181 <==      Total: 1session1: 500...queryUserBalance:debug:181 ==>  Preparing: select balance from user_wallet WHERE user_id = ? and type = ? ...queryUserBalance:debug:181 ==> Parameters: 10(Long), 1(Integer)...queryUserBalance:debug:181 <==      Total: 1session2: 500...addBalanceByType:debug:181 ==>  Preparing: update user_wallet set balance = balance + ? where user_id = ? and type = ? ...addBalanceByType:debug:181 ==> Parameters: 100(Long), 10(Long), 1(Integer)...addBalanceByType:debug:181 <==    Updates: 1...queryUserBalance:debug:181 ==>  Preparing: select balance from user_wallet WHERE user_id = ? and type = ? ...queryUserBalance:debug:181 ==> Parameters: 10(Long), 1(Integer)...queryUserBalance:debug:181 <==      Total: 1session2: 600session1: 500

可以看到在 session2 更新了值得情况下,session1 依旧使用了一级缓存查询出旧值。

关闭一级缓存

public void test2() {
SqlSession sqlSession1 = sqlSessionFactory.openSession(true); UserWalletMapperExt mapper1 = sqlSession1.getMapper(UserWalletMapperExt.class); System.out.println("session1: " + mapper1.queryUserBalance(10L, 1)); SqlSession sqlSession2 = sqlSessionFactory.openSession(true); UserWalletMapperExt mapper2 = sqlSession2.getMapper(UserWalletMapperExt.class); System.out.println("session2: " + mapper2.queryUserBalance(10L, 1)); mapper2.addBalanceByType(10L, 1, 100L); System.out.println("session2: " + mapper2.queryUserBalance(10L, 1)); System.out.println("session1: " + mapper1.queryUserBalance(10L, 1));}
...queryUserBalance:debug:181 ==>  Preparing: select balance from user_wallet WHERE user_id = ? and type = ? ...queryUserBalance:debug:181 ==> Parameters: 10(Long), 1(Integer)...queryUserBalance:debug:181 <==      Total: 1session1: 500...queryUserBalance:debug:181 ==>  Preparing: select balance from user_wallet WHERE user_id = ? and type = ? ...queryUserBalance:debug:181 ==> Parameters: 10(Long), 1(Integer)...queryUserBalance:debug:181 <==      Total: 1session2: 500...addBalanceByType:debug:181 ==>  Preparing: update user_wallet set balance = balance + ? where user_id = ? and type = ? ...addBalanceByType:debug:181 ==> Parameters: 100(Long), 10(Long), 1(Integer)...addBalanceByType:debug:181 <==    Updates: 1...queryUserBalance:debug:181 ==>  Preparing: select balance from user_wallet WHERE user_id = ? and type = ? ...queryUserBalance:debug:181 ==> Parameters: 10(Long), 1(Integer)...queryUserBalance:debug:181 <==      Total: 1session2: 600session1: 500

可以看到在 session2 更新了值得情况下,session1 依旧使用了一级缓存查询出旧值。

关闭一级缓存

接下来关闭一级缓存试一下,设置 LocalCache 级别为 statement 或者在语句上设置 flushCache=“true” 都可以。

// 设置全局一级缓存级别为 STATEMENTorg.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();configuration.setLocalCacheScope(LocalCacheScope.STATEMENT);sqlSessionFactoryBean.setConfiguration(configuration);

测试

public void test3() {
SqlSession sqlSession = sqlSessionFactory.openSession(true); UserWalletMapperExt mapper = sqlSession.getMapper(UserWalletMapperExt.class); System.out.println(mapper.queryUserBalance(10L, 1)); System.out.println(mapper.queryUserBalance(10L, 1)); System.out.println(mapper.queryUserBalance(10L, 1));}
...queryUserBalance:debug:181 ==>  Preparing: select balance from user_wallet WHERE user_id = ? and type = ? ...queryUserBalance:debug:181 ==> Parameters: 10(Long), 1(Integer)...queryUserBalance:debug:181 <==      Total: 1600...queryUserBalance:debug:181 ==>  Preparing: select balance from user_wallet WHERE user_id = ? and type = ? ...queryUserBalance:debug:181 ==> Parameters: 10(Long), 1(Integer)...queryUserBalance:debug:181 <==      Total: 1600...queryUserBalance:debug:181 ==>  Preparing: select balance from user_wallet WHERE user_id = ? and type = ? ...queryUserBalance:debug:181 ==> Parameters: 10(Long), 1(Integer)...queryUserBalance:debug:181 <==      Total: 1600

可以看到关闭一级缓存后所有语句都会实际执行。

总结

MyBatis 的一级缓存在单机环境下可以减少与 MySql 的交互,提高性能,但是在分布式环境下容易产生脏数据。建议在生产环境下关闭,使用 Redis,Memcache 等代替。

最后

整理了 1000 道多家公司 java 面试题 400 多页 pdf 文档,都已经分专题整理好了。还有几百页的Java核心知识点PDF。

欢迎大家领取,点击: 加入。验证:CSDN,即可免费领取。

部分如下

Java面试体系400题整理

在这里插入图片描述

Redis学习笔记

在这里插入图片描述

转载地址:http://srdrn.baihongyu.com/

你可能感兴趣的文章
Istio Pilot 源码分析(二)
查看>>
微软发布.NET 5.0 RC1,未来将只有一个.NET
查看>>
ASP.NET Core Blazor Webassembly 之 路由
查看>>
跟我一起学.NetCore之静态文件处理的那些事
查看>>
《ASP.NET Core 真机拆解》 送书活动结果公布
查看>>
.NET Core 使用 Consul 服务注册发现
查看>>
RabbitMq如何确保消息不丢失
查看>>
ASP.NET Core 基于声明的访问控制到底是什么鬼?
查看>>
收好这张MySQL导图,全是知识点!
查看>>
[C#.NET 拾遗补漏]09:数据标注与数据校验
查看>>
微服务模式下,实现前后端多资源服务调用
查看>>
BeetleX之HTTP网关部署
查看>>
跟我一起学.NetCore之路由的最佳实现
查看>>
楼继伟:现有5G技术很不成熟
查看>>
.NET Core 下的 API 网关
查看>>
遍历 Dictionary,你会几种方式?
查看>>
dotnet 在 UOS 国产系统上使用 Xamarin Forms 创建 xaml 界面的 GTK 应用
查看>>
ABP VNext从单体切换到微服务
查看>>
C# 中 System.Index 结构体和 Hat 运算符(^)的全新用法
查看>>
.NET Core中间件与依赖注入的一些思考
查看>>