従来の「REFRESH ON COMMIT」のマテリアライズド・ビュー(以下、ON COMMIT MView)を運用していて、ピーク時間帯になるとCOMMITがなかなか返ってこない、AWRレポートを解析すると「enq: JI - contention」が待機イベントの上位を占めている――そんな経験はありませんか。
集計の鮮度を最優先したくて「多少コストがかかってもON COMMITでリアルタイムに反映したい」と設計したはずが、いざ本番運用が軌道に乗りユーザー数やトランザクション数が増えてくると、「コミットのたびにMViewリフレッシュが走り、更新処理そのもののスループットを押し下げてしまう」というジレンマに悩まされるケースは少なくありません。
本記事では、従来のON COMMIT MViewが抱えてきたこのようなスループット低下や待機イベントのボトルネックを振り返りつつ、Oracle AI Database 26aiが提供する「同時リフレッシュ(Concurrent Refresh)」によって、OLTP/DWHそれぞれのユースケースでどのような改善が見込めるのかを検証していきます。
ON COMMIT MViewの長年の課題
まずMViewとは、通常のビューとは異なり、クエリの結果をデータとして保持するビューです。
例えば、DWH系のデータベースであれば、ファクト表とディメンション表の結合や集計といった高負荷な処理を事前に結果として格納しておくことができます。
分散データベース構成でのユースケースも想定され、Database Linkと共に活用することでリモート・サイトのデータをローカル・サイトにレプリケーションし、検索時のネットワーク負荷を下げるような活用方法も考えられます。
ON COMMIT MViewの概要と活用におけるネック
MViewはクエリの結果をデータとして保持するため、定期的な同期(リフレッシュ)によってデータの鮮度を保つ必要があります。
リフレッシュのタイミングはMViewのCREATE文で指定でき、「REFRESH ON COMMIT」句を指定したものを本記事ではON COMMIT MViewと呼びます。
ON COMMIT MViewでは、データの基となるマスター表に対するトランザクションがコミットされるタイミングで、自動的にリフレッシュが行われます。
これはMView上のデータが、マスター表と常に同期していることを必須とする要件がある場合などによく採用される構成の一つです。
ただし、ON COMMIT MViewが常に最適解とは限らず、この構成ではリフレッシュ処理がコミット処理の一部として実行されるため、マスター表のコミットに要する時間が長くなる制約があります。
特に、同じMViewのマスター表を複数セッションから同時更新すると、集計処理の整合性を保つためにコミット時のリフレッシュ処理が、enq: JI - contention(JI enqueue待ち)で直列化されてしまう点が、高負荷時の深刻なボトルネックとなってしまいました。
26aiの新機能「同時リフレッシュ(Concurrent Refresh)」とは
前述のような長年の悩みを緩和する新機能として、26aiからON COMMIT MViewの同時リフレッシュ(Concurrent Refresh)が登場しました。
同時リフレッシュは、これまで直列化されていた複数のセッションからのリフレッシュ処理の同時(並列)実行を実現します。
この同時セッションの数に制限がないことも驚きの一つです。
従って、OLTP系の処理においてON COMMIT MViewを採用するケースや、マイクロバッチによって複数のセッションよりファクト表が更新され得るデータベースなどにおいて、最大限の力を発揮できるものと考えられます。
「同時リフレッシュ」の使用上の注意事項
同時リフレッシュは、どのようなMViewの構成でも自由に利用できるわけではありません。
次の制限にご注意ください。
同時リフレッシュが行われる条件
- 同時リフレッシュを明示的に有効化している
※CREATE MATERIALIZED VIEW文のデフォルトでは、無効でMViewが作成されます
- すべての同時DMLセッションで同じマスター表が更新される
- 異なるリフレッシュ・セッションで更新されたMView行が重複しない
同時リフレッシュの制限事項
- 同時リフレッシュを有効化できるのはREFRESH FAST ON COMMITのMViewに限る
※REFRESH COMPLETE ON COMMITで有効化しようとすると、ORA-32383が発生します
(参考:My Oracle Support KB167453)(オラクル社のサイトに移動します)
- ログベースの高速リフレッシュでのみ有効で、パーティション操作などDDLを契機としたコミット時の高速リフレッシュでは利用できない
同時リフレッシュ有効化でスループットはどう変わる?
同時リフレッシュを活用することで、ON COMMIT MViewのマスター表に対する更新処理は、スループットがどのくらい向上するでしょうか。
今回は、OLTP相当の処理を想定した小さなトランザクションを多発させるケースと、DWHにおけるマイクロバッチ程度の規模を想定したまとまった更新単位のトランザクションのケースで動作検証を行いました。
OLTP(小さなトランザクションの多発)での効果
このケースでは、全国チェーンの小売店におけるPOS売上データのリアルタイム集計基盤を想定して検証しました。
マスター表「SALES_MASTER」は、各レジで発生した売上明細(店舗ID、レジ端末ID、金額など)をINSERTするためのトランザクション・テーブルです。
SQL> create TABLE SALES_MASTER (
2 ID number primary key,
3 BRANCH_ID number,
4 TERMINAL_ID number,
5 AMOUNT number,
6 SALES_DATE date
7 );
表が作成されました。
このテーブルに対して、リアルタイム集計結果を保持するサマリー・テーブルとしてMViewを作成します。
店舗(BRANCH_ID)× 端末(TERMINAL_ID)ごとの合計金額(SUM)と件数(COUNT)を常に最新状態で保持することが狙いです。
まずは、MView「SALES_SUMMARY_MV」を従来の同時リフレッシュが無効なON COMMIT MViewとして作成します。
SQL> create MATERIALIZED VIEW LOG on SALES_MASTER
2 with ROWID,
3 SEQUENCE (BRANCH_ID, TERMINAL_ID, AMOUNT, SALES_DATE),
4 COMMIT SCN
5 including new values;
マテリアライズド・ビュー・ログが作成されました。
SQL> create MATERIALIZED VIEW SALES_SUMMARY_MV
2 build immediate
3 REFRESH FAST ON COMMIT
4 as
5 select BRANCH_ID, TERMINAL_ID, count(*) as CNT, sum(AMOUNT) as TOTAL_AMOUNT
6 from SALES_MASTER
7 group by BRANCH_ID, TERMINAL_ID;
マテリアライズド・ビューが作成されました。
同時リフレッシュが無効な場合
OLTP相当の処理をイメージして、次のような処理を端末ごとのトランザクションと見立てて実行します。
[oracle@Lin96 tmp]$ cat ./oltp_test.sql
set TIMING ON
declare
V_SID number;
V_BASE_ID number;
V_BRANCH_ID number;
begin
-- 自身のSIDを後続のINSERTで「TERMINAL_ID(レジ番号)」として扱う
select SYS_CONTEXT('USERENV', 'SID') into V_SID from DUAL;
V_BASE_ID := V_SID * 10000000;
-- 1,000回の送信(COMMIT)ループ
for i in 1..1000 LOOP
-- 店舗IDは 1~10 の間でランダムに決定
V_BRANCH_ID := mod(i, 10) + 1;
-- 取引明細を送信する
insert into SALES_MASTER (ID, BRANCH_ID, TERMINAL_ID, AMOUNT, SALES_DATE)
values (
V_BASE_ID + (i * 1000),
V_BRANCH_ID,
V_SID,
1000,
SYSDATE
);
COMMIT;
end LOOP;
end;
/
exit;
そして、上記の処理を並行して10セッションから実行させます。
これにより、全国の店舗からランダムに取り引きの記録が登録される状況を再現します。
[oracle@Lin96 tmp]$ cat ./run_oltp.sh
#!/bin/bash
echo "OLTP Test Started: $(date)"
# 10セッション同時にキック
for i in {1..10}
do
sqlplus -s user01/pass@Lin96:1521/orcl_pdb @oltp_test.sql &
done
wait
echo "OLTP Test Ended: $(date)"
それでは、早速ワークロードを実行してみましょう。
[oracle@Lin96 tmp]$ ./run_oltp.sh
OLTP Test Started: 2026年 3月 2日 月曜日 11:21:53 JST
PL/SQLプロシージャが正常に完了しました。
経過: 00:03:42.87
PL/SQLプロシージャが正常に完了しました。
経過: 00:03:43.17
PL/SQLプロシージャが正常に完了しました。
経過: 00:03:42.95
PL/SQLプロシージャが正常に完了しました。
経過: 00:03:43.22
PL/SQLプロシージャが正常に完了しました。
経過: 00:03:42.75
PL/SQLプロシージャが正常に完了しました。
経過: 00:03:42.77
PL/SQLプロシージャが正常に完了しました。
経過: 00:03:43.14
PL/SQLプロシージャが正常に完了しました。
経過: 00:03:43.47
PL/SQLプロシージャが正常に完了しました。
経過: 00:03:42.97
PL/SQLプロシージャが正常に完了しました。
経過: 00:03:43.06
OLTP Test Ended: 2026年 3月 2日 月曜日 11:25:38 JST
それぞれのセッションにおいて、1,000ループ完了までにおおよそ3分40秒程度(シェルスクリプトの完了までに3分45秒)要しました。
この処理の間、Oracle内部ではリフレッシュ処理を直列化するための待機イベントである「enq: JI - contention」が発生していました。
「enq: JI - contention」のパラメータ(P1列およびその16進数のP1RAW列)から、JI enqueueを排他モードで獲得することで、リフレッシュを直列化しMViewを保護していることが見受けられます。
SQL> select EVENT, count(*) from V$SESSION
2 where STATUS='ACTIVE'
3 and EVENT in (select NAME from V$EVENT_NAME where WAIT_CLASS != 'Idle')
4 group by EVENT order by 2 desc;
EVENT COUNT(*)
--------------------------------------------- ----------
enq: JI - contention 10
log file parallel write 1
SQL> select SID, EVENT, P1, P1RAW from V$SESSION
2 where EVENT like 'enq%' and STATE = 'WAITING';
SID EVENT P1 P1RAW
---------- --------------------------------------------- ---------- ----------------
6 enq: JI - contention 1246298118 000000004A490006
17 enq: JI - contention 1246298118 000000004A490006
135 enq: JI - contention 1246298118 000000004A490006
389 enq: JI - contention 1246298118 000000004A490006
504 enq: JI - contention 1246298118 000000004A490006
510 enq: JI - contention 1246298118 000000004A490006
623 enq: JI - contention 1246298118 000000004A490006
752 enq: JI - contention 1246298118 000000004A490006
876 enq: JI - contention 1246298118 000000004A490006
9行が選択されました。
次の検証では、同時リフレッシュを有効にすることでどれだけスループットが改善するか、また「enq: JI - contention」が抑制されるかを観察してみたいと思います。
同時リフレッシュが有効な場合
まず作成したMView「SALES_SUMMARY_MV」の同時リフレッシュを有効化します。
有効化するには「ALTER MATERIALIZED VIEW … ENABLE CONCURRENT REFRESH」コマンドを実行します。
また、MViewの作成タイミングに「CREATE MATERIALIZED VIEW」コマンドで同時リフレッシュを有効化することも可能です。
SQL> alter materialized view SALES_SUMMARY_MV ENABLE CONCURRENT REFRESH;
マテリアライズド・ビューが変更されました。
SQL> select MVIEW_NAME,REFRESH_METHOD,REFRESH_MODE,CONCURRENT_REFRESH_ENABLED
2 from USER_MVIEWS;
MVIEW_NAME REFRESH_METHOD REFRESH_MODE CONCURRENT_REFRESH_ENABLED
------------------------- ------------------------ --------------------------- ------------------------------
SALES_SUMMARY_MV FAST COMMIT Y
それでは、改めて同時リフレッシュ無効時と同じワークロードを、有効化後にも実行してみましょう。
[oracle@Lin96 tmp]$ ./run_oltp.sh
OLTP Test Started: 2026年 3月 2日 月曜日 11:45:22 JST
PL/SQLプロシージャが正常に完了しました。
経過: 00:02:06.21
PL/SQLプロシージャが正常に完了しました。
経過: 00:02:07.12
PL/SQLプロシージャが正常に完了しました。
経過: 00:02:07.30
PL/SQLプロシージャが正常に完了しました。
経過: 00:02:07.36
PL/SQLプロシージャが正常に完了しました。
経過: 00:02:07.91
PL/SQLプロシージャが正常に完了しました。
経過: 00:02:07.94
PL/SQLプロシージャが正常に完了しました。
経過: 00:02:07.92
PL/SQLプロシージャが正常に完了しました。
経過: 00:02:08.25
PL/SQLプロシージャが正常に完了しました。
経過: 00:02:08.14
PL/SQLプロシージャが正常に完了しました。
経過: 00:02:08.63
OLTP Test Ended: 2026年 3月 2日 月曜日 11:47:31 JST
1セッションあたり1,000ループ完了するまでの時間が、2分10秒ほど(シェルスクリプトの完了までに2分9秒)にまで短縮されました。
| 同時リフレッシュが無効 |
3分45秒 |
| 同時リフレッシュが有効 |
2分9秒 |
待機イベントの傾向にも変化があり、「enq: JI - contention」とは別のリソース保護でエンキュー獲得待ちが発生するタイミングがあるものの、ワークロード全体としては3分45秒 → 2分9秒と1分30秒以上のスループット改善が確認できました。
SQL> select EVENT, count(*) from V$SESSION
2 where STATUS='ACTIVE'
3 and EVENT in (select NAME from V$EVENT_NAME where WAIT_CLASS != 'Idle')
4 group by EVENT order by 2 desc;
EVENT COUNT(*)
--------------------------------------------- ----------
library cache lock 6
enq: JI - MV setup 2
library cache: mutex X 2
log file parallel write 1
SQL> select SID, EVENT, P1, P1RAW from V$SESSION
2 where EVENT like 'enq%' and STATE = 'WAITING';
SID EVENT P1 P1RAW
---------- --------------------------------------------- ---------- ----------------
1 enq: JI - MV setup 1246298115 000000004A490003
6 enq: JI - MV setup 1246298116 000000004A490004
138 enq: JI - MV setup 1246298115 000000004A490003
264 enq: JI - MV setup 1246298115 000000004A490003
383 enq: JI - MV setup 1246298116 000000004A490004
510 enq: JI - MV setup 1246298116 000000004A490004
628 enq: JI - MV setup 1246298115 000000004A490003
750 enq: JI - MV setup 1246298115 000000004A490003
876 enq: JI - MV setup 1246298116 000000004A490004
9行が選択されました。
「enq : JI - MV setup」では、JI enqueueを行排他および共有モードで獲得しています。
ここから、同時リフレッシュが有効であれば「enq : JI - contention」のように表ごと排他ロックしないため、複数セッションからのリフレッシュを実現することができているように見受けられました。
DWH/マイクロバッチ(まとまった更新単位のトランザクション)の場合
次に、全国のスマートメーターから数分おきに届く電力データの、並列マイクロバッチ処理を想定した検証を行います。
各地域ごとの集約データを並列でマスター表(ファクト表)「SMART_METER_LOGS」に溜め込みます。
SQL> create TABLE SMART_METER_LOGS (
2 LOG_ID number primary key,
3 AREA_CODE number,
4 METER_ID number,
5 KWH_VALUE number,
6 LOG_TIMESTAMP date
7 );
表が作成されました。
このファクト表から、例えば電力ひっ迫アラートなどの監視システムが参照するサマリー表としてMViewを作成します。
地域(AREA_CODE)ごとの総電力消費量(SUM)を計算することで、この監視システムを実現します。
まずは、MView「AREA_POWER_SUMMARY_MV」を従来の同時リフレッシュが無効なON COMMIT MViewとして作成します。
SQL> create MATERIALIZED VIEW LOG on SMART_METER_LOGS
2 with ROWID,
3 SEQUENCE (AREA_CODE, KWH_VALUE, LOG_TIMESTAMP),
4 COMMIT SCN
5 including new values;
マテリアライズド・ビュー・ログが作成されました。
SQL> create MATERIALIZED VIEW AREA_POWER_SUMMARY_MV
2 build immediate
3 REFRESH FAST ON COMMIT
4 as
5 select AREA_CODE, count(*) as LOG_COUNT, sum(KWH_VALUE) as TOTAL_KWH
6 from SMART_METER_LOGS
7 group by AREA_CODE;
マテリアライズド・ビューが作成されました。
同時リフレッシュが無効な場合
地域内のスマートメーター情報を集約した、まとまったデータの登録をイメージして、OLTPのケースよりも大きい次のようなトランザクションを実行します。
[oracle@Lin96 tmp]$ cat ./microbatch_test.sql
set TIMING ON
declare
V_SID number;
V_BASE_ID NUMBER;
V_AREA_CODE NUMBER;
begin
-- 自身のセッションIDを取得
select SYS_CONTEXT('USERENV', 'SID') into V_SID from DUAL;
-- セッションIDをAREA_CODEとして扱う
V_AREA_CODE := V_SID;
V_BASE_ID := V_SID * 10000000;
-- 10回のマイクロバッチ (1回あたり5万件、計50万件/セッション)
for i in 1..10 LOOP
-- IoTデータを一括生成&INSERT
insert into SMART_METER_LOGS (LOG_ID, AREA_CODE, METER_ID, KWH_VALUE, LOG_TIMESTAMP)
select
V_BASE_ID + (i * 1000000) + LEVEL,
V_AREA_CODE,
mod(LEVEL, 1000),
round(DBMS_RANDOM.VALUE(1, 10), 2),
SYSDATE
from DUAL
connect by LEVEL <= 50000;
COMMIT;
end LOOP;
end;
/
exit;
この処理を全国10か所の地域からファクト表へとロードする状況を再現するために、10セッションから並行して処理を実行します。
[oracle@Lin96 tmp]$ cat ./run_microbatch.sh
#!/bin/bash
echo "Micro-batch Test Started: $(date)"
# 10か所の地域集約データを同時ロード
for i in {1..10}
do
sqlplus -s user01/pass@Lin96:1521/orcl_pdb @microbatch_test.sql &
done
wait
echo "Micro-batch Test Ended: $(date)"
こちらのワークロードを実行してみましょう。
[oracle@Lin96 tmp]$ ./run_microbatch.sh
Micro-batch Test Started: 2026年 3月 2日 月曜日 15:31:38 JST
PL/SQLプロシージャが正常に完了しました。
経過: 00:07:48.96
PL/SQLプロシージャが正常に完了しました。
経過: 00:08:01.51
PL/SQLプロシージャが正常に完了しました。
経過: 00:08:03.14
PL/SQLプロシージャが正常に完了しました。
経過: 00:08:04.85
PL/SQLプロシージャが正常に完了しました。
経過: 00:08:06.31
PL/SQLプロシージャが正常に完了しました。
経過: 00:08:07.09
PL/SQLプロシージャが正常に完了しました。
経過: 00:08:07.49
PL/SQLプロシージャが正常に完了しました。
経過: 00:08:08.83
PL/SQLプロシージャが正常に完了しました。
経過: 00:08:09.36
PL/SQLプロシージャが正常に完了しました。
経過: 00:08:10.05
Micro-batch Test Ended: 2026年 3月 2日 月曜日 15:39:49 JST
おおよそ8分前後で各セッションの処理が完了し、シェルスクリプトは8分11秒で完了しました。
今回のワークロードについては、SQLファイルmicrobatch_test.sqlのとおり、大規模な更新処理が伴うため、MViewのリフレッシュだけではなくI/Oにかかる処理時間も大部分を占めるかと思います。
ただし、次のようにリフレッシュのフェーズにおいてはやはり「enq: JI - contention」がボトルネックの一つとなってしまうようです。
SQL> select EVENT, count(*) from V$SESSION
2 where STATUS='ACTIVE'
3 and EVENT in (select NAME from V$EVENT_NAME where WAIT_CLASS != 'Idle')
4 group by EVENT order by 2 desc;
EVENT COUNT(*)
--------------------------------------------- ----------
enq: JI - contention 8
log buffer space 1
buffer busy waits 1
log file parallel write 1
同時リフレッシュが有効な場合
それでは、同時リフレッシュを有効化した場合にマイクロバッチ処理のパフォーマンスはどのように変化するでしょうか。
まずは同時リフレッシュを有効化します。
SQL> alter materialized view AREA_POWER_SUMMARY_MV ENABLE CONCURRENT REFRESH;
マテリアライズド・ビューが変更されました。
SQL> select MVIEW_NAME,REFRESH_METHOD,REFRESH_MODE,CONCURRENT_REFRESH_ENABLED
2 from USER_MVIEWS;
MVIEW_NAME REFRESH_METHOD REFRESH_MODE CONCURRENT_REFRESH_ENABLED
------------------------- ------------------------ --------------------------- ------------------------------
AREA_POWER_SUMMARY_MV FAST COMMIT Y 1
同時リフレッシュを有効化したところで、同じワークロードを再実行します。
[oracle@Lin96 tmp]$ ./run_microbatch.sh
Micro-batch Test Started: 2026年 3月 2日 月曜日 15:40:21 JST
PL/SQLプロシージャが正常に完了しました。
経過: 00:05:32.09
PL/SQLプロシージャが正常に完了しました。
経過: 00:05:38.71
PL/SQLプロシージャが正常に完了しました。
経過: 00:05:44.31
PL/SQLプロシージャが正常に完了しました。
経過: 00:05:55.78
PL/SQLプロシージャが正常に完了しました。
経過: 00:05:56.88
PL/SQLプロシージャが正常に完了しました。
経過: 00:06:03.58
PL/SQLプロシージャが正常に完了しました。
経過: 00:06:04.62
PL/SQLプロシージャが正常に完了しました。
経過: 00:06:04.83
PL/SQLプロシージャが正常に完了しました。
経過: 00:06:11.08
PL/SQLプロシージャが正常に完了しました。
経過: 00:06:11.74
Micro-batch Test Ended: 2026年 3月 2日 月曜日 15:46:34 JST
なんと5分半から6分程度で処理が完了するセッションもあり、シェルスクリプト全体としては6分13秒の処理時間となりました。
同時リフレッシュの無効時の8分11秒と比較して1分58秒のスループット改善という、実運用上も十分に意味のある結果が得られました。
| 同時リフレッシュが無効 |
8分11秒 |
| 同時リフレッシュが有効 |
6分13秒 |
同時リフレッシュ有効時の「row cache lock」
一方で、マイクロバッチ規模の大きなトランザクションでは、同時リフレッシュ有効時に「row cache lock」をはじめとした待機イベントが目立つ傾向も確認できました。
SQL> select EVENT, count(*) from V$SESSION
2 where STATUS='ACTIVE'
3 and EVENT in (select NAME from V$EVENT_NAME where WAIT_CLASS != 'Idle')
4 group by EVENT order by 2 desc;
EVENT COUNT(*)
--------------------------------------------- ----------
enq: JI - MV setup 4
row cache lock 4
latch: undo global data 1
log buffer space 1
log file parallel write 1
db file async I/O submit 1
6行が選択されました。
同時リフレッシュが無効な場合は、「enq: JI - contention」によってリフレッシュ処理そのものが直列化されました。
対して有効な場合には、Oracle AI Database内部のメタデータ更新時における排他制御(ディクショナリキャッシュの保護)フェーズで「row cache lock」などの待機が顕在化するかたちで、ボトルネックの位置が移動していると言えます。
こういった検証結果から、「どのような環境」「どのようなワークロード」においても同時リフレッシュの活用が最適な選択肢となるかは断言が難しいものと考えられます。
機能の採択に際しては、やはり本番相当のワークロードによるテストは必須と感じる検証結果となりました。
まとめ
26aiの同時リフレッシュは、従来「enq: JI - contention」によって直列化されていたON COMMIT MViewのリフレッシュを並列化し、OLTP/DWHのいずれにおいてもスループット改善という明確なメリットをもたらします。
その一方で、今回の検証では大規模トランザクション時に「row cache lock」など別種のボトルネックが現れるケースも確認されました。
従って、本機能は「常に有効化しておけばよい」というより、システム特性やワークロードに応じて使いどころを見極めることで、より効果を引き出せるタイプの機能と言えます。
読者のみなさまが運用・保守するシステム特性が、本記事で取り上げたOLTP/DWHのどのパターンに近いかを意識しつつ、自システムのワークロードで挙動と効果をぜひ検証してみてください。
執筆者情報
アシストテックフェイス
2016年アシスト北海道へ入社後、Oracle Databaseのサポート業務に従事。現在はサポートチームのリーダーとしてメンバーのバックフォローの傍ら、Oracle Databaseの技術検証に勤しんでいる。
...show more