精华内容
下载资源
问答
  • MyBatis一级缓存和二级缓存

    万次阅读 多人点赞 2018-09-05 22:44:30
    MyBatis自带的缓存有一级缓存和二级缓存 一级缓存 Mybatis的一级缓存是指Session缓存。一级缓存的作用域默认是一个SqlSession。Mybatis默认开启一级缓存。 也就是在同一个SqlSession中,执行相同的查询SQL,第一次...

    MyBatis自带的缓存有一级缓存和二级缓存

    一级缓存

    Mybatis的一级缓存是指Session缓存。一级缓存的作用域默认是一个SqlSession。Mybatis默认开启一级缓存。
    也就是在同一个SqlSession中,执行相同的查询SQL,第一次会去数据库进行查询,并写到缓存中;
    第二次以后是直接去缓存中取。
    当执行SQL查询中间发生了增删改的操作,MyBatis会把SqlSession的缓存清空。

    一级缓存的范围有SESSION和STATEMENT两种,默认是SESSION,如果不想使用一级缓存,可以把一级缓存的范围指定为STATEMENT,这样每次执行完一个Mapper中的语句后都会将一级缓存清除。
    如果需要更改一级缓存的范围,可以在Mybatis的配置文件中,在下通过localCacheScope指定。

    1

    <setting name="localCacheScope" value="STATEMENT"/>

    建议不需要修改

    需要注意的是
    当Mybatis整合Spring后,直接通过Spring注入Mapper的形式,如果不是在同一个事务中每个Mapper的每次查询操作都对应一个全新的SqlSession实例,这个时候就不会有一级缓存的命中,但是在同一个事务中时共用的是同一个SqlSession。
    如有需要可以启用二级缓存。

    二级缓存

    Mybatis的二级缓存是指mapper映射文件。二级缓存的作用域是同一个namespace下的mapper映射文件内容,多个SqlSession共享。Mybatis需要手动设置启动二级缓存。

    二级缓存是默认启用的(要生效需要对每个Mapper进行配置),如想取消,则可以通过Mybatis配置文件中的元素下的子元素来指定cacheEnabled为false。

    1

    2

    3

    <settings>

      <setting name="cacheEnabled" value="false" />

    </settings>

    cacheEnabled默认是启用的,只有在该值为true的时候,底层使用的Executor才是支持二级缓存的CachingExecutor。具体可参考Mybatis的核心配置类org.apache.ibatis.session.Configuration的newExecutor方法实现。
    可以通过源码看看

    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

    ...

        public Executor newExecutor(Transaction transaction) {

            return this.newExecutor(transaction, this.defaultExecutorType);

        }

     

        public Executor newExecutor(Transaction transaction, ExecutorType executorType) {

            executorType = executorType == null this.defaultExecutorType : executorType;

            executorType = executorType == null ? ExecutorType.SIMPLE : executorType;

            Object executor;

            if (ExecutorType.BATCH == executorType) {

                executor = new BatchExecutor(this, transaction);

            else if (ExecutorType.REUSE == executorType) {

                executor = new ReuseExecutor(this, transaction);

            else {

                executor = new SimpleExecutor(this, transaction);

            }

     

            if (this.cacheEnabled) {//设置为true才执行的

                executor = new CachingExecutor((Executor)executor);

            }

     

            Executor executor = (Executor)this.interceptorChain.pluginAll(executor);

            return executor;

        }

    ...

    要使用二级缓存除了上面一个配置外,我们还需要在我们每个DAO对应的Mapper.xml文件中定义需要使用的cache

    1

    2

    3

    4

    5

    ...

    <mapper namespace="...UserMapper">

        <cache/><!-- 加上该句即可,使用默认配置、还有另外一种方式,在后面写出 -->

        ...

    </mapper>

    具体可以看org.apache.ibatis.executor.CachingExecutor类的以下实现
    其中使用的cache就是我们在对应的Mapper.xml中定义的cache。

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {

        BoundSql boundSql = ms.getBoundSql(parameterObject);

        CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);

        return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);

    }

     

    public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {

        Cache cache = ms.getCache();

        if (cache != null) {//第一个条件 定义需要使用的cache 

            this.flushCacheIfRequired(ms);

            if (ms.isUseCache() && resultHandler == null) {//第二个条件 需要当前的查询语句是配置了使用cache的,即下面源码的useCache()是返回true的  默认是true

                this.ensureNoOutParams(ms, parameterObject, boundSql);

                List<E> list = (List)this.tcm.getObject(cache, key);

                if (list == null) {

                    list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);

                    this.tcm.putObject(cache, key, list);

                }

     

                return list;

            }

        }

     

        return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);

    }

    还有一个条件就是需要当前的查询语句是配置了使用cache的,即上面源码的useCache()是返回true的,默认情况下所有select语句的useCache都是true,如果我们在启用了二级缓存后,有某个查询语句是我们不想缓存的,则可以通过指定其useCache为false来达到对应的效果。
    如果我们不想该语句缓存,可使用useCache="false

    1

    2

    3

    4

    5

    6

    <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.String" useCache="false">

            select

            <include refid="Base_Column_List"/>

            from tuser

            where id = #{id,jdbcType=VARCHAR}

        </select>

    cache定义的两种使用方式

    上面说了要想使用二级缓存,需要在每个DAO对应的Mapper.xml文件中定义其中的查询语句需要使用cache来缓存数据的。
    这有两种方式可以定义,一种是通过cache元素定义,一种是通过cache-ref元素来定义。
    需要注意的是
    对于同一个Mapper来讲,只能使用一个Cache,当同时使用了和时,定义的优先级更高(后面的代码会给出原因)。
    Mapper使用的Cache是与我们的Mapper对应的namespace绑定的,一个namespace最多只会有一个Cache与其绑定。

    cache元素定义

    使用cache元素来定义使用的Cache时,最简单的做法是直接在对应的Mapper.xml文件中指定一个空的元素(看前面的代码),这个时候Mybatis会按照默认配置创建一个Cache对象,准备的说是PerpetualCache对象,更准确的说是LruCache对象(底层用了装饰器模式)。
    具体的可看org.apache.ibatis.builder.xml.XMLMapperBuilder中的cacheElement()方法解析cache元素的逻辑。

    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

    27

    28

    29

    30

    31

    32

    33

    34

    35

    36

    37

    38

    39

    40

    41

    42

    43

    44

    45

    46

    47

    48

    49

    ...

        private void configurationElement(XNode context) {

            try {

                String namespace = context.getStringAttribute("namespace");

                if (namespace.equals("")) {

                    throw new BuilderException("Mapper's namespace cannot be empty");

                else {

                    this.builderAssistant.setCurrentNamespace(namespace);

                    this.cacheRefElement(context.evalNode("cache-ref"));

                    this.cacheElement(context.evalNode("cache"));//执行在后面

                    this.parameterMapElement(context.evalNodes("/mapper/parameterMap"));

                    this.resultMapElements(context.evalNodes("/mapper/resultMap"));

                    this.sqlElement(context.evalNodes("/mapper/sql"));

                    this.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));

                }

            catch (Exception var3) {

                throw new BuilderException("Error parsing Mapper XML. Cause: " + var3, var3);

            }

        }

    ...

        private void cacheRefElement(XNode context) {

            if (context != null) {

                this.configuration.addCacheRef(this.builderAssistant.getCurrentNamespace(), context.getStringAttribute("namespace"));

                CacheRefResolver cacheRefResolver = new CacheRefResolver(this.builderAssistant, context.getStringAttribute("namespace"));

     

                try {

                    cacheRefResolver.resolveCacheRef();

                catch (IncompleteElementException var4) {

                    this.configuration.addIncompleteCacheRef(cacheRefResolver);

                }

            }

     

        }

     

        private void cacheElement(XNode context) throws Exception {

            if (context != null) {

                String type = context.getStringAttribute("type""PERPETUAL");

                Class<? extends Cache> typeClass = this.typeAliasRegistry.resolveAlias(type);

                String eviction = context.getStringAttribute("eviction""LRU");

                Class<? extends Cache> evictionClass = this.typeAliasRegistry.resolveAlias(eviction);

                Long flushInterval = context.getLongAttribute("flushInterval");

                Integer size = context.getIntAttribute("size");

                boolean readWrite = !context.getBooleanAttribute("readOnly"false).booleanValue();

                Properties props = context.getChildrenAsProperties();

                this.builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, props);

    //如果同时存在<cache>和<cache-ref>,这里的设置会覆盖前面的cache-ref的缓存

            }

     

        }

    空cache元素定义会生成一个采用最近最少使用算法最多只能存储1024个元素的缓存,而且是可读写的缓存,即该缓存是全局共享的,任何一个线程在拿到缓存结果后对数据的修改都将影响其它线程获取的缓存结果,因为它们是共享的,同一个对象。

    cache元素可指定如下属性,每种属性的指定都是针对都是针对底层Cache的一种装饰,采用的是装饰器的模式。

    1. blocking:默认为false,当指定为true时将采用BlockingCache进行封装,blocking,阻塞的意思,使用BlockingCache会在查询缓存时锁住对应的Key,如果缓存命中了则会释放对应的锁,否则会在查询数据库以后再释放锁,这样可以阻止并发情况下多个线程同时查询数据,详情可参考BlockingCache的源码。
      简单理解,也就是设置true时,在进行增删改之后的并发查询,只会有一条去数据库查询,而不会并发

       

    2. eviction:eviction,驱逐的意思。也就是元素驱逐算法,默认是LRU,对应的就是LruCache,其默认只保存1024个Key,超出时按照最近最少使用算法进行驱逐,详情请参考LruCache的源码。如果想使用自己的算法,则可以将该值指定为自己的驱逐算法实现类,只需要自己的类实现Mybatis的Cache接口即可。除了LRU以外,系统还提供了FIFO(先进先出,对应FifoCache)、SOFT(采用软引用存储Value,便于垃圾回收,对应SoftCache)和WEAK(采用弱引用存储Value,便于垃圾回收,对应WeakCache)这三种策略。
      这里,根据个人需求选择了,没什么要求的话,默认的LRU即可

    3. flushInterval:清空缓存的时间间隔,单位是毫秒,默认是不会清空的。当指定了该值时会再用ScheduleCache包装一次,其会在每次对缓存进行操作时判断距离最近一次清空缓存的时间是否超过了flushInterval指定的时间,如果超出了,则清空当前的缓存,详情可参考ScheduleCache的实现。

    4. readOnly:是否只读
      默认为false。当指定为false时,底层会用SerializedCache包装一次,其会在写缓存的时候将缓存对象进行序列化,然后在读缓存的时候进行反序列化,这样每次读到的都将是一个新的对象,即使你更改了读取到的结果,也不会影响原来缓存的对象,即非只读,你每次拿到这个缓存结果都可以进行修改,而不会影响原来的缓存结果;
      当指定为true时那就是每次获取的都是同一个引用,对其修改会影响后续的缓存数据获取,这种情况下是不建议对获取到的缓存结果进行更改,意为只读(不建议设置为true)。
      这是Mybatis二级缓存读写和只读的定义,可能与我们通常情况下的只读和读写意义有点不同。每次都进行序列化和反序列化无疑会影响性能,但是这样的缓存结果更安全,不会被随意更改,具体可根据实际情况进行选择。详情可参考SerializedCache的源码。

    5. size:用来指定缓存中最多保存的Key的数量。其是针对LruCache而言的,LruCache默认只存储最多1024个Key,可通过该属性来改变默认值,当然,如果你通过eviction指定了自己的驱逐算法,同时自己的实现里面也有setSize方法,那么也可以通过cache的size属性给自定义的驱逐算法里面的size赋值。

    6. type:type属性用来指定当前底层缓存实现类,默认是PerpetualCache,如果我们想使用自定义的Cache,则可以通过该属性来指定,对应的值是我们自定义的Cache的全路径名称。

    cache-ref元素定义

    cache-ref元素可以用来指定其它Mapper.xml中定义的Cache,有的时候可能我们多个不同的Mapper需要共享同一个缓存的
    是希望在MapperA中缓存的内容在MapperB中可以直接命中的,这个时候我们就可以考虑使用cache-ref,这种场景只需要保证它们的缓存的Key是一致的即可命中,二级缓存的Key是通过Executor接口的createCacheKey()方法生成的,其实现基本都是BaseExecutor,源码如下。

    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

    27

    28

    29

    30

    31

    32

    33

    34

    35

    public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {

        if (this.closed) {

            throw new ExecutorException("Executor was closed.");

        else {

            CacheKey cacheKey = new CacheKey();

            cacheKey.update(ms.getId());

            cacheKey.update(rowBounds.getOffset());

            cacheKey.update(rowBounds.getLimit());

            cacheKey.update(boundSql.getSql());

            List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();

            TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();

     

            for(int i = 0; i < parameterMappings.size(); ++i) {

                ParameterMapping parameterMapping = (ParameterMapping)parameterMappings.get(i);

                if (parameterMapping.getMode() != ParameterMode.OUT) {

                    String propertyName = parameterMapping.getProperty();

                    Object value;

                    if (boundSql.hasAdditionalParameter(propertyName)) {

                        value = boundSql.getAdditionalParameter(propertyName);

                    else if (parameterObject == null) {

                        value = null;

                    else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {

                        value = parameterObject;

                    else {

                        MetaObject metaObject = this.configuration.newMetaObject(parameterObject);

                        value = metaObject.getValue(propertyName);

                    }

     

                    cacheKey.update(value);

                }

            }

     

            return cacheKey;

        }

    }

    打个比方我想在MenuMapper.xml中的查询都使用在UserMapper.xml中定义的Cache,则可以通过cache-ref元素的namespace属性指定需要引用的Cache所在的namespace,即UserMapper.xml中的定义的namespace,假设在UserMapper.xml中定义的namespace是cn.chenhaoxiang.dao.UserMapper,则在MenuMapper.xml的cache-ref应该定义如下。

    1

    <cache-ref namespace="cn.chenhaoxiang.dao.UserMapper"/>

    这样这两个Mapper就共享同一个缓存了

    自定义cache就不介绍了。

    测试二级缓存

    查询测试

    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

    27

    28

    <br />/**

     * Created with IntelliJ IDEA.

     * User: 陈浩翔.

     * Date: 2018/1/10.

     * Time: 下午 10:15.

     * Explain:

     */

    @RunWith(SpringJUnit4ClassRunner.class)

    //配置了@ContextConfiguration注解并使用该注解的locations属性指明spring和配置文件之后

    @ContextConfiguration(locations = {"classpath:spring.xml","classpath:spring-mybatis.xml"})

    public class MyBatisTestBySpringTestFramework {

     

        //注入userService

        @Autowired

        private UserService userService;

     

        @Test

        public void testGetUserId(){

            String userId = "4e07f3963337488e81716cfdd8a0fe04";

            User user = userService.getUserById(userId);

            System.out.println(user);

     

     

            //前面说到spring和MyBatis整合

            User user2 = userService.getUserById(userId);

            System.out.println("user2:"+user2);

        }

    }

    接下来我们把Mapper中的cache元素删除,不使用二级缓存

    再运行测试

    对二级缓存进行了以下测试,获取两个不同的SqlSession(前面有说,Spring和MyBatis集成,每次都是不同的SqlSession)执行两条相同的SQL,在未指定Cache时Mybatis将查询两次数据库,在指定了Cache时Mybatis只查询了一次数据库,第二次是从缓存中拿的。

    Cache Hit Ratio 表示缓存命中率。
    开启二级缓存后,每执行一次查询,系统都会计算一次二级缓存的命中率。
    第一次查询也是先从缓存中查询,只不过缓存中一定是没有的。
    所以会再从DB中查询。由于二级缓存中不存在该数据,所以命中率为0.但第二次查询是从二级缓存中读取的,所以这一次的命中率为1/2=0.5。
    当然,若有第三次查询,则命中率为1/3=0.66
    0.5这个值可以从上面开启cache的图看出来,0.0的值未截取到~漏掉了~

    注意:
    增删改操作,无论是否进行提交sqlSession.commit(),均会清空一级、二级缓存,使查询再次从DB中select。
    说明:
    二级缓存的清空,实质上是对所查找key对应的value置为null,而非将对,即entry对象删除。
    从DB中进行select查询的条件是:缓存中根本不存在这个key或者缓存中存在该key所对应的entry对象,但value为null。

    设置增删改操作不刷新二级缓存:
    若要使某个增、删或改操作不清空二级缓存,则需要在其或或中添加属性flushCache="false",默认为true。

    二级缓存的使用原则

    1. 只能在一个命名空间下使用二级缓存
      由于二级缓存中的数据是基于namespace的,即不同namespace中的数据互不干扰。在多个namespace中若均存在对同一个表的操作,那么这多个namespace中的数据可能就会出现不一致现象。
    2. 在单表上使用二级缓存
      如果一个表与其它表有关联关系,那么久非常有可能存在多个namespace对同一数据的操作。而不同namespace中的数据互补干扰,所以就有可能出现多个namespace中的数据不一致现象。
    3. 查询多于修改时使用二级缓存
      在查询操作远远多于增删改操作的情况下可以使用二级缓存。因为任何增删改操作都将刷新二级缓存,对二级缓存的频繁刷新将降低系统性能。
    展开全文
  • 大家都知道CPU缓存很重要,但对于缓存的具体细分却知之甚少,本文只要是关于CPU缓存的介绍,并着重描述了一级缓存、二级缓存、三级缓存区别方法。 CPU缓存 CPU缓存(Cache Memory)是位于CPU与内存之间的临时存储器...

    大家都知道CPU缓存很重要,但对于缓存的具体细分却知之甚少,本文只要是关于CPU缓存的介绍,并着重描述了一级缓存、二级缓存、三级缓存区别方法。

    CPU缓存

    CPU缓存(Cache Memory)是位于CPU与内存之间的临时存储器,它的容量比内存小的多但是交换速度却比内存要快得多。高速缓存的出现主要是为了解决CPU运算速度与内存读写速度不匹配的矛盾,因为CPU运算速度要比内存读写速度快很多,这样会使CPU花费很长时间等待数据到来或把数据写入内存。在缓存中的数据是内存中的一小部分,但这一小部分是短时间内CPU即将访问的,当CPU调用大量数据时,就可先缓存中调用,从而加快读取速度。
    在这里插入图片描述
    CPU缓存的容量比内存小的多但是交换速度却比内存要快得多。缓存的出现主要是为了解决CPU运算速度与内存读写速度不匹配的矛盾,因为CPU运算速度要比内存读写速度快很多,这样会使CPU花费很长时间等待数据到来或把数据写入内存。

    缓存大小是CPU的重要指标之一,而且缓存的结构和大小对CPU速度的影响非常大,CPU内缓存的运行频率极高,一般是和处理器同频运作,工作效率远远大于系统内存和硬盘。实际工作时,CPU往往需要重复读取同样的数据块,而缓存容量的增大,可以大幅度提升CPU内部读取数据的命中率,而不用再到内存或者硬盘上寻找,以此提高系统性能。但是从CPU芯片面积和成本的因素来考虑,缓存都很小。

    按照数据读取顺序和与CPU结合的紧密程度,CPU缓存可以分为一级缓存,二级缓存,部分高端CPU还具有三级缓存,每一级缓存中所储存的全部数据都是下一级缓存的一部分,这三种缓存的技术难度和制造成本是相对递减的,所以其容量也是相对递增的。当CPU要读取一个数据时,首先从一级缓存中查找,如果没有找到再从二级缓存中查找,如果还是没有就从三级缓存或内存中查找。一般来说,每级缓存的命中率大概都在80%左右,也就是说全部数据量的80%都可以在一级缓存中找到,只剩下20%的总数据量才需要从二级缓存、三级缓存或内存中读取,由此可见一级缓存是整个CPU缓存架构中最为重要的部分。

    一级缓存、二级缓存、三级缓存区别是什么?

    一级缓存、二级缓存、三级缓存是什么?作用?区别? 首先简单了解一下一级缓存。目前所有主流处理器大都具有一级缓存和二级缓存,少数高端处理器还集成了三级缓存。其中,一级缓存可分为一级指令缓存和一级数据缓存。一级指令缓存用于暂时存储并向CPU递送各类运算指令;一级数据缓存用于暂时存储并向CPU递送运算所需数据,这就是一级缓存的作用。 那么,二级缓存的作用又是什么呢?简单地说,二级缓存就是一级缓存的缓冲器:一级缓存制造成本很高因此它的容量有限,二级缓存的作用就是存储那些CPU处理时需要用到、一级缓存又无法存储的数据。同样道理,三级缓存和内存可以看作是二级缓存的缓冲器,它们的容量递增,但单位制造成本却递减。

    需要注意的是,无论是二级缓存、三级缓存还是内存都不能存储处理器操作的原始指令,这些指令只能存储在CPU的一级指令缓存中,而余下的二级缓存、三级缓存和内存仅用于存储CPU所需数据。 根据工作原理的不同,目前主流处理器所采用的一级数据缓存又可以分为实数据读写缓存和数据代码指令追踪缓存2种,它们分别被AMD和Intel所采用。不同的一级数据缓存设计对于二级缓存容量的需求也各不相同,下面让我们简单了解一下这两种一级数据缓存设计的不同之处。

    一、AMD一级数据缓存设计 AMD采用的一级缓存设计属于传统的“实数据读写缓存”设计。基于该架构的一级数据缓存主要用于存储CPU最先读取的数据;而更多的读取数据则分别存储在二级缓存和系统内存当中。做个简单的假设,假如处理器需要读取“AMD ATHLON 64 3000+ IS GOOD”这一串数据(不记空格),那么首先要被读取的“AMDATHL”将被存储在一级数据缓存中,而余下的“ON643000+ISGOOD”则被分别存储在二级缓存和系统内存当中(如下图所示)。 需要注意的是,以上假设只是对AMD处理器一级数据缓存的一个抽象描述,一级数据缓存和二级缓存所能存储的数据长度完全由缓存容量的大小决定,而绝非以上假设中的几个字节。“实数据读写缓存”的优点是数据读取直接快速,但这也需要一级数据缓存具有一定的容量,增加了处理器的制造难度(一级数据缓存的单位制造成本较二级缓存高)。

    二、Intel一级数据缓存设计 自P4时代开始,Intel开始采用全新的“数据代码指令追踪缓存”设计。基于这种架构的一级数据缓存不再存储实际的数据,而是存储这些数据在二级缓存中的指令代码(即数据在二级缓存中存储的起始地址)。假设处理器需要读取“INTEL P4 IS GOOD”这一串数据(不记空格),那么所有数据将被存储在二级缓存中,而一级数据代码指令追踪缓存需要存储的仅仅是上述数据的起始地址。

    由于一级数据缓存不再存储实际数据,因此“数据代码指令追踪缓存”设计能够极大地降CPU对一级数据缓存容量的要求,降低处理器的生产难度。但这种设计的弊端在于数据读取效率较“实数据读写缓存设计”低,而且对二级缓存容量的依赖性非常大。 在了解了一级缓存、二级缓存的大致作用及其分类以后,下面我们来回答以下硬件一菜鸟网友提出的问题。

    从理论上讲,二级缓存越大处理器的性能越好,但这并不是说二级缓存容量加倍就能够处理器带来成倍的性能增长。目前CPU处理的绝大部分数据的大小都在0-256KB之间,小部分数据的大小在256KB-512KB之间,只有极少数数据的大小超过512KB。所以只要处理器可用的一级、二级缓存容量达到256KB以上,那就能够应付正常的应用;512KB容量的二级缓存已经足够满足绝大多数应用的需求。 这其中,对于采用“实数据读写缓存”设计的AMD Athlon 64、Sempron处理器而言,由于它们已经具备了64KB一级指令缓存和64KB一级数据缓存,只要处理器的二级缓存容量大于等于128KB就能够存储足够的数据和指令,因此它们对二级缓存的依赖性并不大。这就是为什么主频同为1.8GHz的Socket 754 Sempron 3000+(128KB二级缓存)、Sempron 3100+(256KB二级缓存)以及Athlon 64 2800+(512KB二级缓存)在大多数评测中性能非常接近的主要原因。所以对于普通用户而言754 Sempron 2600+是值得考虑的。 反观Intel目前主推的P4、赛扬系列处理器,它们都采用了“数据代码指令追踪缓存”架构,其中Prescott内核的一级缓存中只包含了12KB一级指令缓存和16KB一级数据缓存,而Northwood内核更是只有12KB一级指令缓存和8KB一级数据缓存。

    因此,P4、赛隆系列处理器非常依赖于二级缓存,赛扬D 320(256KB二级缓存)和赛扬2.4GHz(128kb二级缓存)的性能差距是很好的证明;Cayon D和P4E处理器之间的性能差距也非常明显。最后,如果你是一个狂热的游戏爱好者或者一个专业的多媒体用户,一个带有1MB二级缓存的P4处理器和一个512Kb/1MB二级缓存的Athon 64处理器是你的理想选择。由于CPU的主存和二级缓存在重计算负载下几乎是“满”的,大的二级缓存可以为处理器提供大约5%到10%的性能改进,这对于要求苛刻的用户是绝对必要的。一级缓存是在CPU内的,用来存放内部指令,2级缓存和CPU封装在一起,也是用来存放指令数据的,三级和四级缓存只在高端的服务器CPU里有,作用差不多,速度更快,更稳定,更有效 并不是缓存越大越好,譬如AMD和INTER就有不同的理论,AMD认为一级缓存越大越好,所以一级比较大,而INTER认为过大会有更长的指令执行时间,所以一级很小,二级缓存那两个公司的理论又反过来了,AMD的小,INTER的大,一般主流的INTERCPU的2级缓存都在2M左右 我们通常用(L1,L2)来称呼缓存又叫高速缓冲存储器其作用在于缓解主存速度慢、跟不上CPU读写速度要求的矛盾。它的实现原理,是把CPU最近最可能用到的少量信息(数据或指令)从主存复制到CACHE中,当CPU下次再用这些信息时,它就不必访问慢速的主存,而直接从快速的CACHE中得到,从而提高了得到这些信息的速度,使CPU有更高的运行效率。

    缓存的大小

    一般说来,更大一点的cache容量,对提高命中率是有好处的,如图4.20所示,由于cache 是用价格很高的静态存储器SRAM器件实现的,而cache容量达到一定大小这后,再增加其容量,对命中率的提高并不明显,从合理的性能/价格比考虑,cache的容量设置应在一个合理的容量范围之内。缓存要分一级二级 三级,是为了建立一个层次存储结构,以达到最高性价比。而且多级组织还可以提高cache的命中率,提高执行效能。

    CPU缓存(Cache Memory)是位于CPU与内存之间的临时存储器,它的容量比内存小的多但是交换速度却比内存要快得多。缓存的出现主要是为了解决CPU运算速度与内存读写速度不匹配的矛盾,因为CPU运算速度要比内存读写速度快很多,这样会使CPU花费很长时间等待数据到来或把数据写入内存。在缓存中的数据是内存中的一小部分,但这一小部分是短时间内CPU即将访问的,当CPU调用大量数据时,就可避开内存直接从缓存中调用,从而加快读取速度。由此可见,在CPU中加入缓存是一种高效的解决方案,这样整个内存储器(缓存+内存)就变成了既有缓存的高速度,又有内存的大容量的存储系统了。缓存对CPU的性能影响很大,主要是因为CPU的数据交换顺序和CPU与缓存间的带宽引起的。 缓存的工作原理是当CPU要读取一个数据时,首先从缓存中查找,如果找到就立即读取并送给CPU处理;如果没有找到,就用相对慢的速度从内存中读取并送给CPU处理,同时把这个数据所在的数据块调入缓存中,可以使得以后对整块数据的读取都从缓存中进行,不必再调用内存。 正是这样的读取机制使CPU读取缓存的命中率非常高(大多数CPU可达90%左右),也就是说CPU下一次要读取的数据90%都在缓存中,只有大约10%需要从内存读取。

    这大大节省了CPU直接读取内存的时间,也使CPU读取数据时基本无需等待。总的来说,CPU读取数据的顺序是先缓存后内存。 目前缓存基本上都是采用SRAM存储器,SRAM是英文Static RAM的缩写,它是一种具有静志存取功能的存储器,不需要刷新电路即能保存它内部存储的数据。不像DRAM内存那样需要刷新电路,每隔一段时间,固定要对DRAM刷新充电一次,否则内部的数据即会消失,因此SRAM具有较高的性能,但是SRAM也有它的缺点,即它的集成度较低,相同容量的DRAM内存可以设计为较小的体积,但是SRAM却需要很大的体积,这也是目前不能将缓存容量做得太大的重要原因。

    它的特点归纳如下:优点是节能、速度快、不必配合内存刷新电路、可提高整体的工作效率,缺点是集成度低、相同的容量体积较大、而且价格较高,只能少量用于关键性系统以提高效率。 按照数据读取顺序和与CPU结合的紧密程度,CPU缓存可以分为一级缓存,二级缓存,部分高端CPU还具有三级缓存,每一级缓存中所储存的全部数据都是下一级缓存的一部分,这三种缓存的技术难度和制造成本是相对递减的,所以其容量也是相对递增的。当CPU要读取一个数据时,首先从一级缓存中查找,如果没有找到再从二级缓存中查找,如果还是没有就从三级缓存或内存中查找。一般来说,每级缓存的命中率大概都在80%左右,也就是说全部数据量的80%都可以在一级缓存中找到,只剩下20%的总数据量才需要从二级缓存、三级缓存或内存中读取,由此可见一级缓存是整个CPU缓存架构中最为重要的部分。

    一级缓存(Level 1 Cache)简称L1 Cache,位于CPU内核的旁边,是与CPU结合最为紧密的CPU缓存,也是历史上最早出现的CPU缓存。由于一级缓存的技术难度和制造成本最高,提高容量所带来的技术难度增加和成本增加非常大,所带来的性能提升却不明显,性价比很低,而且现有的一级缓存的命中率已经很高,所以一级缓存是所有缓存中容量最小的,比二级缓存要小得多。一般来说,一级缓存可以分为一级数据缓存(Data Cache,D-Cache)和一级指令缓存(InstrucTIon Cache,I-Cache)。二者分别用来存放数据以及对执行这些数据的指令进行即时解码,而且两者可以同时被CPU访问,减少了争用Cache所造成的冲突,提高了处理器效能。目前大多数CPU的一级数据缓存和一级指令缓存具有相同的容量,例如AMD的Athlon XP就具有64KB的一级数据缓存和64KB的一级指令缓存,其一级缓存就以64KB+64KB来表示,其余的CPU的一级缓存表示方法以此类推。

    Intel的采用NetBurst架构的CPU(最典型的就是PenTIum 4)的一级缓存有点特殊,使用了新增加的一种一级追踪缓存(ExecuTIon Trace Cache,T-Cache或ETC)来替代一级指令缓存,容量为12KμOps,表示能存储12K条即12000条解码后的微指令。一级追踪缓存与一级指令缓存的运行机制是不相同的,一级指令缓存只是对指令作即时的解码而并不会储存这些指令,而一级追踪缓存同样会将一些指令作解码,这些指令称为微指令(micro-ops),而这些微指令能储存在一级追踪缓存之内,无需每一次都作出解码的程序,因此一级追踪缓存能有效地增加在高工作频率下对指令的解码能力,而μOps就是micro-ops,也就是微型操作的意思。它以很高的速度将μops提供给处理器核心。Intel NetBurst微型架构使用执行跟踪缓存,将解码器从执行循环中分离出来。

    这个跟踪缓存以很高的带宽将uops提供给核心,从本质上适于充分利用软件中的指令级并行机制。Intel并没有公布一级追踪缓存的实际容量,只知道一级追踪缓存能储存12000条微指令(micro-ops)。所以,我们不能简单地用微指令的数目来比较指令缓存的大小。实际上,单核心的NetBurst架构CPU使用8Kμops的缓存已经基本上够用了,多出的4kμops可以大大提高缓存命中率。而如果要使用超线程技术的话,12KμOps就会有些不够用,这就是为什么有时候Intel处理器在使用超线程技术时会导致性能下降的重要原因。

    例如Northwood核心的一级缓存为8KB+12KμOps,就表示其一级数据缓存为8KB,一级追踪缓存为12KμOps;而Prescott核心的一级缓存为16KB+12KμOps,就表示其一级数据缓存为16KB,一级追踪缓存为12KμOps。在这里12KμOps绝对不等于12KB,单位都不同,一个是μOps,一个是Byte(字节),而且二者的运行机制完全不同。所以那些把Intel的CPU一级缓存简单相加,例如把Northwood核心说成是20KB一级缓存,把Prescott核心说成是28KB一级缓存,并且据此认为Intel处理器的一级缓存容量远远低于AMD处理器128KB的一级缓存容量的看法是完全错误的,二者不具有可比性。在架构有一定区别的CPU对比中,很多缓存已经难以找到对应的东西,即使类似名称的缓存在设计思路和功能定义上也有区别了,此时不能用简单的算术加法来进行对比;而在架构极为近似的CPU对比中,分别对比各种功能缓存大小才有一定的意义。

    转载自(http://www.elecfans.com/bandaoti/cunchu/20180814728879.html)

    展开全文
  • Mybatis使用的过程中,每次使用Mybatis开启次和数据库的会话操作的时候,Mybatis就会创建出个SqlSession对象来表示次数据库会话。 在对数据库的次会话中,我们有可能会反复地执行完全相同的查询语句,如果...

    Mybatis使用的过程中,每次使用Mybatis开启一次和数据库的会话操作的时候,Mybatis就会创建出一个SqlSession对象来表示一次数据库会话。

    在对数据库的一次会话中,我们有可能会反复地执行完全相同的查询语句,如果不采取一些措施的话,每一次 查询都会查询一次数据库,而我们在极短是时间内做了完全相同的查询,那么它们的结果极有可能完全相同,由于查询一次数据库的代价很大,这有可能造成很大的资源浪费。

    为了解决这一问题,减少资源的浪费,Mybatis会在表示会话的SqlSession对象中建立一个简单的缓存,将每次查询到的结果缓存起来,当下次查询的时候,如果判断先前有一个完全一样的查询,会直接从缓存汇总直接将结果取出来,返回给用户,不需要再次进行一次数据库的查询操作了。

    Mybatis会在一次会话的表示————>一个SqlSession对象中创建一个本地缓存(local cache),对于每一次查询,都会尝试根据查询的条件去本地缓存中查找是否在缓存中,如果在缓存中,就直接从缓存中取出,然后返回给用户;否则,从数据库读取数据,将查询结果存入缓存并且返回给用户。

       对于会话(Session)级别的数据缓存,我们称之为一级数据缓存,简称一级缓存。

    展开全文
  • Mybatis一级缓存与二级缓存

    千次阅读 2019-01-01 15:31:16
    mybatis的有两种缓存,一级缓存和二级缓存。两个缓存的不同点和相同点总结如下 不同点: 一级缓存存在于一个SqlSession之内,二级缓存存在于不同的SqlSession之间 一级缓存不需要手动开启,属于默认开启状态;二...

    mybatis的有两种缓存,一级缓存和二级缓存。两个缓存的不同点和相同点总结如下

    不同点:

    • 一级缓存存在于一个SqlSession之内,二级缓存存在于不同的SqlSession之间
    • 一级缓存不需要手动开启,属于默认开启状态;二级缓存需要手动开启

    相同点:

    • 在增删改SQL之后,缓存会自动清空
    • flushCache="true"的查询语句查询内容不存放进缓存

     

    一级缓存

    一级缓存是mybatis自带的缓存,mybatis每次在查询后,会将语句和参数相同的查询SQL的结果集存放进缓存,待下一次有相同的语句和参数时,mybatis直接将缓存内的结果集返回,而不再查询数据库。如果对于缓存的数据对应的表有增删改操作的话,缓存自动清空。

    通过实际的代码测试来看,在上一次一个简单的mybatis入门demo的基础上改造工程

    增加BaseMaperTest.java类,用以进行SqlSession的获取

    package cn.mybatis.xml;
    
    import java.io.IOException;
    import java.io.Reader;
    
    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 org.junit.BeforeClass;
    
    /**
     * 设置mapper测试父类
     * 用以进行数据库连接,获取SqlSession
     * @author PC
     */
    public class BaseMapperTest {
    
    	private static SqlSessionFactory sqlSessionFactory;
    	
    	/**
    	 * 进行数据库连接
    	 */
    	@BeforeClass
    	public static void init() {
    		try {
    			Reader reader = Resources.getResourceAsReader("mybatis-config.xml");
    			sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
    			reader.close();
    		} catch (IOException e) {
    			e.printStackTrace();
    		}
    	}
    	
    	/**
    	 * 获取SqlSession
    	 * @return
    	 */
    	public SqlSession getSqlSession() {
    		return sqlSessionFactory.openSession();
    	}
    }
    

    在CountryMapper.java中增加根据id查询方法,增加一个addCountry方法

    	/**
    	 * 查询国家/地区
    	 * @param id 查询id
    	 * @return 查询到的国家/地区
    	 */
    	Country selectCountryById(Long id);
    	
    	/**
    	 * 添加国家/地区
    	 * @param country
    	 * @return 影响的数据条数
    	 */
    	int addCountry(Country country);

    对应CountryMapper.xml配置文件

    	<select id="selectCountryById" resultType="cn.mybatis.xml.model.Country">
    		select id, countryname, countrycode 
    		from country
    		where id = #{id}
    	</select>
    	
    	<insert id="addCountry">
    		insert into country(id, countryname, countrycode)
    		values(#{id}, #{countryname}, #{countrycode})
    	</insert>

    通过Junit来测试,三种场景,分别来测试

    • 缓存后,直接查询
    	/**
    	 * 一级缓存测试
    	 * 测试缓存后,再查询
    	 */
    	@Test
    	public void testCache1() {
    		SqlSession sqlSession = getSqlSession();
    		try {
    			// 第一次查询
    			Country country = sqlSession.selectOne("selectCountryById", 2l);
    			System.out.println(country.getCountryname() + ":" + country.getCountrycode());
    			
    			// 通过日志可以发现,第二次查询并未到数据库查数据,说明第二次走的是缓存
    			Country country2 = sqlSession.selectOne("selectCountryById", 2l);
    			System.out.println(country2.getCountryname() + ":" + country2.getCountrycode());
    		} finally {
    			sqlSession.close();
    		}
    	}

    执行后,可以看到日志

    Opening JDBC Connection
    Created connection 1291113768.
    Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
    ==>  Preparing: select id, countryname, countrycode from country where id = ? 
    ==> Parameters: 2(Long)
    <==    Columns: id, countryname, countrycode
    <==        Row: 2, 美国, US
    <==      Total: 1
    美国:US
    美国:US
    Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
    Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
    Returned connection 1291113768 to pool.

    反馈了两次查询结果,但是只查询了一次数据库,说明第二次的查询是取得缓存结果

    • 缓存后,增删改,再查询
    	/**
    	 * 一级缓存测试
    	 * 测试缓存后,增删改查,再查询
    	 */
    	@Test
    	public void testCache2() {
    		SqlSession sqlSession = getSqlSession();
    		try {
    			// 第一次查询
    			Country country = sqlSession.selectOne("selectCountryById", 2l);
    			System.out.println(country.getCountryname() + ":" + country.getCountrycode());
    			
    			Country country2 = new Country();
    			country2.setId(7);
    			country2.setCountrycode("TW");
    			country2.setCountryname("中国台湾");
    			int result = sqlSession.insert("addCountry", country2);
    			if (result == 1) {
    				System.out.println("** insert success **");
    			}
    			
    			// 由于进行了insert操作,第二次查询没有走缓存,直接走的数据库查询
    			Country country3 = sqlSession.selectOne("selectCountryById", 2l);
    			System.out.println(country3.getCountryname() + ":" + country3.getCountrycode());
    		} finally {
    			sqlSession.commit();
    			sqlSession.close();
    		}
    	}

    执行结果

    Opening JDBC Connection
    Created connection 1291113768.
    Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
    ==>  Preparing: select id, countryname, countrycode from country where id = ? 
    ==> Parameters: 2(Long)
    <==    Columns: id, countryname, countrycode
    <==        Row: 2, 美国, US
    <==      Total: 1
    美国:US
    ==>  Preparing: insert into country(id, countryname, countrycode) values(?, ?, ?) 
    ==> Parameters: 7(Integer), 中国台湾(String), TW(String)
    <==    Updates: 1
    ** insert success **
    ==>  Preparing: select id, countryname, countrycode from country where id = ? 
    ==> Parameters: 2(Long)
    <==    Columns: id, countryname, countrycode
    <==        Row: 2, 美国, US
    <==      Total: 1
    美国:US
    Committing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
    Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
    Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
    Returned connection 1291113768 to pool.

    通过日志,可以印证,在缓存后,如果执行增删改操作,之前缓存的数据会自动清空。

    • 不缓存时的两连查

    此时,我们需要在查询语句的标签中增加 flushCache="true" ,意为查询结果不缓存。

    增加后的SQL标签为

    	<select id="selectCountryById" flushCache="true" resultType="cn.mybatis.xml.model.Country">
    		select id, countryname, countrycode 
    		from country
    		where id = #{id}
    	</select>

    测试代码

    	/**
    	 * 一级缓存测试
    	 * 测试select查询,不存入缓存,再查询
    	 */
    	@Test
    	public void testCache3() {
    		SqlSession sqlSession = getSqlSession();
    		try {
    			// 第一次查询,但是SQL设置了flushCache="true",即查询结果不会缓存
    			Country country = sqlSession.selectOne("selectCountryById", 2l);
    			System.out.println(country.getCountryname() + ":" + country.getCountrycode());
    			
    			// 通过日志可以发现,第二次查询依然查询了数据库,查询出来的结果依然不会缓存
    			Country country2 = sqlSession.selectOne("selectCountryById", 2l);
    			System.out.println(country2.getCountryname() + ":" + country2.getCountrycode());
    		} finally {
    			sqlSession.close();
    		}
    	}

    执行后的日志

    Opening JDBC Connection
    Created connection 1291113768.
    Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
    ==>  Preparing: select id, countryname, countrycode from country where id = ? 
    ==> Parameters: 2(Long)
    <==    Columns: id, countryname, countrycode
    <==        Row: 2, 美国, US
    <==      Total: 1
    美国:US
    ==>  Preparing: select id, countryname, countrycode from country where id = ? 
    ==> Parameters: 2(Long)
    <==    Columns: id, countryname, countrycode
    <==        Row: 2, 美国, US
    <==      Total: 1
    美国:US
    Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
    Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
    Returned connection 1291113768 to pool.
    

    可以看出,两次查询都走了数据库查询,原因就在于flushCache="true" 上面。flushCache标签,意为查询结果后是否缓存,默认为false,即默认存入缓存,方便后续查询。如果不想存缓存,则需要手动的设置为true,查询结果不会存缓存。一般不推荐这么做,这么做会增加数据库的负担,增加一些不必要的查询。

     

    通过上面的测试,一级缓存的场景可以总结如下:

    直接查,存缓存;增删改,清缓存;flushCache不缓存。

    这里的flushCache值得是手动设置flushCache为true的情形。

     

    二级缓存

    相较于一级缓存的自动默认开启,二级缓存需要手动开启。一级缓存在同一个SqlSession内,以SqlSession为缓存单位;二级缓存在不同的SqlSession间,以mapper为单位,即不同的SqlSession间可以共享相同的mapper下接口查询的数据。

    准备测试环境

    增加SysUser.java

    package cn.mybatis.xml.model;
    
    import java.io.Serializable;
    import java.util.Date;
    
    /**
     * 用户表
     * @author PC
     */
    public class SysUser implements Serializable{
    
    	/**
    	 * 
    	 */
    	private static final long serialVersionUID = 1L;
    
    	/**
    	 * 用户ID
    	 */
    	private Long id;
    	
    	/**
    	 * 用户名
    	 */
    	private String userName;
    	
    	/**
    	 * 密码
    	 */
    	private String userPassword;
    	
    	/**
    	 * 邮箱
    	 */
    	private String userEmail;
    	
    	/**
    	 * 简介
    	 */
    	private String userInfo;
    	
    	/**
    	 * 头像
    	 */
    	private byte[] headImg;
    	
    	/**
    	 * 创建时间
    	 */
    	private Date createTime;
    
    	getter and setter...
    }
    

    mybatis-config.xml中增加UserMapper.xml配置

    	<mappers>
    		<mapper resource="cn/mybatis/xml/mapper/CountryMapper.xml" />
    		<mapper resource="cn/mybatis/xml/mapper/UserMapper.xml" />
    	</mappers>

    UserMapper.xml内容

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <!-- 定义当前XML的命名空间 -->
    <mapper namespace="cn.mybatis.xml.mapper.UserMapper">
    
    	<!-- 启用mybatis二级缓存,不设置具体数值,均为默认项 -->
    	<cache/>
    	
    	<!-- resultMap 设置返回值的类型和映射关系 -->
    	<resultMap id="userMap" type="cn.mybatis.xml.model.SysUser">
    		<id property="id" column="id"/>
    		<result property="userName" column="user_name"/>
    		<result property="userPassword" column="user_password"/>
    		<result property="userEmail" column="user_email"/>
    		<result property="userInfo" column="user_info"/>
    		<result property="headImg" column="head_img" jdbcType="BLOB"/>
    		<result property="createTime" column="create_time" jdbcType="TIMESTAMP"/>
    	</resultMap>
    	
    	<!-- ID属性,定义当前select查询的唯一ID;resultType,定义当前查询的返回值类型,上面设置了resultMap的别名,这里引用id即可 -->
    	<select id="selectUserByUserId" resultMap="userMap">
    		select id,user_name,user_password,user_email,user_info,head_img,create_time from sys_user where id = #{id}
    	</select>
    	
    	<!-- 增加user -->
    	<insert id="addUser">
    		insert into sys_user(id, user_name,user_password,user_email,user_info) values(#{id}, #{userName}, #{userPassword}, #{userEmail}, #{userInfo});
    	</insert>
    	
    	<delete id="deleteUser">
    		delete from sys_user where id = #{id}
    	</delete>
    </mapper>

    上面,看到了<cache/>,这个标签标示启用二级缓存,二级缓存有一系列的参数策略,这里不配置任何内容,表示均使用默认值。

    通过Junit来测试,主要测试三种不同的场景

    • 不同SqlSession之间的查询
    	/**
    	 * 二级缓存测试
    	 * 不同SqlSession之间的查询
    	 */
    	@Test
    	public void testCache1() {
    		SqlSession sqlSession = getSqlSession();
    		try {
    			SysUser user = sqlSession.selectOne("selectUserByUserId", 1001l);
    			System.out.println(user.getUserName());
    		} finally {
    			sqlSession.close();
    		}
    		
    		// 二级缓存,在第一个session关闭时,数据存入二级缓存中
    		SqlSession sqlSession2 = getSqlSession();
    		try {
    			SysUser user = sqlSession2.selectOne("selectUserByUserId", 1001l);
    			System.out.println(user.getUserName());
    		} finally {
    			sqlSession.close();
    		}
    	}

    执行后,通过日志可看到

    Cache Hit Ratio [cn.mybatis.xml.mapper.UserMapper]: 0.0
    Opening JDBC Connection
    Created connection 1291113768.
    Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
    ==>  Preparing: select id,user_name,user_password,user_email,user_info,head_img,create_time from sys_user where id = ? 
    ==> Parameters: 1001(Long)
    <==    Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
    <==        Row: 1001, test, 123456, test@1.com, <<BLOB>>, <<BLOB>>, 2017-04-01 12:00:01.0
    <==      Total: 1
    test
    Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
    Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
    Returned connection 1291113768 to pool.
    Cache Hit Ratio [cn.mybatis.xml.mapper.UserMapper]: 0.5
    test
    

    执行了一次数据库查询,第二次查询时直接通过获取缓存的值返回,证明二级缓存生效。

    • 缓存后,执行增删改,再查询
    	/**
    	 * 二级缓存测试
    	 * 不同SqlSession之间查询,增删改,再查询
    	 */
    	@Test
    	public void testCache2() {
    		SqlSession sqlSession = getSqlSession();
    		try {
    			SysUser user = sqlSession.selectOne("selectUserByUserId", 1001l);
    			System.out.println(user.getUserName());
    		} finally {
    			sqlSession.close();
    		}
    		
    		SqlSession sqlSession2 = getSqlSession();
    		try {
    			int result = sqlSession2.delete("deleteUser", 2001l);
    			System.out.println(result);
    		} finally {
    			sqlSession2.commit();
    			sqlSession2.close();
    		}
    		
    		// 第二次查询
    		SqlSession sqlSession3 = getSqlSession();
    		try {
    			SysUser user = sqlSession3.selectOne("selectUserByUserId", 1001l);
    			System.out.println(user.getUserName());
    		} finally {
    			sqlSession.close();
    		}
    	}

    执行后,看日志

    Cache Hit Ratio [cn.mybatis.xml.mapper.UserMapper]: 0.0
    Opening JDBC Connection
    Created connection 1291113768.
    Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
    ==>  Preparing: select id,user_name,user_password,user_email,user_info,head_img,create_time from sys_user where id = ? 
    ==> Parameters: 1001(Long)
    <==    Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
    <==        Row: 1001, test, 123456, test@1.com, <<BLOB>>, <<BLOB>>, 2017-04-01 12:00:01.0
    <==      Total: 1
    test
    Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
    Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
    Returned connection 1291113768 to pool.
    Opening JDBC Connection
    Checked out connection 1291113768 from pool.
    Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
    ==>  Preparing: delete from sys_user where id = ? 
    ==> Parameters: 2001(Long)
    <==    Updates: 1
    1
    Committing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
    Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
    Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
    Returned connection 1291113768 to pool.
    Cache Hit Ratio [cn.mybatis.xml.mapper.UserMapper]: 0.0
    Opening JDBC Connection
    Checked out connection 1291113768 from pool.
    Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@4cf4d528]
    ==>  Preparing: select id,user_name,user_password,user_email,user_info,head_img,create_time from sys_user where id = ? 
    ==> Parameters: 1001(Long)
    <==    Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
    <==        Row: 1001, test, 123456, test@1.com, <<BLOB>>, <<BLOB>>, 2017-04-01 12:00:01.0
    <==      Total: 1
    test
    

    可以发现,在执行了delete语句后,缓存被清空了,待第二次查询时,又查了数据库。

    其实上面在说一级缓存时有说到,任何的增删改的语句,都会清空一级缓存,二级缓存自然会被清空了。

    • 关闭二级缓存总开关

    上面的两个场景测试,都是在mapper文件中设置使用二级缓存,二级缓存其实还有一个总开关,在mybatis-config.xml文件的setting配置中,为何之前并没有去配这个开关呢,这个开关默认打开的,只需要在mapper.xml文件中配置二级缓存开关即可。

    	<settings>
    		<!-- mybatis 二级缓存总开关,总开关默认为true -->
    		<!-- 总开关打开后,然后在每个mapper中设置自己的二级缓存开关;若总开关关闭,则后续mapper设置均无效 -->
    		<setting name="cacheEnabled" value="false"/>
    		<!-- 设置驼峰匹配 -->
    		<setting name="mapUnderscoreToCamelCase" value="true" />
    		<!-- 配置指定使用log4j输出日志 -->
    		<setting name="logImpl" value="STDOUT_LOGGING" />
    	</settings>

    该标签为cacheEnabled,默认值为true,所以,默认的二级缓存的总开关是打开的,不需要手动设置。

    我们现在将其设置为false,再次执行二级缓存场景一的测试语句

    可以看到日志

    Opening JDBC Connection
    Created connection 345281752.
    Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@149494d8]
    ==>  Preparing: select id,user_name,user_password,user_email,user_info,head_img,create_time from sys_user where id = ? 
    ==> Parameters: 1001(Long)
    <==    Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
    <==        Row: 1001, test, 123456, test@1.com, <<BLOB>>, <<BLOB>>, 2017-04-01 12:00:01.0
    <==      Total: 1
    test
    Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@149494d8]
    Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@149494d8]
    Returned connection 345281752 to pool.
    Opening JDBC Connection
    Checked out connection 345281752 from pool.
    Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@149494d8]
    ==>  Preparing: select id,user_name,user_password,user_email,user_info,head_img,create_time from sys_user where id = ? 
    ==> Parameters: 1001(Long)
    <==    Columns: id, user_name, user_password, user_email, user_info, head_img, create_time
    <==        Row: 1001, test, 123456, test@1.com, <<BLOB>>, <<BLOB>>, 2017-04-01 12:00:01.0
    <==      Total: 1
    test

    两次查询,均走了数据库查询。

     

    综上:二级缓存可以作为一级缓存的补充,一级缓存在同一个SqlSession之间,二级缓存将缓存扩大到不同的SqlSession之间。相同的点是,一旦有增删改的操作,缓存均会清空。

     

    以上是本人学习mybatis缓存的简单认知,记录下来,供初学的童鞋予以参考。如果内容有错误或疏漏部分,还望批评指正。谢谢。

     

    附 工程源代码路径:mybatis一级缓存和二级缓存简单示例

    展开全文
  • 堆数据如果存放在二级cache中,那么CPU读取堆数据的时候是不是就要先去一级缓存查看没有之后再去二级缓存中查找呢?会不会把二级缓存的内容加到一级缓存中呢?
  • hibernate二级缓存(一)一级缓存与二级缓存 1.hibernate一级缓存 hibernate的一级缓存是session级别的缓存,一级缓存hibernate默认启用且不能被卸载,一个事务内有效。 特点: 使用一级缓存的目的是为了...
  • Mybatis中有一级缓存和二级缓存,默认情况下一级缓存是开启的,而且是不能关闭的。一级缓存是指SqlSession级别的缓存,当在同一个SqlSession中进行相同的SQL语句查询时,第二次以后的查询不会从数据库查询,而是直接...
  • Hibernate一级缓存和二级缓存详解

    万次阅读 多人点赞 2014-10-14 09:02:38
    一、一级缓存二级缓存的概念解释 (1)一级缓存就是Session级别的缓存,一个Session做了一个查询操作,它会把这个操作的结果放在一级缓存中,如果短时间内这个 session(一定要同一个session)又做了同一个操作,...
  • Mybatis一级缓存和二级缓存

    千次阅读 2021-04-07 09:38:42
    一级缓存一级缓存是SqlSession范围的缓存,当调用SqlSession的增删改,commit(),close()等方法时,一级缓存就会清空 二级缓存:二级缓存是mapper映射级别的缓存,多个SqlSession去操作同一个Mapper映射的...
  • MyBatis【四】一级缓存和二级缓存

    千次阅读 多人点赞 2020-04-22 10:18:33
    一级缓存2. 二级缓存 MyBatis缓存 一级缓存默认是开启的 二级缓存在映射配置文件中开启 Mybatis中的一级缓存和二级缓存: 【一级缓存】 它指的是Mybatis中SqlSession对象的缓存 当我们执行查询之后,查询的...
  • 一级缓存存入数据的时候,放入一级缓存区和一级缓存快照区,当更新了一级缓存的数据的时候,事务一旦提交,比对一级缓存和快照区,如果数据一致,不更新,如果数据不一致,自动更新数据库. Hibernate管理一级缓存:1、clear...
  • Mybatis 一级缓存和二级缓存的使用

    万次阅读 2021-01-19 10:28:52
    目录Mybatis缓存一级缓存二级缓存缓存原理 Mybatis缓存 官方文档:https://mybatis.org/mybatis-3/zh/sqlmap-xml.html#cache MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制 Mybatis中...
  • CPU中的一级缓存,二级缓存,三级缓存

    千次阅读 多人点赞 2018-06-14 13:49:56
    CPU中的一级缓存,二级缓存,三级缓存 缓存又叫高速缓冲存储器,其作用在于缓解主存速度慢、跟不上CPU读写速度要求的矛盾。 缓存的实现原理,是把CPU最近最可能用到的少量信息(数据或指令)从主存复制到CACHE中,当...
  • MyBatis中的一级缓存和二级缓存介绍

    千次阅读 多人点赞 2017-06-13 20:07:22
    先说缓存,合理使用缓存是优化...一级缓存 一级缓存是SqlSession级别的缓存。在操作数据库时需要构造sqlSession对象,在对象中有一个数据结构用于存储缓存数据。不同的sqlSession之间的缓存数据区域是互相不影响的。也
  • MyBatis一级缓存

    千次阅读 2019-05-18 18:32:47
    MyBatis一级缓存
  • 刚学习了MyBatis,所以不太懂。请大神、好人指点,能详细点就更好,谢谢。
  • 1、先判断二级缓存是否开启,如果没开启,再判断一级缓存是否开启,如果没开启,直接查数据库 2、如果一级缓存关闭,即使二级缓存开启也没有数据,因为二级缓存的数据从一级缓存获取 3、一般不会关闭一级缓存 4、...
  • mybatis中一级缓存

    千次阅读 2018-10-19 00:36:11
    mybatis中提供有一级缓存 和 二级缓存,这里记录一下一级缓存 一级缓存(mybatis中默认开启) SqlSession级别的缓存,操作数据库时需要构造SQLSession对象, 在对象中有一个数据结构(HashMap)用于存储缓存数据,...
  • 2.什么是Mybatis一级缓存 3.一级缓存的生命周期 4.一级缓存的CacheKey生成策略 5.在日常开发时,怎么才能用到一级缓存(通过事务) 一.类关系图: 二.什么是会话(SqlSession) 在Mybatis中,SqlSession可以理解...
  • mybaits提供一级缓存,和二级缓存。 一级缓存是SqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,在对象中有一个(内存区域)数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据...
  • Mybatis的一级缓存和二级缓存详解

    千次阅读 多人点赞 2018-11-19 19:11:59
    注:本笔记是根据尚硅谷的MyBatis视频记录的 对于任何一个持久层框架,都有缓存机制;... 两个关于mybatis缓存额外的链接: ...关于Mybatis的一级缓存和二级缓存执行顺序具体可参考:Mybatis的一级缓存和二级缓存执行...
  • 缓存相关的设置 1. <setting name=... 关闭二级缓存,一级缓存可正常使用 2. <select useCache="false"> 关闭二级缓存,一级缓存可正常使用 3. <select flushCache="true"> :一二级均被清空
  • 【MyBatis】MyBatis一级缓存和二级缓存

    千次阅读 2018-01-27 19:35:53
    MyBatis自带的缓存有一级缓存和二级缓存 一级缓存Mybatis的一级缓存是指Session缓存。一级缓存的作用域默认是一个SqlSession。Mybatis默认开启一级缓存。 也就是在同一个SqlSession中,执行相同的查询SQL,第一次会...
  • 20-Mybatis一级缓存

    万次阅读 2020-11-13 23:34:16
    mybatis的一级缓存就是SqlSession级别的缓存 同一个SqlSession连续查询两次仅发出了一个sql,使用了一级缓存 mybaits缓存失效的四种情况 不同的SqlSession 相同的SqlSession,查询条件不同 相同的SqlSession,查询...
  • Mybatis一级缓存和二级缓存的区别

    万次阅读 多人点赞 2020-03-11 21:35:06
    1)一级缓存 Mybatis的一级缓存是指SQLSession,一级缓存的作用域是SQlSession, Mabits默认开启一级缓存。 在同一个SqlSession中,执行相同的SQL查询时;第一次会去查询数据库,并写在缓存中,第二次会直接从缓存中...
  • mybatis一级缓存(session cache)引发的问题
  • 浅谈Hibernate缓存机制:一级缓存、二级缓存
  • mybaits提供一级缓存,和二级缓存。 一级缓存是SqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,在对象中有一个(内存区域)数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据...
  • mybatis中一级缓存和二级缓存

    万次阅读 2019-05-05 16:28:46
    1.一级缓存 一级缓存是SqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,在对象中有一个(内存区域)数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 613,639
精华内容 245,455
关键字:

一级缓存是什么