【PL/SQL】FUNCTION RESULT_CACHEの使い方|関数キャッシュの判断基準・確認SQL・注意点

【PL/SQL】結果キャッシュ(FUNCTION RESULT_CACHE)の活用方法 PL/SQL

FUNCTION RESULT_CACHE は、PL/SQL関数の戻り値をSGA上の結果キャッシュに保存し、同じ引数で呼び出されたときに再計算を避けるための仕組みです。計算コストが高く、入力に対して結果が安定している関数では、大きな効果が出ることがあります。

ただし、付ければ必ず速くなる機能ではありません。セッション依存値、NLS、権限、頻繁に更新される表、軽すぎる関数、RAC環境などでは、むしろ扱いづらくなることがあります。Result Cache全体の仕組みは Oracle Result Cache完全ガイド、関数設計全般は プロシージャ・ファンクション完全ガイド も参考になります。

この記事で扱うこと

  • FUNCTION RESULT_CACHE の基本構文
  • 使ってよい関数・使ってはいけない関数の判断基準
  • RELIES_ON の現行Oracleでの扱い
  • DBMS_RESULT_CACHEV$RESULT_CACHE_OBJECTS の確認SQL
  • 実行時間の測定、キャッシュヒット確認、フラッシュ方法
  • RAC、セッション依存値、DML無効化、NLSの注意点
スポンサーリンク

FUNCTION RESULT_CACHEとは

RESULT_CACHE を付けたPL/SQL関数は、同じ引数で呼び出された場合に、前回の戻り値を再利用できます。Oracle公式ドキュメントでも、結果はSGA上にキャッシュされ、同じインスタンスに接続するセッションで再利用できると説明されています。

保存場所セッション内ではなく、SGA上の共有結果キャッシュに保存されます。
キー関数名と引数値などをもとに同一呼び出しを判定します。
無効化参照した表が更新されると、関連するキャッシュは無効化されます。
目的高コストで結果が安定する処理の再計算を避けます。

基本構文

関数定義に RESULT_CACHE を付けます。まずは、入力値だけで結果が決まる単純な例で動きを確認します。

basic-result-cache.sql
CREATE OR REPLACE FUNCTION calc_tax(
  p_amount IN NUMBER
) RETURN NUMBER
RESULT_CACHE
IS
BEGIN
  RETURN ROUND(p_amount * 0.10);
END;
/

SELECT calc_tax(1000) FROM dual;
SELECT calc_tax(1000) FROM dual; -- 同じ引数ならキャッシュ再利用候補

ただし、このように軽い計算ではキャッシュの管理コストの方が大きい可能性があります。Result Cacheは、重い計算、頻繁な参照、結果の安定性がそろったときに検討します。

使ってよい関数の条件

Result Cache向きの関数は、同じ入力なら同じ結果を返し、かつ再計算コストが高い関数です。参照するデータが頻繁に変わらず、複数セッションから同じ値で呼ばれるほど効果が出ます。

同じ入力で同じ結果引数以外の状態に左右されにくい関数です。
計算コストが高い集計、マスタ参照、複雑な判定など再計算が重い処理に向きます。
更新頻度が低い参照表が頻繁にDMLされると無効化が多くなります。
呼び出し回数が多い同じ引数で繰り返し呼ばれるほど効果があります。
戻り値が小さい巨大な戻り値を大量にキャッシュするとメモリを圧迫します。

使ってはいけない関数

Result Cacheに向かない関数もあります。特に、セッションや時刻、NLS、アプリケーションコンテキスト、外部状態に依存する関数は注意が必要です。

SYSDATE依存現在時刻で結果が変わる関数はキャッシュに向きません。
SYS_CONTEXT依存ユーザー、テナント、ロールなどセッション依存値を読む関数は危険です。
NLS依存日付や数値の文字列表現がNLS設定で変わる場合は避けます。
副作用ありログ出力、DML、外部呼び出し、シーケンス採番を伴う関数には使いません。
更新頻度が高いDMLのたびに無効化され、キャッシュ効果が出にくくなります。
bad-result-cache.sql
-- NG例: 時刻に依存する
CREATE OR REPLACE FUNCTION today_label
RETURN VARCHAR2
RESULT_CACHE
IS
BEGIN
  RETURN TO_CHAR(SYSDATE, 'YYYY-MM-DD');
END;
/

-- NG例: セッション依存値に依存する
CREATE OR REPLACE FUNCTION current_tenant
RETURN VARCHAR2
RESULT_CACHE
IS
BEGIN
  RETURN SYS_CONTEXT('APP_CTX', 'TENANT_ID');
