Result Cache
result cache是用来存储查询sql得到的结果,给之后的重复查询来使用。通过缓存这些结果,oracle能避免那些重复的实际消耗,并且节省了大量的数据库操作,比如排序、合并、物理io和表关联等。result cache是内存里的一块单独区域,要么是SGA里或者客户端应用程序内存里。存放在里面的缓存结果在不同的sql语句或者不同的会话之间是可以共享的,除非缓存结果本身失效了。结果缓存对于应用程序来说是完全透明的,它不需要人为介入,而是直接由oracle内部来进行自动管理和维护。
对于不同的应用系统来说,result cache所带来的收益是不一样的,对于OLAP这种大数据量的分析统计系统来说效果最明显。缓存的最佳选择是查询了大量的数据,但最后却只返回少量的数据,例如数据仓库。除此之外,对于只读或者数据较少变化的SQL来说效果也会更好,因为当数据进行了变化后,相对于的缓存也会失效,而需要重新维护生成新的。
数据库相关配置
oracle与result cache相关的一些初始化参数如下
1 | select name, value, isdefault |
result_cache_mode
决定了哪些查询是可以存储在result cache里的,如果查询有资格放到缓存,则应用会去检查result cache里是否已经存在,如果存在则直接从里面获取数据,如果不存在数据库则会执行查询语句,并返回结果同时将结果放到result cache当中。
Manual
只有带查询hint和表标注的查询结果才会进行缓存,这是默认值
Force
所有的查询都会强制缓存到result cache,但是可以在sql中指定/*+ NO_RESULT_CACHE */来排除当前sql
result_cache_max_size
设定result cache的大小,这部分内存是直接从shared pool中分配但是是单独维护,刷新shared pool并不会刷新result cache
result_cache_max_result
指定对于单个结果来说所占用的内存大小不能超过总result cache大小的百分比,默认值是5,这个参数可以在system或者session级别进行修改
result_cache_remote_expiration
指定那些依赖远端数据库对象的结果集超期时间(分钟),默认是0表示使用远端数据库对象的结果不会被缓存。如果指定的非0值,则对远端数据库对象进行的DML操作并不会使缓存结果失效。
清空结果缓存
1 | exec DBMS_RESULT_CACHE.FLUSH; |
手动result cache
默认缓存模式为手动表示数据库并不会自动缓存结果,除非使用RESULT_CACHE hint。
1 | create table t |
通过上面的这个结果得到了如下信息:
- 首先在执行计划中看到了一个新的operation,ID=1的“RESULT CACHE”。这是执行计划在执行过程中的最后一步,它告诉我们oracle会缓存之前步骤执行后得到的结果。
- 在Name那一列,operation为“RESULT CACHE”的那一行多了一个系统生成的标识值,这是一个内部key供系统来在result cache中查询和匹配sql语句,看起来是通过sql文本hash值得到的,因为刷新了cache以后这个key不会变。
- 执行计划报告中多了一个新的“Result Cache Information”,包含了这个sql所依赖的对象,和生成这个结果的sql文本开头的一部分。
这里总共扫描了5W行数据,92个一致性读,84个物理读,最终获取了10行数据。
对这个已经缓存到内存的sql重新执行 ,比较下两者之间的结果有何不同。
1 | -------------------------------------------------------------------------------------------------- |
执行计划并没有什么区别,重点看最后的Statistics部分。可以看到在这些执行中oracle本身只做了非常少的工作,逻辑读和物理读都是0。oracle已经意识到这个查询是可以直接从result cache当中获取结果的,所以就直接很容易的返回了预先计算好的答案。
1 | col name for a40 |
通过v$result_cache_objects
视图可以看到有两个不同的类型,dependency
和result
,result类型清晰的表示了之前执行的sql。
同样也可以通过v$result_cache_statistics
视图来查到与result cache相关的更加详细的统计信息
1 | SQL> select * from v$result_cache_statistics; |
这个视图可以看到一些基本信息,缓存的条目和命中。目前block大小为1k,最多有41952个块也就是41952k,与show parameter看到的结果一致。当前使用了256k,单个结果最多能使用2097k,成功创建一个结果(Create Count Success),并且命中过一次缓存(Find Count)。
result cache依赖性
每个查询的结果都依赖一个或多个表,通过V$RESULT_CACHE_DEPENDENCY
视图可以查到result cache当中每一个条目所依赖的对象。
1 | SELECT a.id, a.name, listagg(c.object_name) within group (order by 1) AS object_names |
依赖性主要是为了保持缓存中数据的完整性,如果所依赖的表被修改了,oracle会修改缓存的条目为失效状态,在原sql重新执行以后则会生成一个新的条目出来。这个特性无法关闭,即使你能容忍数据的不一致。
所以想要使result cache里的条目失效非常简单,只要简单做个更新然后commit即可。
1 | update t |
这时重新执行之前的sql
1 | set autotrace traceonly |
oracle并不会判断你的语句是否造成了数据的变更,可以通过上面的结果看到又重新产生了一致性读,表示这次执行oracle并没有直接采用result cache里存放的结果,而是重新执行了一遍语句,通过v$result_cache_objects
查看更详细的信息。
1 | col name for a40 |
这里STATUS
列很清楚的表示了之前的那个条目变成了失效状态,而之后生成了一个cache_id一样的新条目,状态是published状态,也就是当前可用的状态。
缓存使用计数
缓存了结果以后,可以通过V$RESULT_CACHE_STATISTICS
视图来查看缓存具体被使用的次数,但是这里显示的数据都代表的result cache的整体统计信息,无法获取单个条目的情况。
这个视图的结果可以给我们用来验证一些实验,通过PL/SQL循环调用sql,查看缓存使用了多少次
1 | select value from v$result_cache_statistics |
重新检查find count的计数
1 | select value from v$result_cache_statistics |
计数增加了99,这是因为第一次是创建新的缓存条目,而剩下的99次都是直接使用这个缓存条目的结果。
1 | col name for a40 |
性能比较
在前面提到oracle建议是那些读取大量数据但是返回少量数据的情况最适合result cache,接下来会通过几个例子来量化性能的提升到底有多少,对于不同的场景来说提升的幅度有多大。
简单数据求和
1 | SQL> exec DBMS_RESULT_CACHE.FLUSH; |
都是重复了100次,第一次没有使用result cache,而第二次进行了缓存,从总的时间上来看第二次快了7.8%,消耗了更少的cpu。在statistics报告里面,一些一致性读的部分大量减少,RUN1出现了很多CBC latch争用,总latch数量RUN2会少了62.4%,总的来说对于这种简单语句,效果并没有预想的那么明显。
复杂语句
创建两张测试表,每张表大概50W数据
1 | create table t1 as select * from dba_objects; |
循环执行sql
1 | SQL> set serveroutput on |
可以看到对于这种访问大数据最后返回数据量又小的情况,性能提升的效果就非常明显。未使用result cache的情况花费了120s,而使用result cache的情况则使用了1s多,CPU的消耗也减少了99%,而在一些具体的统计指标比如latch事件的等待、一致性读的构造和内存分配等情况下耗费的资源都少的多。
总的来说最大的性能提升方面在于避免了大量重复的数据库工作,直接从结果中获取数据,然而对于源表经常要进行修改的情况可能也会导致更负面的效果。