Skip to Main Content
  • Questions
  • FORCE result cache for queries that are not cacheable?

Breadcrumb

Question and Answer

Connor McDonald

Thanks for the question, Jim.

Asked: December 13, 2016 - 10:26 pm UTC

Last updated: December 16, 2016 - 2:10 am UTC

Version: 12.1

Viewed 10K+ times! This question is

You Asked

Is there a way to force the result cache for ANY query and let the application handle invalidation?

In an extreme (not very wise) case, do this:

select /*+ force_result_cache */ * from dba_tables where table = 'XXX';

Basically, we want to cache complex queries (involving multi-level views, sysdate, 'connect by level <= x' row generator, etc) and let the app invalidate when necessary. The result cache has the basic functionality we desire: ability of the DB to intercept queries, compare binds, return a pre-queried result. But it's too limiting with what can be cached and doesn't allow any tolerance for stale data even by an explicit override. Granted, I understand the reasons and they make total sense, I'm just trying to extend its use to my application.

Currently we have our own cache using something similar to in-place MV's. That works but forces us to 1) do complete refreshes which are time consuming and 2) do so in a specific order because of view-over-view dependencies.

Any suggestions or alternatives would be appreciated. Thank you for your time.

and Chris said...

I don't understand what you mean by "let the application handle invalidation". The result cache is fully managed by Oracle Database.

The first time you execute a query Oracle stores the result in the cache. If the data in the table(s) change, it invalidates the cache immediately.

For example. I've created a function that sleeps for 1s to make the gains clear and a table with 10 rows:

create or replace function f (p int)
  return int as 
begin
  dbms_lock.sleep(1);
  return p;
end;
/

create table t (
  x int
);

insert into t 
  select rownum from dual connect by level <= 10;

commit;


The first execution counting 5 rows takes 5s. The second nearly 0s:

SQL> set timing on
SQL>
SQL> select /*+ result_cache */count(f(x)) from t
  2  where  x <= 5;

COUNT(F(X))
-----------
          5

Elapsed: 00:00:05.52
SQL> select /*+ result_cache */count(f(x)) from t
  2  where  x <= 5;

COUNT(F(X))
-----------
          5

Elapsed: 00:00:00.47


But as soon as you add another row to t, Oracle invalidates the cache. And the query takes 5s again:

SQL> insert into t values (11);

1 row created.

Elapsed: 00:00:00.31
SQL> commit;

Commit complete.

Elapsed: 00:00:00.31
SQL>
SQL> select /*+ result_cache */count(f(x)) from t
  2  where  x <= 5;

COUNT(F(X))
-----------
          5

Elapsed: 00:00:05.46


So you can't "let the app manage it". And this makes it unsuitable for queries against tables with lots of DML.

If you're looking for a way to have a cache of stale data then MVs are the way to go. Create your MV over your complex query and set it up to refresh on a schedule. Then you could point your app at the MV.

Or you could change the query_rewrite_integrity to allow stale data with:

alter session set query_rewrite_integrity = stale_tolerated;


Then Oracle can use query rewrite to and return data from the stale MV automagically:

https://www.youtube.com/watch?v=2iRadmXtMGk
https://www.youtube.com/watch?v=aSj9eDDUE-Y

FWIW you can force result caching for all statements in a session with:

alter session set result_cache_mode = force;


Then disable it for specific queries with the no_result_cache hint:

SQL> alter session set result_cache_mode = force;

Session altered.

Elapsed: 00:00:00.40
SQL>
SQL> select count(f(x)) from t
  2  where  x <= 5;

COUNT(F(X))
-----------
          5

Elapsed: 00:00:00.47
SQL> select /*+ no_result_cache */count(f(x)) from t
  2  where  x <= 5;

COUNT(F(X))
-----------
          5

Elapsed: 00:00:05.94

Rating

  (1 rating)

Is this answer out of date? If it is, please let us know via a Comment

Comments

Not quite what we're looking for

Jim R., December 15, 2016 - 9:09 pm UTC

I guess my question was more: is there an option to have the result cache LESS MANAGED by Oracle? Matching the SQL, matching the binds, and caching the result is desired. Forcing certain statements into the cache and selective invalidation of cache entries is what I'm looking for more control over.

I don't think query rewrite would be applicable here due to complexity of the views (sysdate, sys_context, etc).

Full refresh MV's are basically what we're using now. Disadvantage is we have to refresh the entire view whenever one row needs to be updated. They are not fast refreshable due to view complexity and there's no way to refresh an arbitrary subset of rows.
Connor McDonald
December 16, 2016 - 2:10 am UTC

I think the only way you could probably get *some* way down that path would be to break up things into smaller 'chunks', and result cache each of the individual chunks, and have views as "building blocks" of those chunks.

eg if a view was originally a 10 table join, then maybe you have a a few views which are 3 table joins, and a view to bring them all together. Result cache each of the views, with the aim of only needing refresh a subset of them when data changes.

I know I'm being pretty simplistic here - this would not be a trivial undertaking (or a guarantee of success).

Ultimately, if memory based operations are critical to performance, then the in-memory option is by far the best solution (but yes I know...separately licensed)

More to Explore

PL/SQL demos

Check out more PL/SQL tutorials on our LiveSQL tool.

PL/SQL docs

PL/SQL reference manual from the Oracle documentation library