PL/SQL Function Result Cache

When a PL/SQL function has the RESULT_CACHE option, its results are cached so sessions can reuse these results when available.

Oracle Database automatically detects all data sources (tables and views) that are queried while a result-cached function is running. If changes to any of these data sources are committed, the cached result becomes invalid across all instances. The best candidates for result-caching are functions that are invoked frequently but depend on information that changes infrequently or never.

A result object is the result of a query or result cached function execution. A temp object is the result of a query or result-cached function execution that exceeds the limit set by the multiplication of the RESULT_CACHE_MAX_SIZE by RESULT_CACHE_MAX_RESULT parameters. Temp objects are temporary segments stored in the temporary tablespace defined for the SYS user.

You can view the result and temp objects together by joining the V$RESULT_CACHE_OBJECTS using the type Temp for temp object and type Result for result objects.

SELECT rc1.NAME, rc2.STATUS, rc3.STATUS, rc2.BLOCK_COUNT
FROM V$RESULT_CACHE_OBJECTS rc1, V$RESULT_CACHE_OBJECTS rc2
WHERE rc1.TYPE = 'Result'
AND rc2.TYPE = 'Temp'
AND rc1.CACHE_KEY = rc2.CACHE_KEY;

The RESULT_CACHE_MAX_TEMP_SIZE parameter sets the maximum amount of temporary tablespace that the result cache can consume in a PDB.

The result cache usage is optimized for best performance based on changes in the application workload.

Before fetching a cached result from a remote instance, the database uses heuristics to determine if it is more cost efficient to recompute the result on the local instance.

Oracle Database tracks recently used result-cached functions. Using this history, the database only caches a result-cached function and arguments pair if it has seen it x times in recent history, where x is set by the initialization parameter RESULT_CACHE_EXECUTION_THRESHOLD. Assuming the default value of 2, the result is cached on the second execution and reused on the third execution.

You can assess the health of your result cache by running the following query. It shows the distribution of the reuse rate of cached functions. If you notice a majority of these results have a scan count of 0, consider increasing the value of the RESULT_CACHE_EXECUTION_THRESHOLD by 1 or 2.

SELECT SCAN_COUNT, COUNT(CACHE_KEY)
FROM V$RESULT_CACHE_OBJECTS
WHERE NAMESPACE = 'PLSQL'
GROUP BY SCAN_COUNT;

Topics

Enabling Result-Caching for a Function

To make a function result-cached, include the RESULT_CACHE clause in the function declaration and definition. For syntax details, see "Function Declaration and Definition".

Note:

For more information about configuring and managing the database server result cache, see Oracle Database Reference and Oracle Database Performance Tuning Guide.

In Example 9-39, the package department_pkg declares and then defines a result-cached function, get_dept_info, which returns a record of information about a given department. The function depends on the database tables DEPARTMENTS and EMPLOYEES.

You invoke the function get_dept_info as you invoke any function. For example, this invocation returns a record of information about department number 10:

department_pkg.get_dept_info(10);

This invocation returns only the name of department number 10:

department_pkg.get_dept_info(10).dept_name;

If the result for get_dept_info(10) is in the result cache, the result is returned from the cache; otherwise, the result is computed and added to the cache. Because get_dept_info depends on the DEPARTMENTS and EMPLOYEES tables, any committed change to DEPARTMENTS or EMPLOYEES invalidates all cached results for get_dept_info, relieving you of programming cache invalidation logic everywhere that DEPARTMENTS or EMPLOYEES might change.

Example 9-39 Declaring and Defining Result-Cached Function

CREATE OR REPLACE PACKAGE department_pkg AUTHID DEFINER IS
 
  TYPE dept_info_record IS RECORD (
    dept_name  departments.department_name%TYPE,
    mgr_name   employees.last_name%TYPE,
    dept_size  PLS_INTEGER
  );
 
  -- Function declaration
 
  FUNCTION get_dept_info (dept_id NUMBER)
    RETURN dept_info_record
    RESULT_CACHE;
 
