Mybatis---一级缓存--二级缓存------缓存穿透,缓存击穿,缓存雪崩

Mybatis缓存

为什么使用缓存

缓存(也成为cache)的作用是为了减去数据库的压力,提高数据库的性能,缓存实现的原理是从数据库中查询处理的对象再使用完后不要销毁,而是储存在内存(缓存)中,当再次需要获取该对象的时,直接从内存(缓存)中直接获取,不在向数据库执行select语句,从而减少了对数据库的查询次数,因此提高了数据库的性能,缓存是使用Map集合缓存数据的

缓存: 
将数据临时存储(本地硬盘,内存),这样就减少了对数据库的访问
mybatis 提供一级缓存  二级缓存

一级缓存是Sqlsession级别的
    在同一个Sqlsession中可以将第一次查询到的数据缓存到Sqlsession
    第二次查询相同数据时,就可以直接从Sqlsession中获取
    缓存失效
    close
    clearCache
    执行新增,修改,删除操作会清空一级缓存

二级缓存是SqlSessionFactory级别(整个程序只有一个SqlSessionFactory)
	以namespace 划分存储的区域

一级缓存

  • 一级缓存的作用域是同一个SqlSession
  • 同一个sqlSession中两次执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。
  • 当一个sqlSession结束后该sqlSession中的一级缓存也就不存在了
  • Mybatis默认开启一级缓存。

在这里插入图片描述

一级缓存的生命周期

  • mybatis在开启一个数据库会话时…会创建一个新的SqlSession对象,SqlSession对象中会有一个新的Executor对象,Executor对象中持有一个新的PerpetualCache对象,当会话结束的时候,sqlSqssion对象及其内部的Executor对象还有PerpetualCache对象也一并释放掉
  • 如果SqlSession调用close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可使用
  • 如果SqlSession调用了clearCache(),会清空PerpetualCache对象
    中的数据,但是该对象仍可使用
  • SqlSession中执行了任何一个update操作(update()delete()insert()),都会清空PerpetualCache对象的数据,但是该对象可以继续使用


二级缓存

  • 是多个SqlSession共享的,其作用域是mapper的同一个namespace,

    <mapper namespace="com.nie.dao.StudentDao">
    
  • 不同的sqlSession两次执行相同namespace下的sql语句且向sql中传递参数也相同即最终执行相同的sql语句,第一次执行完毕会将数据库中查询的数据写到缓存(内存),第二次会从缓存中获取数据将不再从数据库查询,从而提高查询效率。

  • Mybatis默认没有开启二级缓存需要在setting全局参
    数中配置开启二级缓存。

二级缓存

二级缓存详解

二级缓存区域是根据mapper的namespace划分的,相同namespace的mapper查询的数据缓存在同一个区域,如果使用mapper代理方法每个mappernamespace都不同,此时可以理解为二级缓存区域是根据mapper划分。

每次查询会先从缓存区域中查找,如果找不到则从数据库查询,并并将查询到数据写入缓存

Mybatis内部缓存使用一个hashMap,Key为hashCode+sqlId+Sql语句,Value为从从查询出来映射的java对象

sqlSession执行insert、update、delete等操作commit提交后会清空缓存区
域,防止脏读。

在这里插入图片描述

配置二级缓存配置

第一步:

启用二级缓存

在SqlMapperConfig.xml中启用二级缓存,如下代码所示,当cacheEnabled设置为true时启用二级缓存,设置为false时禁用二级缓存。
<settingname="cacheEnabled" value="true"/>

第二步:POJO序列化

将所有的POJO类实现序列化接口Java.io.Serializable。

import java.io.Serializable;

public class Student implements Serializable {
.......
}

第三步:配置映射文件

在Mapper映射文件中添加,表示此mapper开启二级缓存

  <!--使用namespace使用二级缓存-->
    <cache
            eviction="FIFO"
            flushInterval="60000"
            size="512"
            readOnly="true">

    </cache>

这个更高级的配置创建了一个FIFP缓存,每隔60S刷新一次,存储集合或对象的512个引用,而且返回的对象被认为是只读的,因而在不同线程中的调用者之间修改它们会导致冲突。

cache可以配置的属性如下:

eviction(收回策略)

LRU 最近最少使用的,移除最长时间不被使用的对象,这是默认值
FIFO 先进先出,按对象进入缓存的顺序来移除它们
SOFT 软引用,移除基于垃圾回收器状态和软引用规则的对象
WEAK 弱引用,更积极的移除基于垃圾收集器状态和弱引用规则的对象

flushInterval(刷新间隔)

可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式的时间段。 默认情况不设置,即没有刷新间隔,缓存仅仅在调用语句时刷新

