【PL/SQL】Result Cacheとマテリアライズドビューを併用した高速化戦略

【PL/SQL】Result Cacheとマテリアライズドビューを併用した高速化戦略 PL/SQL

Result Cacheとマテリアライズドビュー(MV)はどちらも「繰り返し同じ結果を返す処理を前計算または再利用で速くする」ための仕組みですが、効くレイヤーと無効化の条件が異なります。MVはSQL結果を物理表として保持し、更新ポリシーで鮮度を制御します。Result Cache(ここではPL/SQL Function Result CacheとServer Result Cacheの総称)はメモリ上に結果をキャッシュし、同一入力に対して再計算を避けます。この記事では両者を併用して応答時間を最短化する戦略を、設計の考え方、作り方、無効化・鮮度管理、監視とメンテナンスの順に解説します。

設計方針:MVで「重い集合処理」を前倒し、Result Cacheで「高頻度の同一呼び出し」を短絡

基幹となる集計・結合・フィルタをMVで前計算し、参照時はMVから読むだけにします。呼び出し側で同じキーや期間の問い合わせが頻発するなら、PL/SQLのRESULT_CACHEやServer Result Cacheで最終結果をメモ化します。MVは「分単位やイベント単位で更新される基底データ」に対して効果を発揮し、Result Cacheは「秒〜分の短い間に同じ問い合わせが繰り返される」パターンで真価を発揮します。

ステップ1:ベース表にマテリアライズドビュー・ログを用意

FAST REFRESHを狙うなら、基底表にMVログを作成します。主キー、行識別情報、必要な列をログ化しておくと差分適用が可能になります。

-- 基底表
CREATE TABLE sales(
  sale_id     NUMBER PRIMARY KEY,
  cust_id     NUMBER NOT NULL,
  amount      NUMBER(12,2) NOT NULL,
  sale_dt     DATE NOT NULL
);

-- FAST REFRESH用ログ
CREATE MATERIALIZED VIEW LOG ON sales
  WITH ROWID, PRIMARY KEY, SEQUENCE (cust_id, amount, sale_dt)
  INCLUDING NEW VALUES;

ステップ2:集計MVを作成(リフレッシュポリシーの決定)

集計の代表例として、顧客×月の合計を保持します。用途に応じてON COMMIT、ON DEMAND、スケジューラ連携を選びます。

CREATE MATERIALIZED VIEW mv_sales_cust_m
BUILD IMMEDIATE
REFRESH FAST ON DEMAND
ENABLE QUERY REWRITE
AS
SELECT
  cust_id,
  TRUNC(sale_dt, 'MM') AS ym,
  SUM(amount) AS sum_amount,
  COUNT(*)    AS cnt
FROM sales
GROUP BY cust_id, TRUNC(sale_dt, 'MM');

「書き込み頻度が低く読み取りが極端に多い」ならON COMMITで即時同期も選べますが、トランザクションの待ち時間が増えるため、通常はON DEMAND+スケジューラで短い間隔の差分更新が扱いやすくなります。

ステップ3:MVを読むPL/SQL関数にFUNCTION RESULT_CACHEを付与

集計MVから特定キーを引く操作は同一入力で再利用可能です。関数インターフェースを用意してUI層や他PL/SQLから共通利用すると効果が波及します。

CREATE OR REPLACE FUNCTION get_monthly_sum(p_cust_id NUMBER, p_ym DATE)
  RETURN NUMBER
  RESULT_CACHE
IS
  v_sum NUMBER;
BEGIN
  SELECT sum_amount INTO v_sum
    FROM mv_sales_cust_m
   WHERE cust_id = p_cust_id
     AND ym      = TRUNC(p_ym, 'MM');
  RETURN NVL(v_sum, 0);
EXCEPTION
  WHEN NO_DATA_FOUND THEN
    RETURN 0;
END;
/

この関数は同じ(p_cust_id, p_ym)ならキャッシュを返します。MVのリフレッシュが走ると該当領域は整合性上再評価が必要になるため、鮮度要求に合わせてMVの更新頻度を決めるのが肝要です。

ステップ4:クエリ全体にServer Result Cacheを併用(読み取り特化の高速化)

PL/SQL関数のキャッシュに加え、SQL側のServer Result CacheでSELECT結果をメモリに保持できます。短時間に同じクエリが繰り返されるAPIやダッシュボードで有効です。

-- セッション既定(必要なら)
ALTER SESSION SET result_cache_mode = MANUAL;

-- ヒントで明示
SELECT /*+ RESULT_CACHE */ cust_id, ym, sum_amount
  FROM mv_sales_cust_m
 WHERE ym = DATE '2025-10-01';

Server Result Cacheはテーブル変更で自動無効化されます。MVを読んでいる場合はMVのリフレッシュが境界となり、鮮度とヒット率のトレードオフを調整できます。