END department_pkg;
/
CREATE OR REPLACE PACKAGE BODY department_pkg IS
  -- Function definition
  FUNCTION get_dept_info (dept_id NUMBER)
    RETURN dept_info_record
    RESULT_CACHE
  IS
    rec  dept_info_record;
  BEGIN
    SELECT department_name INTO rec.dept_name
    FROM departments
    WHERE department_id = dept_id;
 
    SELECT e.last_name INTO rec.mgr_name
    FROM departments d, employees e
    WHERE d.department_id = dept_id
    AND d.manager_id = e.employee_id;
 
    SELECT COUNT(*) INTO rec.dept_size
    FROM EMPLOYEES
    WHERE department_id = dept_id;
 
    RETURN rec;
  END get_dept_info;
END department_pkg;
/

Developing Applications with Result-Cached Functions

When developing an application that uses a result-cached function, make no assumptions about the number of times the body of the function will run for a given set of parameter values.

Some situations in which the body of a result-cached function runs are:

  • The first time a session on this database instance invokes the function with these parameter values is run

    Note:

    RESULT_CACHE_EXECUTION_THRESHOLD specifies the number of times a function and a particular set of arguments must be seen until it is cached. The default value for that parameter is 2 and can be configured at the system level.
  • When the cached result for these parameter values is invalid

    When a change to any data source on which the function depends is committed, the cached result becomes invalid

  • When the cached results for these parameter values have aged out

    If the system needs memory, it might discard the oldest or rarely used cached values based on PL/SQL function history tracking

  • When the DBMS_RESULT_CACHE block list procedure is invoked to explicitly block some result caching related objects from being cached on a local instance or globally
  • After the DBMS_RESULT_CACHE.FLUSH has run and flushed all the cached results for SQL queries and all the cached results for PL/SQL functions
  • When the function bypasses the cache (see "Result Cache Bypass")

Requirements for Result-Cached Functions

A result-cached PL/SQL function is safe if it always produces the same output for any input that it would produce were it not marked with RESULT_CACHE. This safety is only guaranteed if these conditions are met:

  • When the function is run, it has no side effects.

    For information about side effects, see "Subprogram Side Effects".

  • All tables that the function accesses are ordinary, non-SYS-owned permanent tables in the same database as the function.

  • The function’s result must be determined only by the vector of input actuals together with the committed content, at the current SCN, of the tables that it references.

It is recommended that a result-cached function also meet these criteria:

For more information, see Oracle Database Performance Tuning Guide.

Examples of Result-Cached Functions

The best candidates for result-caching are functions that are invoked frequently but depend on information that changes infrequently (as might be the case in the first example). Result-caching avoids redundant computations in recursive functions.

Examples:

Result-Cached Application Configuration Parameters

Consider an application that has configuration parameters that can be set at either the global level, the application level, or the role level. The application stores the configuration information in these tables:

-- Global Configuration Settings
DROP TABLE global_config_params;
CREATE TABLE global_config_params
  (name  VARCHAR2(20), -- parameter NAME
   val   VARCHAR2(20), -- parameter VALUE
   PRIMARY KEY (name)
  );

-- Application-Level Configuration Settings
CREATE TABLE app_level_config_params
  (app_id  VARCHAR2(20), -- application ID
   name    VARCHAR2(20), -- parameter NAME
   val     VARCHAR2(20), -- parameter VALUE
   PRIMARY KEY (app_id, name)
  );

-- Role-Level Configuration Settings
CREATE TABLE role_level_config_params
  (role_id  VARCHAR2(20), -- application (role) ID
   name     VARCHAR2(20),  -- parameter NAME
   val      VARCHAR2(20),  -- parameter VALUE
   PRIMARY KEY (role_id, name)
  );

For each configuration parameter, the role-level setting overrides the application-level setting, which overrides the global setting. To determine which setting applies to a parameter, the application defines the PL/SQL function get_value. Given a parameter name, application ID, and role ID, get_value returns the setting that applies to the parameter.

The function get_value is a good candidate for result-caching if it is invoked frequently and if the configuration information changes infrequently.

Example 9-40 shows a possible definition for get_value. Suppose that for one set of parameter values, the global setting determines the result of get_value. While get_value is running, the database detects that three tables are queried—role_level_config_params, app_level_config_params, and global_config_params. If a change to any of these three tables is committed, the cached result for this set of parameter values is invalidated and must be recomputed.