END;
/

セッション依存の高速化は、Result Cacheではなく、パッケージ変数のセッション内キャッシュや通常の索引設計を検討します。アプリケーションコンテキストを使う設計は DBMS_SESSION.SET_CONTEXTの利用例 と合わせて考えると整理しやすいです。

マスタ参照関数の実装例

Result Cacheが効きやすい例として、更新頻度の低いマスタから表示名を引く関数があります。頻繁にSQL内で呼ばれるが、マスタは日中ほとんど変わらない、という条件なら候補になります。

master-lookup-cache.sql
CREATE TABLE m_status (
  status_code VARCHAR2(20) PRIMARY KEY,
  status_name VARCHAR2(100) NOT NULL
);

CREATE OR REPLACE FUNCTION get_status_name(
  p_status_code IN VARCHAR2
) RETURN VARCHAR2
RESULT_CACHE
IS
  l_status_name m_status.status_name%TYPE;
BEGIN
  SELECT status_name
  INTO l_status_name
  FROM m_status
  WHERE status_code = p_status_code;

  RETURN l_status_name;
EXCEPTION
  WHEN NO_DATA_FOUND THEN
    RETURN NULL;
END;
/

SELECT order_id,
       get_status_name(status_code) AS status_name
FROM orders;

SQL内でPL/SQL関数を呼ぶ設計は、関数呼び出しコストも含めて測る必要があります。SQL内PL/SQL関数の扱いは WITH FUNCTION句の使い方 も関連します。

RELIES_ONの扱い

古いサンプルでは RESULT_CACHE RELIES_ON(table_name) を見かけます。ただし、現行のOracleでは、関数実行中に問い合わせた表やビューなどの依存データソースをデータベース側が自動検出します。Oracle 12c以降では RELIES_ON は実質的に意味を持たない扱いなので、新規コードでは基本的に書かない方針でよいです。

relies-on-note.sql
-- 古い11g系サンプルで見かける形
CREATE OR REPLACE FUNCTION get_customer_sales(p_customer_id NUMBER)
RETURN NUMBER
RESULT_CACHE RELIES_ON (orders)
IS
  l_total NUMBER;
BEGIN
  SELECT NVL(SUM(amount), 0)
  INTO l_total
  FROM orders
  WHERE customer_id = p_customer_id;

  RETURN l_total;
END;
/

-- 現行設計では通常 RELIES_ON を書かず、依存関係の自動検出に任せる
CREATE OR REPLACE FUNCTION get_customer_sales(p_customer_id NUMBER)
RETURN NUMBER
RESULT_CACHE
IS
  l_total NUMBER;
BEGIN
  SELECT NVL(SUM(amount), 0)
  INTO l_total
  FROM orders
  WHERE customer_id = p_customer_id;

  RETURN l_total;
END;
/

キャッシュ状態を確認する

Result Cacheを使ったら、効いているか確認します。関数に句が付いているか、キャッシュオブジェクトが作られているか、統計上ヒットしているかを見ます。

inspect-result-cache.sql
-- 関数にRESULT_CACHEが付いているか
SELECT object_name,
       procedure_name,
       result_cache
FROM user_procedures
WHERE result_cache = 'YES'
ORDER BY object_name, procedure_name;

-- キャッシュされているオブジェクト
SELECT id,
       type,
       status,
       name,
       namespace,
       creation_timestamp
FROM v$result_cache_objects
ORDER BY creation_timestamp DESC;

-- 統計情報
SELECT name, value
FROM v$result_cache_statistics
ORDER BY name;

V$RESULT_CACHE_OBJECTSV$RESULT_CACHE_STATISTICS の参照には権限が必要です。権限がない開発環境では、DBAに確認してもらうか、検証用スキーマで参照権限を付与してもらいます。

DBMS_RESULT_CACHEで管理する

結果キャッシュは DBMS_RESULT_CACHE でフラッシュやメモリレポートを実行できます。本番でむやみに全フラッシュすると他処理にも影響するため、検証環境で使い方を確認しておきます。

dbms-result-cache.sql
-- キャッシュ全体のフラッシュ
BEGIN
  DBMS_RESULT_CACHE.FLUSH;
END;
/

-- メモリ使用状況のレポート
SET SERVEROUTPUT ON
BEGIN
  DBMS_RESULT_CACHE.MEMORY_REPORT;
END;
/

Result Cacheの詳細な管理やSQLヒント側の使い方は Result Cache完全ガイド 側で確認できます。パフォーマンス改善全体の位置づけは PL/SQLパフォーマンス改善プレイブック も参考になります。