size(引用数目)

可以被设置为任意的正整数,要记住缓存的对象数目和运行环境的可用内存资源数目,默认1024

readOnly(只读)

属性可以被设置为true后者false。 只读的缓存会给所有调用者返回缓存对象的相同实例,因此这些对象不能被修改,这提供了很重要的性能优势。 可读写的缓存会通过序列化返回缓存对象的拷贝,这种方式会慢一些,但很安全,因此默认为false

在这里插入图片描述

在这里插入图片描述

MyBatisUtils
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;

public class MyBatisUtils {
    private static SqlSessionFactory factory = null;

    static {
        String config = "mybatis.xml";

        try {
            InputStream resourceAsStream = Resources.getResourceAsStream(config);
            factory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    //创建方法,获取sqlSession
    public static SqlSession getSqlSession() {
        SqlSession SqlSession = null;
        if (factory != null) {
            SqlSession = factory.openSession();//非自动提交事务
        }
        return SqlSession;
    }

    //创建方法,获取SqlSessionFactory
    public static SqlSessionFactory getsqlSessionFactory() {
     return (SqlSessionFactory) factory;
    }
}

在这里插入图片描述



相关的动态sql

生命周期和作用域

生命周期和作用域是至关重要的,因为错误的使用会导致非常严重的并发问题

SqlSessionFactoryBuilder

这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。
因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。

SqlSessionFactory

说白了就是数据库连接池。SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。
使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因此 SqlSessionFactory 的最佳作用域是应用作用域。

SqlSession

连接到连接池的一个请求每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。用完之后需要赶紧关闭,否则资源被占用



缓存穿透,缓存击穿,缓存雪崩

相关详情

在这里插入图片描述

缓存穿透

是指查询一个一定不存在的数据,由于缓存是不命中时被动写的,并别出于容错考虑,如果从储存层查不到数据则不写入缓存,则将导致这个不存在的数据每次请求都要到储存层去查询,失去了缓存的意义,在流量大的时,可能数据库就挂掉,要是有人利用不存在的key频繁攻击我们的应用,这就是漏洞

缓存穿透是指缓存和数据库中都没有的数据,而用户不断发起请求,如发起为id为“-1”的数据或id为特别大不存在的数据。这时的用户很可能是攻击者,攻击会导致数据库压力过大。

解决方案:
  1. 有很多种方法可以有效地解决缓存穿透问题,接口层增加校验,如用户鉴权校验,最常见的则是采用布隆过滤器,将所有可能存在的数据哈希到一个足够大的bitmap中,一个一定不存在的数据会被 这个bitmap拦截掉,从而避免了对底层存储系统的查询压力
  2. 另外也有一个更为简单粗暴的方法(我们采用的就是这种),如果一个查询返回的数据为空(不管是数 据不存在,还是系统故障),我们仍然把这个空结果进行缓存,但它的过期时间会很短,最长不超过五分钟。

缓存雪崩

是指我们设置缓存时采用了相同的过期时间,导致缓存在某一时间同时失效,请求全部转发到的DB,DB瞬间压力过于重 雪崩

解决方案

缓存失效时的雪崩效应对底层系统的冲击非常可怕。

  1. 考虑加锁或者从队列的方式保证缓存的单线程写,从而避免了失效时大量的并发请求落在到底层数储存系统上.
  2. 一个简单方案就时讲缓存失效时间分散开,比如可以在原有的失效时间上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
  3. 如果缓存数据库是分布式部署,将热点数据均匀分布在不同搞得缓存数据库中。
  4. 设置热点数据永远不过期。

缓存击穿

缓存击穿是指缓存中没有但是数据库中有的数据(一般是缓存时间到期),这时由于并发用户特别多,同时读缓存没有读到的数据,又同时去数据库取数据,引起数据库压力瞬间增大,造成过大压力

缓存被“击穿”的问题,这个和缓存雪崩的区别在于这里针对某一key缓存,前者则是很多key。

解决方案
  1. 设置热点数据永远不过期。
  2. 加互斥锁,互斥锁参考代码如下

在这里插入图片描述

1)缓存中有数据,直接走上述代码13行后就返回结果了

2)缓存中没有数据,第1个进入的线程,获取锁并从数据库去取数据,没释放锁之前,其他并行进入的线程会等待100ms,再重新去缓存取数据。这样就防止都去数据库重复取数据,重复往缓存中更新数据情况出现。

3)当然这是简化处理,理论上如果能根据key值加锁就更好了,就是线程A从数据库取key1的数据并不妨碍线程B取key2的数据,上面代码明显做不到这点。

相关优秀文章

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值