Now suppose that, for a second set of parameter values, the role-level setting determines the result of get_value. While get_value is running, the database detects that only the role_level_config_params table is queried. If a change to role_level_config_params is committed, the cached result for the second set of parameter values is invalidated; however, committed changes to app_level_config_params or global_config_params do not affect the cached result.

Example 9-40 Result-Cached Function Returns Configuration Parameter Setting

CREATE OR REPLACE FUNCTION get_value
  (p_param VARCHAR2,
   p_app_id  NUMBER,
   p_role_id NUMBER
  )
  RETURN VARCHAR2
  RESULT_CACHE
  AUTHID DEFINER
IS
  answer VARCHAR2(20);
BEGIN
  -- Is parameter set at role level?
  BEGIN
    SELECT val INTO answer
      FROM role_level_config_params
        WHERE role_id = p_role_id
          AND name = p_param;
    RETURN answer;  -- Found
    EXCEPTION
      WHEN no_data_found THEN
        NULL;  -- Fall through to following code
  END;
  -- Is parameter set at application level?
  BEGIN
    SELECT val INTO answer
      FROM app_level_config_params
        WHERE app_id = p_app_id
          AND name = p_param;
    RETURN answer;  -- Found
    EXCEPTION
      WHEN no_data_found THEN
        NULL;  -- Fall through to following code
  END;
  -- Is parameter set at global level?
    SELECT val INTO answer
     FROM global_config_params
      WHERE name = p_param;
    RETURN answer;
END;
/

Result-Cached Recursive Function

A recursive function for finding the nth term of a Fibonacci series that mirrors the mathematical definition of the series might do many redundant computations. For example, to evaluate fibonacci(7), the function must compute fibonacci(6) and fibonacci(5). To compute fibonacci(6), the function must compute fibonacci(5) and fibonacci(4). Therefore, fibonacci(5) and several other terms are computed redundantly. Result-caching avoids these redundant computations.

Note:

The maximum number of recursive invocations cached is 128.

CREATE OR REPLACE FUNCTION fibonacci (n NUMBER)
  RETURN NUMBER
  RESULT_CACHE
  AUTHID DEFINER
IS
BEGIN
  IF (n =0) OR (n =1) THEN
    RETURN 1;
  ELSE
    RETURN fibonacci(n - 1) + fibonacci(n - 2);
  END IF;
END;
/

Advanced Result-Cached Function Topics

Topics

Rules for a Cache Hit

Each time a result-cached function is invoked with different parameter values, those parameters and their result are stored in the cache. Subsequently, when the same function is invoked with the same parameter values (that is, when there is a cache hit), the result is retrieved from the cache, instead of being recomputed.

The rules for parameter comparison for a cache hit differ from the rules for the PL/SQL "equal to" (=) operator, as follows:

Category Cache Hit Rules "Equal To" Operator Rules

NULL comparison

NULL equals NULL

NULL = NULL evaluates to NULL.

Non-null scalar comparison

Non-null scalars are the same if and only if their values are identical; that is, if and only if their values have identical bit patterns on the given platform. For example, CHAR values 'AA' and 'AA ' are different. (This rule is stricter than the rule for the "equal to" operator.)

Non-null scalars can be equal even if their values do not have identical bit patterns on the given platform; for example, CHAR values 'AA' and 'AA ' are equal.

Result Cache Bypass

In some situations, the cache is bypassed. When the cache is bypassed:

  • The function computes the result instead of retrieving it from the cache.

  • The result that the function computes is not added to the cache.

Some examples of situations in which the cache is bypassed are:

  • The cache is unavailable to all sessions.

    For example, the database administrator has disabled the use of the result cache during application patching (as in "Hot-Patching PL/SQL Units on Which Result-Cached Functions Depend").

  • A session is performing a DML statement on a table or view on which a result-cached function depends.

    The session bypasses the result cache for that function until the DML statement is completed—either committed or rolled back. If the statement is rolled back, the session resumes using the cache for that function.

    Cache bypass ensures that:

    • The user of each session sees their own uncommitted changes.

    • The PL/SQL function result cache has only committed changes that are visible to all sessions, so that uncommitted changes in one session are not visible to other sessions.

