Result Cache とマテリアライズドビュー(MV)は、どちらも「同じ計算を何度も繰り返さない」ための高速化手段です。ただし、同じキャッシュという言葉でまとめると設計を誤ります。MVは重いJOIN、GROUP BY、集計、明細からサマリーへの変換をデータベース上に保持する仕組みで、Result Cacheは同じSQL結果や同じ引数のPL/SQL関数結果をSGA上で再利用する仕組みです。
結論から言うと、大きな集合処理はMVで前計算し、頻繁に参照される小さな派生値はResult Cacheで再利用するのが基本です。そのうえで、業務が許容する鮮度、更新頻度、DML負荷、障害時の復旧方法、監視SQLまで決めておくと、本番で壊れにくい高速化になります。単体のResult Cache設計は FUNCTION RESULT_CACHEの使い方、Oracle全体のResult Cacheは Oracle Result Cache完全ガイド もあわせて確認してください。
- MV、Function Result Cache、Server Result Cache、索引、パーティションの使い分け
- 鮮度SLAから
ON COMMITとON DEMANDを決める考え方 QUERY REWRITE、DBMS_MVIEW.EXPLAIN_MVIEW、STALENESSの確認V$RESULT_CACHE_STATISTICSとV$RESULT_CACHE_OBJECTSによるResult Cache監視- 本番投入前に避けるべき設計とチェックリスト
最初に結論:MVとResult Cacheは役割を分ける
MVとResult Cacheを併用するときは、先にデータ量と鮮度で役割を切ります。MVはディスク上に結果を保持できるため、何百万行を集計して数千行のサマリーにするような処理に向きます。Result Cacheはメモリ上の再利用なので、戻り値が小さく、引数の種類が絞られ、参照元の更新頻度が低い処理に向きます。
RESULT_CACHE ヒントなどで、同じSELECT結果を短時間に多くのセッションが読むケースです。索引やパーティションで解ける問題を、いきなりMVやResult Cacheに寄せると、更新運用と監視が増えるだけになります。大量データ側の切り分けは Oracleパーティションテーブル完全ガイド、PL/SQLの性能改善全体は PL/SQLパフォーマンス改善プレイブック が関連します。
判断基準:先に鮮度SLAを決める
キャッシュ設計で最初に決めるべきなのは「何秒・何分・何時間まで古くてよいか」です。この鮮度SLAが曖昧なままMVやResult Cacheを入れると、速くはなっても業務上の正しさを説明できません。
ON COMMIT MVや同期更新を検討できますが、更新トランザクションにリフレッシュ負荷が乗ります。ON DEMAND MVを DBMS_SCHEDULER で定期更新する設計が扱いやすくなります。大事なのは、キャッシュの古さを障害ではなく仕様として説明できることです。たとえば「売上速報は5分遅れまで許容」「請求確定は最新必須」のように、画面や処理ごとに分けて決めます。
MVの基本構成
MVを高速化の中心に置く場合は、ベース表、MVログ、MV本体、リフレッシュ方式、クエリリライト可否をセットで設計します。特にFAST REFRESHを狙う場合、対象SQLの形やMVログの有無で成立条件が変わるため、作ってから速いかどうかを見るのでは遅いです。
MV単体の作成、管理、リフレッシュ方式を先に確認したい場合は Oracleマテリアライズドビュー完全ガイド で基本構文と運用ポイントを押さえてから、この記事の併用設計に進むと整理しやすくなります。
MVログを作成する
CREATE MATERIALIZED VIEW LOG ON sales WITH ROWID, SEQUENCE (customer_id, sales_date, amount) INCLUDING NEW VALUES; CREATE MATERIALIZED VIEW LOG ON customers WITH ROWID, SEQUENCE (customer_id, customer_rank) INCLUDING NEW VALUES;
MVログはFAST REFRESHの差分更新に使われます。ログを作るとDML時の記録コストも増えるため、更新頻度が高い表では書き込み側の性能も測定します。
集計MVを作成する
CREATE MATERIALIZED VIEW mv_sales_daily_rank
BUILD IMMEDIATE
REFRESH FAST ON DEMAND
ENABLE QUERY REWRITE
AS
SELECT c.customer_rank,
TRUNC(s.sales_date) AS sales_day,
COUNT(*) AS order_count,
SUM(s.amount) AS total_amount
FROM sales s
JOIN customers c
ON c.customer_id = s.customer_id
GROUP BY c.customer_rank,
TRUNC(s.sales_date);
ENABLE QUERY REWRITE を付けると、条件がそろった場合にOracleが元SQLをMV参照へ書き換えられます。ただし、MVが古い、制約や統計情報が不足している、クエリリライト設定が無効、SQLの形が合わない、といった理由で使われないことがあります。
リフレッシュ方式を選ぶ
MVのリフレッシュは、鮮度と更新負荷のトレードオフです。ON COMMIT は最新性を保ちやすい反面、ベース表のCOMMITに追加処理が乗ります。ON DEMAND は遅延を許容する代わりに、更新タイミングをジョブで制御しやすくなります。
BEGIN
DBMS_MVIEW.REFRESH(
list => 'MV_SALES_DAILY_RANK',
method => 'F',
atomic_refresh => TRUE
);
END;
/
BEGIN
DBMS_MVIEW.REFRESH(
list => 'MV_SALES_DAILY_RANK',
method => 'C',
atomic_refresh => FALSE
);
END;
/
atomic_refresh の扱いは、参照中の一貫性、リフレッシュ時間、領域使用量、メンテナンス時間帯によって決めます。本番ではFASTで失敗したときにCOMPLETEへ自動で逃がすのか、停止して通知するのかも決めておきます。
QUERY REWRITEを確認する
MVを作っただけでは、既存SQLが必ずMVを使うとは限りません。クエリリライトを狙うなら、パラメータ、権限、制約、統計情報、MVの鮮度、SQLの形を確認します。実行計画の読み方は DBMS_XPLAN完全ガイド と EXPLAIN PLAN完全ガイド が関連します。
EXPLAIN_MVIEWで成立条件を見る
BEGIN
DBMS_MVIEW.EXPLAIN_MVIEW('MV_SALES_DAILY_RANK');
END;
/
SELECT capability_name,
possible,
related_text,
msgtxt
FROM mv_capabilities_table
WHERE mvname = 'MV_SALES_DAILY_RANK'
ORDER BY capability_name;
DBMS_MVIEW.EXPLAIN_MVIEW は、FAST REFRESHやQUERY REWRITEが可能かを確認する入口になります。できない理由が MSGTXT に出るため、MVログ不足、集計式、JOIN条件、制約不足などを切り分けます。
リライトされているかを見る
EXPLAIN PLAN FOR
SELECT c.customer_rank,
TRUNC(s.sales_date) AS sales_day,
SUM(s.amount) AS total_amount
FROM sales s
JOIN customers c
ON c.customer_id = s.customer_id
WHERE s.sales_date >= DATE '2026-01-01'
GROUP BY c.customer_rank,
TRUNC(s.sales_date);
SELECT *
FROM TABLE(DBMS_XPLAN.DISPLAY(NULL, NULL, 'BASIC +NOTE'));
実行計画やNOTEにMV利用の痕跡が出るかを確認します。SQLヒントで無理に寄せる前に、統計情報、クエリリライト設定、MVの STALENESS、制約の信頼性を確認します。ヒント句の使い方は Oracle SQLヒント句完全ガイド も参考になります。
MVの鮮度と状態を監視する
MVは作成して終わりではありません。運用では、最後にいつリフレッシュされたか、古くなっていないか、リフレッシュに失敗していないか、参照SQLが期待どおりMVを使っているかを確認します。
SELECT mview_name,
refresh_mode,
refresh_method,
staleness,
last_refresh_type,
last_refresh_date
FROM user_mviews
WHERE mview_name = 'MV_SALES_DAILY_RANK';
SELECT name,
last_refresh
FROM user_mview_refresh_times
WHERE name = 'MV_SALES_DAILY_RANK';
SELECT master,
log_table
FROM user_mview_logs
ORDER BY master;
STALENESS は、MVが最新か、古いか、利用不能に近い状態かを見る重要な列です。リフレッシュジョブの監視とあわせて、古い状態が業務SLAを超えたら通知する仕組みを用意します。ジョブ管理は DBMS_SCHEDULER完全ガイド と PL/SQLのDBMS_SCHEDULER運用 が関連します。
Result Cacheを重ねる場所
MVで集計結果を作ったあと、さらにPL/SQL関数で表示名、閾値、設定値、ランク判定を返す場合があります。この関数が同じ引数で大量に呼ばれるなら、Function Result Cacheを重ねる余地があります。逆に、毎回違う引数、戻り値が大きい、セッション状態に依存する、頻繁に更新される表を読む場合は向きません。
MVを読むResult Cache関数
CREATE OR REPLACE FUNCTION get_rank_sales_total(
p_customer_rank IN VARCHAR2,
p_sales_day IN DATE
) RETURN NUMBER
RESULT_CACHE
IS
l_total NUMBER;
BEGIN
SELECT total_amount
INTO l_total
FROM mv_sales_daily_rank
WHERE customer_rank = p_customer_rank
AND sales_day = TRUNC(p_sales_day);
RETURN l_total;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RETURN 0;
END;
/
古いサンプルでは RESULT_CACHE RELIES_ON(...) を見かけますが、現行設計では依存オブジェクトの自動検出に任せる方針が基本です。ただし、外部状態、セッション状態、NLS、アプリケーションコンテキスト、権限差で結果が変わる関数はキャッシュ対象から外します。依存オブジェクトとINVALIDの扱いは 依存オブジェクトとINVALID再コンパイル が近いテーマです。
Result Cacheの状態を確認する
Result Cacheは入れたら速くなる、ではなく、ヒットしているか、メモリを圧迫していないか、無効化が多すぎないかを見ます。キャッシュサイズが小さすぎる、引数の種類が多すぎる、更新が頻繁すぎる場合は、キャッシュの管理コストが勝ちます。
SELECT name,
value
FROM v$result_cache_statistics
ORDER BY name;
SELECT type,
status,
name,
namespace,
creation_timestamp
FROM v$result_cache_objects
ORDER BY creation_timestamp DESC;
BEGIN
DBMS_RESULT_CACHE.MEMORY_REPORT;
END;
/
リリース直後やMVリフレッシュ直後はキャッシュが無効化されることがあります。計測するときは、初回実行、2回目以降、リフレッシュ直後、ピーク時間帯を分けます。本番の遅いSQL特定は 遅いSQLを特定する方法、AWR/ASHは AWR・ASH完全ガイド と AWRレポート完全ガイド が役立ちます。
スケジューラでMV更新を管理する
ON DEMAND のMVは、手動実行ではなくスケジューラで管理します。ジョブ名、実行間隔、失敗時通知、最大実行時間、再実行方針、業務カレンダーを運用設計に入れます。
BEGIN
DBMS_SCHEDULER.CREATE_JOB(
job_name => 'JOB_REFRESH_MV_SALES_DAILY_RANK',
job_type => 'PLSQL_BLOCK',
job_action => q'[
BEGIN
DBMS_MVIEW.REFRESH(
list => 'MV_SALES_DAILY_RANK',
method => 'F',
atomic_refresh => TRUE
);
END;
]',
start_date => SYSTIMESTAMP,
repeat_interval => 'FREQ=MINUTELY;INTERVAL=5',
enabled => TRUE,
comments => 'Refresh sales summary materialized view every 5 minutes'
);
END;
/
リフレッシュジョブは、成功したかどうかだけでなく、実行時間が伸びていないかも見ます。5分間隔のジョブが4分50秒かかる状態は、次のデータ増加で破綻しやすいです。観測性の設計は PL/SQLインストゥルメンテーション設計 を組み合わせると運用しやすくなります。
ON COMMIT MVの注意点
ON COMMIT は便利ですが、COMMITが軽いままである保証はありません。ベース表に大量INSERT、UPDATE、DELETEが入る処理では、更新トランザクションのレスポンスが悪化したり、ロック保持時間が伸びたりします。
大量データ処理のCOMMIT設計は 大量データ処理のコミット頻度とUNDO最適化、トランザクション全体の考え方は PL/SQLトランザクション設計パターン も関連します。
併用パターン
MVとResult Cacheを併用するなら、役割を重ねすぎないことが重要です。MVで大きく削り、Result Cacheで小さく再利用する、という二段構えにします。
SQL内でPL/SQL関数を呼びすぎると、キャッシュされても呼び出しコストが残ります。SQL側で完結できる処理はSQLに寄せ、関数化した方が保守しやすい処理だけ切り出します。SQL内関数の扱いは WITH FUNCTION句の使い方、集計SQLの整理は 分析関数を活用した集計ロジック が関連します。
避けたい設計
キャッシュ系の高速化は、成功すると大きく効きます。一方で、間違えると「速いが古い」「速いがたまに間違う」「更新側が遅い」「障害時に直せない」という状態になります。
本番投入前チェックリスト
- 対象SQLの現状実行時間、実行回数、論理読取、待機イベントを測定している
- 最新必須、数秒遅延可、数分遅延可、日次可のどれかを業務側と合意している
- MVで前計算する範囲と、Result Cacheで再利用する範囲を分けている
- MVログのDML負荷を登録・更新・削除処理で測定している
DBMS_MVIEW.EXPLAIN_MVIEWでFAST REFRESHとQUERY REWRITEの成立条件を確認しているUSER_MVIEWS.STALENESSとリフレッシュジョブ履歴を監視対象にしているV$RESULT_CACHE_STATISTICSでヒット率、メモリ、無効化傾向を確認している- MVリフレッシュ失敗時の再実行、通知、COMPLETE更新、縮退運用を決めている
- リリース後にAWR/ASH/DBMS_XPLANで参照側と更新側の両方を再測定する
- セッション依存値、外部状態、副作用のある関数をResult Cache対象から外している
-- MV state
SELECT mview_name,
staleness,
last_refresh_type,
last_refresh_date
FROM user_mviews
WHERE mview_name = 'MV_SALES_DAILY_RANK';
-- Result cache statistics
SELECT name,
value
FROM v$result_cache_statistics
ORDER BY name;
-- Cached objects
SELECT type,
status,
name
FROM v$result_cache_objects
WHERE name LIKE '%GET_RANK_SALES_TOTAL%'
OR name LIKE '%MV_SALES_DAILY_RANK%'
ORDER BY type, status, name;
まとめ
Result Cacheとマテリアライズドビューを併用する目的は、単にキャッシュを増やすことではありません。重い集合処理をMVで前計算し、同じ引数で何度も呼ばれる小さな派生値をResult Cacheで再利用し、鮮度SLAと監視で本番運用できる形にすることです。
設計順は、現状計測、鮮度SLA、MV化するSQL、リフレッシュ方式、QUERY REWRITE確認、Result Cache対象の選定、監視、リリース後再測定です。この順番を守ると、速さだけでなく、古さ、更新負荷、障害時の戻し方まで説明できる高速化になります。