リフレッシュ運用:DBMS_MVIEW.REFRESHの粒度と並列

ON DEMAND運用では、スケジューラやバッチから差分更新を行います。複数MVをまとめて整合性を保つ場合はリスト刷新を使います。

BEGIN
  DBMS_MVIEW.REFRESH(
    list            => 'MV_SALES_CUST_M',
    method          => 'F',          -- F: FAST, C: COMPLETE
    atomic_refresh  => TRUE,         -- 一括トランザクション
    parallelism     => 4
  );
END;
/

FASTで追随できない変更(例えば集計式の大幅変更)を行った後は一時的にCOMPLETEへ切り替え、再度FAST条件を満たすようログや定義を見直します。

無効化・鮮度の考え方:どちらを“真実”とみなすかを先に決める

MVは「前回のリフレッシュ時点」での真実、Result Cacheは「最後に計算したとき」の真実です。BIやダッシュボードのように秒単位の最新性が不要な画面では、MV更新間隔(例:1分)を明示してSLAに織り込みます。オペレーション画面で直近トランザクションの反映が必要な操作にはMVを介さずベース表を直接参照させるなどの切り分けが現実的です。

監視:キャッシュヒットとMV状態を可視化する

キャッシュは「ヒットしているか」を継続観測し、パラメータやSQL側の設計を調整します。MVは無効化や遅延を検知します。

-- 結果キャッシュのオブジェクト確認
SELECT name, type, status
  FROM v$result_cache_objects;

-- MVの状態
SELECT mview_name, staleness, last_refresh_type, last_refresh_date
  FROM user_mviews;

-- (必要に応じて)プラン上の結果キャッシュ利用確認
SELECT sql_id, executions, loads, rows_processed
  FROM v$sqlarea
 WHERE sql_text LIKE '%RESULT_CACHE%';

実戦テンプレート:MV→関数→APIの三層で再利用を最大化

読み取り回数が多い集計APIは、MVから読み取る薄い関数へ集約し、その関数自体にRESULT_CACHEを付け、さらにAPIやSQLは同関数を呼ぶ形に統一します。

-- 1) MV読み取りの薄い関数(上で作成済み)
-- 2) APIファサード(JSONで返す例)
CREATE OR REPLACE FUNCTION api_monthly_sum(p_cust_id NUMBER, p_ym DATE)
  RETURN VARCHAR2
IS
  v_val NUMBER := get_monthly_sum(p_cust_id, p_ym);
BEGIN
  RETURN JSON_OBJECT('custId' VALUE p_cust_id,
                     'ym'     VALUE TO_CHAR(TRUNC(p_ym,'MM'),'YYYY-MM'),
                     'sum'    VALUE v_val);
END;
/
-- 3) ダッシュボードSQL(必要ならRESULT_CACHEヒント)
SELECT /*+ RESULT_CACHE */ get_monthly_sum(:cid, :ym) FROM dual;

よくある詰まりと回避策

MVのFAST条件が満たせずCOMPLETEに落ちると更新時間とREDOが跳ね上がります。基底表のキー、MVログ列、集計式を見直して再度FASTへ戻すのが第一です。PL/SQLのFUNCTION RESULT_CACHEは依存テーブルの更新で無効化されますが、RELIES_ON句を安易に多用すると広範囲に無効化を誘発します。MVを参照する関数は通常RELIES_ON不要で、MVの更新リズムに結果整合を委ねるのが簡潔です。Server Result Cacheは「微小差分でも無効化」されやすいため、MV経由にするほどヒット率が安定します。キャッシュサイズ(RESULT_CACHE_MAX_SIZE)やブロックサイズが小さすぎるとスラッシングが起きるため、AWRやv$ビューでヒット率を見ながら段階的に調整します。

パーティション設計との併用でスケールさせる

基底表を期間レンジでパーティションし、MVも同様の表現を取ると、EXCHANGE PARTITIONで新期間のロードとMVリフレッシュの整合が取りやすくなります。更新対象が最新パーティションに偏るワークロードでは、古い領域のキャッシュは長寿命化し、最新領域だけ短周期で回すとヒット率と鮮度の両立が可能です。

まとめ

重い集合処理はMVで前計算し、最終的な頻出問い合わせはResult Cacheで短絡する――この二段構えが最短応答と安定したリソース消費を両立させます。MVログとFAST REFRESHで差分追随し、リフレッシュの粒度をSLAに合わせて設計します。関数のRESULT_CACHEとServer Result Cacheは「同一入力の反復」にだけ効くため、APIやUIの呼び出し単位を定型化しキャッシュ効果を最大化します。監視ではヒット率とMVの鮮度を並行評価し、サイズ・更新間隔・並列度を段階的に調整することが、現場で効く高速化戦略の要諦です。