Making Result-Cached Functions Handle Session-Specific Settings

If a function depends on settings that might vary from session to session (such as NLS_DATE_FORMAT and TIME ZONE), make the function result-cached only if you can modify it to handle the various settings.

The function, get_hire_date, in Example 8–39 uses the TO_CHAR function to convert a DATE item to a VARCHAR item. The function get_hire_date does not specify a format mask, so the format mask defaults to the one that NLS_DATE_FORMAT specifies. If sessions that invoke get_hire_date have different NLS_DATE_FORMAT settings, cached results can have different formats. If a cached result computed by one session ages out, and another session recomputes it, the format might vary even for the same parameter value. If a session gets a cached result whose format differs from its own format, that result is probably incorrect.

Some possible solutions to this problem are:

  • Change the return type of get_hire_date to DATE and have each session invoke the TO_CHAR function.

  • If a common format is acceptable to all sessions, specify a format mask, removing the dependency on NLS_DATE_FORMAT. For example:

    TO_CHAR(date_hired, 'mm/dd/yy');
    
  • Add a format mask parameter to get_hire_date. For example:

    CREATE OR REPLACE FUNCTION get_hire_date (emp_id NUMBER, fmt VARCHAR)
      RETURN VARCHAR
      RESULT_CACHE
      AUTHID DEFINER
    IS
      date_hired DATE;
    BEGIN
      SELECT hire_date INTO date_hired
        FROM HR.EMPLOYEES
          WHERE EMPLOYEE_ID = emp_id;
      RETURN TO_CHAR(date_hired, fmt);
    END;
    /

Example 9-41 Result-Cached Function Handles Session-Specific Settings

CREATE OR REPLACE FUNCTION get_hire_date (emp_id NUMBER)
  RETURN VARCHAR
  RESULT_CACHE
  AUTHID DEFINER
IS
  date_hired DATE;
BEGIN
  SELECT hire_date INTO date_hired
    FROM HR.EMPLOYEES
      WHERE EMPLOYEE_ID = emp_id;
  RETURN TO_CHAR(date_hired);
END;
/

Making Result-Cached Functions Handle Session-Specific Application Contexts

An application context, which can be either global or session-specific, is a set of attributes and their values. A PL/SQL function depends on session-specific application contexts if it does one or more of the following:

  • Directly invokes the SQL function SYS_CONTEXT, which returns the value of a specified attribute in a specified context

  • Indirectly invokes SYS_CONTEXT by using Virtual Private Database (VPD) mechanisms for fine-grained security

    (For information about VPD, see Oracle Database Security Guide.)

The PL/SQL function result-caching feature does not automatically handle dependence on session-specific application contexts. If you must cache the results of a function that depends on session-specific application contexts, you must pass the application context to the function as a parameter. You can give the parameter a default value, so that not every user must specify it.

In Example 9-42, assume that a table, config_tab, has a VPD policy that translates this query:

SELECT value FROM config_tab WHERE name = param_name;

To this query:

SELECT value FROM config_tab
WHERE name = param_name
AND app_id = SYS_CONTEXT('Config', 'App_ID');

Example 9-42 Result-Cached Function Handles Session-Specific Application Context

CREATE OR REPLACE FUNCTION get_param_value (
  param_name VARCHAR,
  appctx     VARCHAR  DEFAULT SYS_CONTEXT('Config', 'App_ID')
) RETURN VARCHAR
  RESULT_CACHE
  AUTHID DEFINER
IS
  rec VARCHAR(2000);
BEGIN
  SELECT val INTO rec
  FROM config_tab
  WHERE name = param_name;
 
  RETURN rec;
END;
/

Choosing Result-Caching Granularity

PL/SQL provides the function result cache, but you choose the caching granularity. To understand the concept of granularity, consider the Product_Descriptions table in the Order Entry (OE) sample schema:

NAME                     NULL?      TYPE
----------------------   --------   ---------------
PRODUCT_ID               NOT NULL   NUMBER(6)
LANGUAGE_ID              NOT NULL   VARCHAR2(3)
TRANSLATED_NAME          NOT NULL   NVARCHAR2(50)
TRANSLATED_DESCRIPTION   NOT NULL   NVARCHAR2(2000)