実行時間を測定する

Result Cacheは、体感で判断せず測定します。同じ引数で1回目と2回目を呼び、統計や経過時間を比較します。ただし、SQLキャッシュ、バッファキャッシュ、実行計画キャッシュの影響もあるため、単純な1回比較だけで結論を出さないようにします。

measure-result-cache.sql
SET SERVEROUTPUT ON
DECLARE
  l_start PLS_INTEGER;
  l_value NUMBER;
BEGIN
  DBMS_RESULT_CACHE.FLUSH;

  l_start := DBMS_UTILITY.GET_TIME;
  l_value := get_customer_sales(1001);
  DBMS_OUTPUT.PUT_LINE('first=' || (DBMS_UTILITY.GET_TIME - l_start));

  l_start := DBMS_UTILITY.GET_TIME;
  l_value := get_customer_sales(1001);
  DBMS_OUTPUT.PUT_LINE('second=' || (DBMS_UTILITY.GET_TIME - l_start));
END;
/

本格的にボトルネックを確認する場合は、Result Cacheだけでなくプロファイラで関数呼び出し回数や実行時間を確認します。行単位の分析は PL/SQLプロファイラ完全ガイドDBMS_PROFILERで行レベル分析する完全ガイド が役立ちます。

DMLによる無効化を理解する

Result Cacheは、関数が問い合わせた表にDMLがコミットされると、関連するキャッシュが無効化されます。つまり、更新頻度が高い表を参照する関数では、キャッシュしてもすぐ無効になり、効果が出にくくなります。

invalidate-cache.sql
-- 1回目: キャッシュ作成
SELECT get_customer_sales(1001) FROM dual;

-- 2回目: キャッシュ再利用候補
SELECT get_customer_sales(1001) FROM dual;

-- 参照表を更新してコミット
UPDATE orders
SET amount = amount + 100
WHERE customer_id = 1001;
COMMIT;

-- 関連キャッシュは無効化され、再計算される
SELECT get_customer_sales(1001) FROM dual;

RAC環境での注意

RAC環境では、インスタンスごとのキャッシュ、無効化通知、メモリ使用量を意識します。同じ関数でも、どのインスタンスで呼ばれるかによってキャッシュヒットの状況が変わることがあります。本番では単一インスタンスの検証結果だけで判断せず、接続先分散やサービス構成も含めて確認します。

よくある失敗

軽い関数に付けて逆に遅くなる

文字列連結や単純計算のような軽い関数では、キャッシュ管理コストの方が目立つことがあります。Result Cacheは重い処理に絞って使います。

ユーザーごとに結果が違う関数へ付ける

同じ引数でもユーザー、テナント、ロール、NLSで結果が変わる関数は危険です。セッション依存値を読む関数では、キャッシュキーにその値が入らない前提で慎重に判断します。

更新頻度が高い表に使う

DMLで無効化されるため、常に更新されるテーブルではキャッシュヒットより無効化が多くなりがちです。この場合は索引、SQL改善、集計テーブル、マテリアライズドビューを検討します。Result Cacheとマテリアライズドビューの併用は Result Cacheとマテリアライズドビューを併用した高速化戦略 も関連します。

本番前チェックリスト

結果の安定性同じ引数なら本当に同じ結果を返すか。
セッション依存SYS_CONTEXT、NLS、CURRENT_USER、SYSDATEに依存していないか。
更新頻度参照表のDMLが多すぎないか。
計測1回目/2回目だけでなく、実運用に近い呼び出し回数で測ったか。
監視V$RESULT_CACHE_STATISTICS でヒット率やメモリを確認できるか。
メモリRESULT_CACHE_MAX_SIZE と共有リソースへの影響を確認したか。
RAC複数インスタンスでのキャッシュ挙動を確認したか。
RELIES_ON古いサンプルをそのまま使わず、現行仕様に合わせているか。

まとめ

FUNCTION RESULT_CACHE は、同じ入力で同じ結果を返す高コストなPL/SQL関数に対して、再計算を避けるための有効な選択肢です。一方で、セッション依存値、時刻、NLS、副作用、更新頻度の高い表に依存する関数には向きません。

実務では、まずプロファイラや実行時間測定で本当に関数がボトルネックか確認し、Result Cache向きの条件を満たす関数にだけ適用します。RELIES_ON は古いサンプルとして見かけますが、現行Oracleでは依存関係の自動検出を前提にし、確認SQLと運用監視まで含めて導入しましょう。