The table has the name and description of each product in several languages. The unique key for each row is PRODUCT_ID,LANGUAGE_ID.

Suppose that you must define a function that takes a PRODUCT_ID and a LANGUAGE_ID and returns the associated TRANSLATED_NAME. You also want to cache the translated names. Some of the granularity choices for caching the names are:

  • One name at a time (finer granularity)

  • One language at a time (coarser granularity)

Table 9-4 Finer and Coarser Caching Granularity

Granularity Benefits

Finer

Each function result corresponds to one logical result.

Stores only data that is needed at least once.

Each data item ages out individually.

Does not allow bulk loading optimizations.

Coarser

Each function result contains many logical subresults.

Might store data that is never used.

One aged-out data item ages out the whole set.

Allows bulk loading optimizations.

In Example 9-43 and Example 9-44, the function productName takes a PRODUCT_ID and a LANGUAGE_ID and returns the associated TRANSLATED_NAME. Each version of productName caches translated names, but at a different granularity.

In Example 9-43, get_product_name_1 is a result-cached function. Whenever get_product_name_1 is invoked with a different PRODUCT_ID and LANGUAGE_ID, it caches the associated TRANSLATED_NAME. Each invocation of get_product_name_1 adds at most one TRANSLATED_NAME to the cache.

In Example 9-44, get_product_name_2 defines a result-cached function, all_product_names. Whenever get_product_name_2 invokes all_product_names with a different LANGUAGE_ID, all_product_names caches every TRANSLATED_NAME associated with that LANGUAGE_ID. Each invocation of all_product_names adds every TRANSLATED_NAME of at most one LANGUAGE_ID to the cache.

Example 9-43 Caching One Name at a Time (Finer Granularity)

CREATE OR REPLACE FUNCTION get_product_name_1 (
  prod_id NUMBER,
  lang_id VARCHAR2
)
  RETURN NVARCHAR2
  RESULT_CACHE
  AUTHID DEFINER
IS
  result_ VARCHAR2(50);
BEGIN
  SELECT translated_name INTO result_
  FROM OE.Product_Descriptions
  WHERE PRODUCT_ID = prod_id
  AND LANGUAGE_ID = lang_id;
  RETURN result_;
END;
/

Example 9-44 Caching Translated Names One Language at a Time (Coarser Granularity)

CREATE OR REPLACE FUNCTION get_product_name_2 (
  prod_id NUMBER,
  lang_id VARCHAR2
)
  RETURN NVARCHAR2
  AUTHID DEFINER
IS
  TYPE product_names IS TABLE OF NVARCHAR2(50) INDEX BY PLS_INTEGER;
 
  FUNCTION all_product_names (lang_id VARCHAR2)
    RETURN product_names
    RESULT_CACHE
  IS
    all_names product_names;
  BEGIN
    FOR c IN (SELECT * FROM OE.Product_Descriptions
              WHERE LANGUAGE_ID = lang_id) LOOP
      all_names(c.PRODUCT_ID) := c.TRANSLATED_NAME;
    END LOOP;
    RETURN all_names;
  END;
BEGIN
  RETURN all_product_names(lang_id)(prod_id);
END;
/

Result Caches in Oracle RAC Environment

Cached results are stored in the system global area (SGA). In an Oracle RAC environment, each database instance manages its own local function result cache. However, the contents of the local result cache are accessible to sessions attached to other Oracle RAC instances. If a required result is missing from the result cache of the local instance, the result might be retrieved from the local cache of another instance, instead of being locally computed. The access pattern and workload of an instance determine the set of results in its local cache; therefore, the local caches of different instances can have different sets of results.

Before fetching a cached result from a remote instance, the database uses heuristics to determine if it is more cost efficient to recompute the result on the local instance. You can monitor the use of this functionality by querying the V$RESULT_CACHE_OBJECTS and V$RESULT_CACHE_STATISTICS views. The V$RESULT_CACHE_OBJECTS has a value ’Yes’ in the GLOBAL column if the object has been fetched from the result cache of another instance. A value of ’No’ means that the result was locally recomputed, either because it was not available remotely, or because the system has decided it is more efficient to do so instead of fetching it remotely. The statistics ’Global Prune Count’ in the V$RESULT_CACHE_STATISTICS view shows the number of times the decision was made not to fetch from a remote instance. ’Global Prune By Self Count’ shows the number of times an instance asked to provide a local result and has decided it is more efficient for the requesting instance to compute the result locally. Finally, ’Global Load Rate’ shows the computed rate - in bytes per 10 milliseconds - of fetching results from result cache of other instances. All these statistics only apply to global result caches in a RAC environment.

Although each database instance might have its own set of cached results, the mechanisms for handling invalid results are Oracle RAC environment-wide. For example, consider a result cache of item prices that are computed from data in database tables. If any of these database tables is updated in a way that affects the price of an item, the cached price of that item is invalidated in every database instance in the Oracle RAC environment.

See Also:

Real Application Clusters Administration and Deployment Guide for more information about setting RESULT_CACHE_MAX_SIZE parameter and other initialization parameters in an Oracle RAC database

Result Cache Management

The PL/SQL function result cache shares its administrative and manageability infrastructure with the Result Cache.

You can administer the shared pool area part that is used by the SQL result cache and the PL/SQL function result cache using the DBMS_RESULT_CACHE subprograms. Using the DBMS_RESULT_CACHE.BLACKLIST_ADD procedure, you can add a query or a PL/SQL function to a blocklist to stop caching the results. No matter the bind variables or arguments used, there will be no objects generated for it. The result cache row source may still appear in the explain plan, but at runtime it will be a no-op. You can solve a result cache issue if you diagnose by looking for a case when ten of thousands set of cached results unique arguments is run for a function. Depending on the workload, the overhead of managing these cached results might offset the benefits of caching the results. The performance views gives you insight on this special cases.

You can run a query to identify problematic queries or functions. The cache_id is the result cache identifier of a SQL cursor or PL/SQL function. This query counts how many unique result cache objects were made for each cache id. A unique object is created for every run of a query or function with unique bind variables or arguments.

SELECT cache_id, COUNT(cache_key) AS uniq_args
FROM GV$RESULT_CACHE_OBJECTS
WHERE type = 'Result'
GROUP BY cache_id
ORDER BY uniq_args DESC;

When a dependent object is frequently updated by a workload, it can adversely impact the performance benefits of using result cache. For example, when a large transaction is committed and is affecting already cached results, messages are sent to invalidate these cached results to prevent wrong results. The first hint that this bottleneck is happening is the observation of high waits with CHANNEL = 'Result Cache: Channel' in the GV$CHANNEL_WAITS view. You can run a query to check the culprit and take appropriate action such as adding the object to the blocklist. An object with an extremely high number of invalidations can be diagnosed using this query.

SELECT object_no, SUM(invalidations) AS num_invals
FROM GV$RESULT_CACHE_OBJECTS
WHERE type = 'Dependency'
GROUP BY object_no
ORDER BY num_invals DESC;

See Also:

Dynamic performance views provide information to monitor the server and client result caches.

See Also:

The database administrator manages the server result cache by specifying the result cache initialization parameters.

See Also:

Hot-Patching PL/SQL Units on Which Result-Cached Functions Depend

When you hot-patch a PL/SQL unit on which a result-cached function depends (directly or indirectly), the cached results associated with the result-cached function might not be automatically flushed in all cases.

For example, suppose that the result-cached function P1.foo() depends on the package subprogram P2.bar(). If a new version of the body of package P2 is loaded, the cached results associated with P1.foo() are not automatically flushed.

Therefore, this is the recommended procedure for hot-patching a PL/SQL unit:

Note:

To follow these steps, you must have the EXECUTE privilege on the package DBMS_RESULT_CACHE.

  1. Put the result cache in bypass mode and flush existing results:
    BEGIN
      DBMS_RESULT_CACHE.Bypass(TRUE);
      DBMS_RESULT_CACHE.Flush;
    END;
    /

    In an Oracle RAC environment, perform this step for each database instance.

  2. Patch the PL/SQL code.
  3. Resume using the result cache:
    BEGIN
      DBMS_RESULT_CACHE.Bypass(FALSE);
    END;
    /

    In an Oracle RAC environment, perform this step for each database instance.