今回は、Oracle AI Database In-Memory を実際に有効化し、どのくらいデータが圧縮されるのか、クエリ性能がどのくらい向上するのかを、実測値ベースで確認してみます。
特に、Disk 読み取り時と In-Memory 読み取り時で、実行時間と物理 I/O がどの程度変わるのかを比較しながら見てみてみます。

Oracle AI Database In-Memory は、Oracle AI Databaseにカラム機能を追加し、分析クエリを透過的に桁違いに高速化します。Oracle AI Database In-Memoryは、リアルタイム・データに基づく意思決定を可能にし、コストの削減、生産性の向上、競争力の強化を実現します。
-
IM列ストア
表、パーティションおよび個別の列のコピーが、高速スキャン向けに最適化された圧縮列形式で保持されます。各表またはビューのデータが行ではなく列ごとに格納されます。各列は、個別の行サブセットに分割されます。インメモリー圧縮単位(IMCU)というコンテナに、表セグメント内の行のサブセットのすべての列が格納されます。
-
SGAの記憶域
IM列ストアは、システム・グローバル領域(SGA)のオプション部分である、インメモリー領域に存在します。IM列ストアは行ベース・ストレージまたはデータベース・バッファ・キャッシュに代わるものではありませんが、それを補完します。データベースでは、データは、行ベースおよび列形式の両方でメモリー内に存在できるようになり、両方の長所が提供されます。IM列ストアにより、ディスク形式とは無関係な、トランザクションの一貫性がある、表データのコピーがさらに提供されます。
Oracle AI Database In-Memory には、次のような特徴があります。
-
デュアル形式アーキテクチャ
従来の行形式と新しいインメモリ列形式を同時に使用してテーブルを表現します。これにより、同じデータベース内の同じデータに対してトランザクション・データ処理と分析データ処理を実行できます。 -
スケールアウト
Oracle Real Application Clusters(RAC)全体にわたり、透過的なインメモリのスケールアウトを可能にします。単一ノードのメモリに制限されることなく、クラスタ内のあらゆるノードにわたり透過的にメモリにアクセスできるため、単一ノードよりも大きな列ストアを作成する機能を提供します。 -
高可用性
アクティブ・データ・ガード構成では、スタンバイ・データベース上にインメモリ列ストアを作成、入力、維持することができます。これにより、分析ワークロードのためにスタンバイ・データベースのCPUおよびメモリ・リソースをフルに活用できるとともに、プライマリ・データベースとその関連スタンバイ・データベース全体にわたり、インメモリ列ストアをフレキシブルに配置することができます。 -
インメモリ・フォルト・トレランス
Oracle Exadataクラスタのノード全体にわたりインメモリ・データを分散および複製するため、ノードの障害による速度低下を排除します。ノードに障害が発生した場合、クエリは生き残ったノード上のデータの複製を透過的に使用できます。 -
互換性
Oracle AI Database互換のアプリケーションにOracle AI Database In-Memoryを導入する場合、アプリケーションの変更は必要ありません。オラクルの広範な機能、データ型、APIはすべて透過的に機能し続けます。 -
自動インメモリ
インメモリ列ストアの内容を使用状況に応じて自動的に管理し、手作業を必要とせずにデータベース・メモリを最大限に活用します。 -
複数のデータ・ソースにわたるリアルタイムのレポート
外部データ・ソースの直接投入を可能にし、ユーザーがOracle AI Databaseの内部および外部のすべてのデータ・ソースに対して分析クエリを実行できるようにします。 -
Exadata Flash Cacheにおけるデータベース・インメモリ・フォーマット
Exadata Flash Cacheにおいてインメモリ・カラム型フォーマットをサポートします。
これにより、Exadataのお客様は、テラバイト規模の容量を持つExadata Flash Cacheにインメモリ・カラム型ストアを拡張し、これまで以上に大規模なデータフットプリントに対して分析クエリを実行できるようになります。

ということで、Oracle AI Database In-Memory の有無でどのくらいデータが圧縮され、クエリ速度が向上するか確認してみてみます。
■ 環境
今回は、Oracle Cloud の Oracle AI Database 26ai を使用し、次を参考に作成します。
ライセンスは、In-Memoryオプションある EE Extreme Performance を使用します。
■ In-Memoryパラメータ設定
1) In-Memory有効化
ALTER SYSTEM SET inmemory_size = 16G SCOPE=SPFILE;
SHUTDOWN IMMEDIATE;
STARTUP;
2) In-Memory有効化確認
SQL> SHOW PARAMETER inmemory
NAME TYPE VALUE
------------------------------------------- ----------- -------
inmemory_adg_enabled boolean TRUE
inmemory_automatic_level string OFF
inmemory_clause_default string
inmemory_deep_vectorization boolean TRUE
inmemory_expressions_usage string ENABLE
inmemory_force string DEFAULT
inmemory_graph_algorithm_execution string DEFAULT
inmemory_max_populate_servers integer 32
inmemory_optimized_arithmetic string DISABLE
inmemory_optimized_date string DISABLE
inmemory_prefer_xmem_memcompress string
inmemory_prefer_xmem_priority string
inmemory_query string ENABLE
inmemory_size big integer 16G
inmemory_trickle_repopulate_servers_percent integer 1
inmemory_virtual_columns string MANUAL
inmemory_xmem_size big integer 0
optimizer_inmemory_aware boolean TRUE
SQL> SELECT pool, alloc_bytes, used_bytes, populate_status
FROM v$inmemory_area
ORDER BY pool;
POOL ALLOC_BYTES USED_BYTES POPULATE_STATUS
________________ ____________ __________ _______________
1MB POOL 117440512000 0 DONE
1MB POOL 113760010240 0 DONE
1MB POOL 117440512000 0 DONE
64KB POOL 48766451712 0 DONE
64KB POOL 50314870784 0 DONE
64KB POOL 50314870784 0 DONE
■ スキーマ作成
1) 表領域作成
create bigfile tablespace imdemo
datafile 'IMDEMO.dbf' size 150g
autoextend on next 10g maxsize 1t
extent management local segment space management auto;
2) ユーザー作成
create user imdemo identified by "********"
default tablespace imdemo
temporary tablespace temp
quota unlimited on imdemo;
grant create session, create table, create procedure, create sequence to imdemo;
grant execute on sys.dbms_inmemory to imdemo;
-- 必要に応じて: grant create view, create materialized view, etc.
■ データ作成
今回は、分析クエリ向けの検証用として、ディメンション表とファクト表を作成します。
- ディメンション表は、カテゴリ・顧客・地域といった集計軸を持つ小規模な参照表
- ファクト表は、大量データを格納する売上明細表
という構成です。
ファクト表 FACT_SALES は月次レンジ・パーティションとし、直近 2 か月分のみを INMEMORY、それ以前を NO INMEMORY に設定します。これにより、メモリ使用量を抑えつつ、In-Memory の効果をわかりやすく確認できるようにします。
● テーブル一覧
・ディメンション表(小さい参照表、JOIN用)
DIM_PRODUCT(商品マスタ)
prod_id:商品ID(結合キー)
category:カテゴリ(集計軸)
用途:FACT_SALES.prod_id とJOINし、カテゴリ別集計を行う。
DIM_CUSTOMER(顧客マスタ)
cust_id:顧客ID(結合キー)
segment:顧客セグメント(集計軸)
signup_dt:登録日(任意)
用途:顧客軸の分析を増やしたい場合にJOINする(今回の基本SQLは region/product join)
DIM_REGION(地域マスタ)
region_id:地域ID(結合キー)
region_name:地域名
用途:FACT_SALES.region_id とJOINし、地域別集計を行う。
・ファクト表(大量データ、パーティション+In-Memoryの主役)
FACT_SALES(売上明細):月次レンジ・パーティション(例:P01〜P06)
sale_dt:日付(パーティションキー)
cust_id/prod_id/region_id:ディメンション結合キー
amount:売上(集計対象)
pad:行サイズを膨らませて「Diskは巨大、IMは圧縮」を作りやすくする列
1) imdemoユーザーログイン
conn imdemo/Password
2) パラメータ定義(サイズ調整用)設定
-- (IMDEMO)
-- ===== 調整パラメータ =====
define P_MONTHS = 6 -- パーティション月数(例: 6)
define P_ROWS_PER_MONTH = 5000000 -- 1パーティションあたり行数(例: 500万)
define P_PAD_LEN = 4000 -- 行サイズを膨らませるパディング長(例: 4000
define P_DEGREE = 8 -- PARALLEL度(環境に合わせて)
define P_START_DATE = '2025-01-01' -- 開始日(YYYY-MM-DD)
set serveroutput on
set timing on
alter session set nls_date_format = 'YYYY-MM-DD';
alter session set optimizer_dynamic_sampling = 0;
alter session set parallel_degree_policy = manual;
3) DDL: ディメンション+ファクト(レンジ月次パーティション)
fact_sales を月次パーティション
直近2か月分のみを INMEMORY とし、それ以前は NO INMEMORY とすることで、メモリ使用量を抑えつつ In-Memory の効果を確認します。
JOIN を含む集計クエリでも、結果セット自体は数十行に収まるようにしています。
-- クリーンアップ(再実行用)
begin
execute immediate 'drop table fact_sales purge';
exception when others then null;
end;
/
begin execute immediate 'drop table dim_product purge'; exception when others then null; end;
/
begin execute immediate 'drop table dim_customer purge'; exception when others then null; end;
/
begin execute immediate 'drop table dim_region purge'; exception when others then null; end;
/
begin execute immediate 'drop sequence seq_sale_id'; exception when others then null; end;
/
create sequence seq_sale_id cache 100000;
-- ディメンション
create table dim_region (
region_id number primary key,
region_name varchar2(30)
);
create table dim_product (
prod_id number primary key,
category varchar2(30),
brand varchar2(30)
);
create table dim_customer (
cust_id number primary key,
segment varchar2(20),
signup_dt date
);
-- ファクト(月次パーティション:明示的に6パーティション作成)
-- 直近2パーティションのみ INMEMORY(後でALTERで制御)
create table fact_sales (
sale_id number not null,
sale_dt date not null,
cust_id number not null,
prod_id number not null,
region_id number not null,
qty number,
amount number(12,2),
channel varchar2(10),
pad varchar2(4000)
)
partition by range (sale_dt)
(
partition p01 values less than (date '2025-02-01'),
partition p02 values less than (date '2025-03-01'),
partition p03 values less than (date '2025-04-01'),
partition p04 values less than (date '2025-05-01'),
partition p05 values less than (date '2025-06-01'),
partition p06 values less than (date '2025-07-01')
)
nologging
parallel &P_DEGREE
-- テーブル定義としてINMEMORYを付けてもよいですが、
-- 今回はパーティション単位で ON/OFF するのでテーブル自体は付けない(後でALTER)。
;
-- 参照・結合を安定させるために最低限の索引(ロウストア側)
drop index ix_fact_sales_dt;
drop index ix_fact_sales_prod;
drop index ix_fact_sales_cust;
drop index ix_fact_sales_region;
create index ix_fact_sales_dt on fact_sales(sale_dt) local nologging parallel &P_DEGREE;
create index ix_fact_sales_prod on fact_sales(prod_id) local nologging parallel &P_DEGREE;
create index ix_fact_sales_cust on fact_sales(cust_id) local nologging parallel &P_DEGREE;
create index ix_fact_sales_region on fact_sales(region_id) local nologging parallel &P_DEGREE;
■ データ生成(パラメータでサイズ調整)
PAD_LEN=4000, 500万行/月 x2パーティション で作成して In-Memory=OFF の SQL Elapsed が十分に時間かかるか確認し、必要に応じて サイズを調整します。
1) ディメンション投入
-- 地域100
insert /*+ append */ into dim_region
select level as region_id,
'REGION_'||to_char(level,'fm000') as region_name
from dual connect by level <= 100;
-- 製品5000(カテゴリ/ブランドに偏りを持たせ圧縮・集計が効きやすい)
insert /*+ append */ into dim_product
select level as prod_id,
'CAT_'||to_char(mod(level,50)+1,'fm00') as category,
'BRAND_'||to_char(mod(level,200)+1,'fm000') as brand
from dual connect by level <= 5000;
-- 顧客100000
insert /*+ append */ into dim_customer
select level as cust_id,
case mod(level,4)
when 0 then 'ENTERPRISE'
when 1 then 'SMB'
when 2 then 'CONSUMER'
else 'PUBLIC'
end as segment,
date'2020-01-01' + mod(level, 365*4) as signup_dt
from dual connect by level <= 100000;
commit;
2) パーティション P05/P06 に各500万行ロード
declare
v_rows number := &P_ROWS_PER_MONTH; -- 500万
v_pad number := &P_PAD_LEN; -- 4000
v_start_dt date := date '2025-01-01';
v_sql clob;
begin
v_sql := q'[
insert /*+ append parallel(fact_sales, &P_DEGREE) */ into fact_sales
select
seq_sale_id.nextval as sale_id,
add_months(:p_start_dt, :p_m) + mod(level-1, 28) as sale_dt,
mod(level, 100000) + 1 as cust_id,
mod(level, 5000) + 1 as prod_id,
mod(level, 100) + 1 as region_id,
mod(level, 10) + 1 as qty,
round(mod(level,1000) + dbms_random.value, 2) as amount,
case mod(level,3) when 0 then 'WEB' when 1 then 'STORE' else 'DIRECT' end as channel,
rpad('x', :p_pad, 'x') as pad
from dual
connect by level <= :p_rows
]';
-- 0起点: 4=2025-05, 5=2025-06
for m in 4 .. 5 loop
execute immediate v_sql using v_start_dt, m, v_pad, v_rows;
commit;
end loop;
end;
/
3) 統計情報取得(重要)
exec dbms_stats.gather_table_stats(user,'DIM_REGION', cascade=>true);
exec dbms_stats.gather_table_stats(user,'DIM_PRODUCT', cascade=>true);
exec dbms_stats.gather_table_stats(user,'DIM_CUSTOMER', cascade=>true);
exec dbms_stats.gather_table_stats(user,'FACT_SALES', cascade=>true);
3) データロード確認
fact_sales表の partition(p05) と partition(p06)のデータ量を確認
SQL> select count(*) from fact_sales partition(p05);
COUNT(*)
________
5000000
SQL> select count(*) from fact_sales partition(p06);
COUNT(*)
________
5000000
■ In-Memory 設定
(直近2パーティションだけON)+ Populate
1) (直近2パーティションだけON)+ Populate
直近2か月(例:p05,p06)だけ IM に載せ、他はNOにします。圧縮方式はまず QUERY HIGH で実施
-- 古い分はIMに載せない(IM 48Gに収めるため)
alter table fact_sales modify partition p01 no inmemory;
alter table fact_sales modify partition p02 no inmemory;
alter table fact_sales modify partition p03 no inmemory;
alter table fact_sales modify partition p04 no inmemory;
-- 直近2パーティションのみINMEMORY(デモで効かせる部分)
alter table fact_sales modify partition p05 inmemory memcompress for query high;
alter table fact_sales modify partition p06 inmemory memcompress for query high;
-- ディメンションも小さいのでIM化(JOIN時の効きやすさを上げる)
alter table dim_product inmemory memcompress for query low;
alter table dim_customer inmemory memcompress for query low;
alter table dim_region inmemory memcompress for query low;
2) Populate(明示的に載せる)
begin
sys.dbms_inmemory.populate(user,'DIM_PRODUCT');
sys.dbms_inmemory.populate(user,'DIM_CUSTOMER');
sys.dbms_inmemory.populate(user,'DIM_REGION');
sys.dbms_inmemory.populate(user,'FACT_SALES');
end;
/
■ 圧縮率(Disk vs IM)確認
1) IM領域使用状況
select * from v$inmemory_area;
POOL ALLOC_BYTES USED_BYTES POPULATE_STATUS CON_ID
________________ ____________ __________ _______________ ______
1MB POOL 117440512000 795869184 DONE 3
64KB POOL 50314870784 149422080 DONE 3
IM POOL METADATA 16777216 16777216 DONE 3
2) セグメント/パーティションごとのディスクサイズとIMサイズ
column segment_name format a20
column partition_name format a10
select
owner, segment_name, partition_name,
round(bytes/1024/1024/1024, 2) as disk_gb,
round(inmemory_size/1024/1024/1024, 2) as im_gb,
case when inmemory_size > 0 then round(bytes / inmemory_size, 2) end as disk_to_im_ratio
from v$im_segments
where owner = user
order by segment_name, partition_name;
OWNER SEGMENT_NAME PARTITION_NAME DISK_GB IM_GB DISK_TO_IM_RATIO
______ ____________ ______________ _______ _____ ________________
IMDEMO DIM_CUSTOMER 0 0 0.87
IMDEMO DIM_PRODUCT 0 0 0.14
IMDEMO DIM_REGION 0 0 0.04
IMDEMO FACT_SALES P05 38.15 0.44 87.23
IMDEMO FACT_SALES P06 38.15 0.44 87.23
3) どのパーティションが In-Memory か確認(メタデータ)
column partition_name format a10
column inmemory format a10
select partition_name, inmemory, inmemory_compression
from user_tab_partitions
where table_name = 'FACT_SALES'
order by partition_position;
PARTITION_NAME INMEMORY INMEMORY_COMPRESSION
______________ ________ ____________________
P01 DISABLED
P02 DISABLED
P03 DISABLED
P04 DISABLED
P05 ENABLED FOR QUERY HIGH
P06 ENABLED FOR QUERY HIGH
6 rows selected.
■ デモ
● Step 1. P05/P06がIMに載っている(圧縮されている)ことを確認
1) In-Memory化した Diskサイズ と In-Memoryサイズを確認
column segment_name format a20
column partition_name format a10
select
owner, segment_name, partition_name,
round(bytes/1024/1024/1024, 2) as disk_gb,
round(inmemory_size/1024/1024/1024, 2) as im_gb,
round(bytes/nullif(inmemory_size,0), 2) as disk_to_im_ratio
from v$im_segments
where owner = 'IMDEMO'
and segment_name = 'FACT_SALES'
and partition_name in ('P05','P06')
order by partition_name;
OWNER SEGMENT_NAME PARTITION_NAME DISK_GB IM_GB DISK_TO_IM_RATIO
______ ____________ ______________ _______ _____ ________________
IMDEMO FACT_SALES P05 38.15 0.44 87.23
IMDEMO FACT_SALES P06 38.15 0.44 87.23
select * from v$inmemory_area;
POOL ALLOC_BYTES USED_BYTES POPULATE_STATUS CON_ID
________________ ____________ __________ _______________ ______
1MB POOL 117440512000 795869184 DONE 3
64KB POOL 50314870784 149422080 DONE 3
IM POOL METADATA 16777216 16777216 DONE 3
● Step 2. 計測の共通設定
set timing on
set lines 200 pages 200
alter session set statistics_level = all;
alter session set result_cache_mode = manual;
● Step 3. IM OFF(ディスク読み)を “コールド” で測定
1) SYSで buffer cache flush
conn / as sysdba
alter system flush buffer_cache;
2) imdemoユーザーで In-Memory=OFF で SQL実行
統計差分のスナップショット→実行→差分
conn imdemo/password
alter session set inmemory_query = disable;
var prb_before number
var slr_before number
begin
select
max(case when n.name = 'physical read bytes' then s.value end),
max(case when n.name = 'session logical reads' then s.value end)
into :prb_before, :slr_before
from v$sesstat s join v$statname n on n.statistic# = s.statistic#
where s.sid = sys_context('USERENV','SID')
and n.name in ('physical read bytes','session logical reads');
end;
/
-- デモSQL(結果は30行)
select /*+ gather_plan_statistics
parallel(f 8) full(f)
parallel(p 8) full(p)
parallel(r 8) full(r)
no_result_cache
*/
r.region_name,
p.category,
sum(f.amount) as sum_amount
from fact_sales f
join dim_region r on r.region_id = f.region_id
join dim_product p on p.prod_id = f.prod_id
where f.sale_dt >= date'2025-05-01'
and f.sale_dt < date'2025-07-01'
group by r.region_name, p.category
order by sum_amount desc
fetch first 30 rows only;
select * from table(dbms_xplan.display_cursor(null,null,'ALLSTATS LAST +IOSTATS'));
var prb_after number
var slr_after number
begin
select
max(case when n.name = 'physical read bytes' then s.value end),
max(case when n.name = 'session logical reads' then s.value end)
into :prb_after, :slr_after
from v$sesstat s join v$statname n on n.statistic# = s.statistic#
where s.sid = sys_context('USERENV','SID')
and n.name in ('physical read bytes','session logical reads');
end;
/
select
(:prb_after - :prb_before) as delta_physical_read_bytes,
(:slr_after - :slr_before) as delta_session_logical_reads
from dual;
SQL> alter session set inmemory_query = disable;
Session altered.
Elapsed: 00:00:00.002
SQL>
SQL> var prb_before number
SQL> var slr_before number
SQL> begin
2 select
3 max(case when n.name = 'physical read bytes' then s.value end),
4 max(case when n.name = 'session logical reads' then s.value end)
5 into :prb_before, :slr_before
6 from v$sesstat s join v$statname n on n.statistic# = s.statistic#
7 where s.sid = sys_context('USERENV','SID')
8 and n.name in ('physical read bytes','session logical reads');
9 end;
10* /
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.025
SQL>
SQL> -- デモSQL(結果は30行)
SQL> select /*+ gather_plan_statistics
2 parallel(f 8) full(f)
3 parallel(p 8) full(p)
4 parallel(r 8) full(r)
5 no_result_cache
6 */
7 r.region_name,
8 p.category,
9 sum(f.amount) as sum_amount
10 from fact_sales f
11 join dim_region r on r.region_id = f.region_id
12 join dim_product p on p.prod_id = f.prod_id
13 where f.sale_dt >= date'2025-05-01'
14 and f.sale_dt < date'2025-07-01'
15 group by r.region_name, p.category
16 order by sum_amount desc
17* fetch first 30 rows only;
REGION_NAME CATEGORY SUM_AMOUNT
___________ ________ ___________
REGION_100 CAT_01 54950021.57
REGION_099 CAT_50 54850089.18
REGION_098 CAT_49 54749867.81
REGION_097 CAT_48 54650005.96
REGION_096 CAT_47 54550089.65
REGION_095 CAT_46 54450035.33
REGION_094 CAT_45 54349934.09
REGION_093 CAT_44 54249968.86
REGION_092 CAT_43 54150124.27
REGION_091 CAT_42 54050007.81
REGION_090 CAT_41 53949878.89
REGION_089 CAT_40 53849952.7
REGION_088 CAT_39 53749973.5
REGION_087 CAT_38 53650081.76
REGION_086 CAT_37 53549950.97
REGION_085 CAT_36 53449992.04
REGION_084 CAT_35 53350022.29
REGION_083 CAT_34 53249868.64
REGION_082 CAT_33 53150025.78
REGION_081 CAT_32 53050125.4
REGION_080 CAT_31 52950155.09
REGION_079 CAT_30 52849982.15
REGION_078 CAT_29 52750038.22
REGION_077 CAT_28 52650104.43
REGION_076 CAT_27 52549911.77
REGION_075 CAT_26 52450047.17
REGION_074 CAT_25 52349919.56
REGION_073 CAT_24 52250070.28
REGION_072 CAT_23 52150014.54
REGION_071 CAT_22 52049971.3
30 rows selected.
Elapsed: 00:02:41.706
SQL>
SQL> select * from table(dbms_xplan.display_cursor(null,null,'ALLSTATS LAST +IOSTATS'));
PLAN_TABLE_OUTPUT
_____________________________________________________________________________________________________________________________________________________________________
SQL_ID cdmv00926pnnx, child number 7
-------------------------------------
select /*+ gather_plan_statistics parallel(f 8) full(f)
parallel(p 8) full(p) parallel(r 8) full(r)
no_result_cache */ r.region_name, p.category,
sum(f.amount) as sum_amount from fact_sales f join dim_region r on
r.region_id = f.region_id join dim_product p on p.prod_id = f.prod_id
where f.sale_dt >= date'2025-05-01' and f.sale_dt < date'2025-07-01'
group by r.region_name, p.category order by sum_amount desc fetch first
30 rows only
Plan hash value: 403177439
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Reads | OMem | 1Mem | Used-Mem |
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 30 |00:02:41.66 | 31 | 4 | | | |
|* 1 | COUNT STOPKEY | | 1 | | 30 |00:02:41.66 | 31 | 4 | | | |
| 2 | TEMP TABLE TRANSFORMATION | | 1 | | 30 |00:02:41.66 | 31 | 4 | | | |
| 3 | LOAD AS SELECT (CURSOR DURATION MEMORY)| SYS_TEMP_0FD9D662C_8C9E4E | 1 | | 0 |00:00:00.02 | 7 | 2 | 1024 | 1024 | |
| 4 | PX COORDINATOR | | 1 | | 100 |00:00:00.01 | 5 | 0 | 73728 | 73728 | |
| 5 | PX SEND QC (RANDOM) | :TQ10001 | 0 | 100 | 0 |00:00:00.01 | 0 | 0 | | | |
| 6 | HASH GROUP BY | | 0 | 100 | 0 |00:00:00.01 | 0 | 0 | 1215K| 1215K| |
| 7 | PX RECEIVE | | 0 | 100 | 0 |00:00:00.01 | 0 | 0 | | | |
| 8 | PX SEND HASH | :TQ10000 | 0 | 100 | 0 |00:00:00.01 | 0 | 0 | | | |
| 9 | KEY VECTOR CREATE BUFFERED | :KV0000 | 0 | 100 | 0 |00:00:00.01 | 0 | 0 | 9216 | 9216 | |
| 10 | PX BLOCK ITERATOR | | 0 | 100 | 0 |00:00:00.01 | 0 | 0 | | | |
|* 11 | TABLE ACCESS FULL | DIM_REGION | 0 | 100 | 0 |00:00:00.01 | 0 | 0 | | | |
| 12 | LOAD AS SELECT (CURSOR DURATION MEMORY)| SYS_TEMP_0FD9D662D_8C9E4E | 1 | | 0 |00:00:00.01 | 5 | 0 | 1024 | 1024 | |
| 13 | PX COORDINATOR | | 1 | | 50 |00:00:00.01 | 5 | 0 | 73728 | 73728 | |
| 14 | PX SEND QC (RANDOM) | :TQ20001 | 0 | 50 | 0 |00:00:00.01 | 0 | 0 | | | |
| 15 | HASH GROUP BY | | 0 | 50 | 0 |00:00:00.01 | 0 | 0 | 1264K| 1264K| |
| 16 | PX RECEIVE | | 0 | 50 | 0 |00:00:00.01 | 0 | 0 | | | |
| 17 | PX SEND HASH | :TQ20000 | 0 | 50 | 0 |00:00:00.01 | 0 | 0 | | | |
| 18 | KEY VECTOR CREATE BUFFERED | :KV0001 | 0 | 50 | 0 |00:00:00.01 | 0 | 0 | 9216 | 9216 | |
| 19 | PX BLOCK ITERATOR | | 0 | 5000 | 0 |00:00:00.01 | 0 | 0 | | | |
|* 20 | TABLE ACCESS FULL | DIM_PRODUCT | 0 | 5000 | 0 |00:00:00.01 | 0 | 0 | | | |
| 21 | PX COORDINATOR | | 1 | | 30 |00:02:41.60 | 18 | 2 | 9216 | 9216 | 8192 (0)|
| 22 | PX SEND QC (ORDER) | :TQ30002 | 0 | 3536 | 0 |00:00:00.01 | 0 | 0 | | | |
| 23 | VIEW | | 0 | 3536 | 0 |00:00:00.01 | 0 | 0 | | | |
|* 24 | SORT ORDER BY STOPKEY | | 0 | 3536 | 0 |00:00:00.01 | 0 | 0 | 18432 | 18432 | 2048 (0)|
| 25 | PX RECEIVE | | 0 | 30 | 0 |00:00:00.01 | 0 | 0 | | | |
| 26 | PX SEND RANGE | :TQ30001 | 0 | 30 | 0 |00:00:00.01 | 0 | 0 | | | |
|* 27 | SORT ORDER BY STOPKEY | | 0 | 30 | 0 |00:00:00.01 | 0 | 0 | 18432 | 18432 | 2048 (0)|
|* 28 | HASH JOIN | | 0 | 3536 | 0 |00:00:00.01 | 0 | 0 | 1449K| 1449K| 1705K (0)|
| 29 | TABLE ACCESS FULL | SYS_TEMP_0FD9D662C_8C9E4E | 0 | 100 | 0 |00:00:00.01 | 0 | 0 | | | |
|* 30 | HASH JOIN | | 0 | 3536 | 0 |00:00:00.01 | 0 | 0 | 1573K| 1573K| 1715K (0)|
| 31 | TABLE ACCESS FULL | SYS_TEMP_0FD9D662D_8C9E4E | 0 | 50 | 0 |00:00:00.01 | 0 | 0 | | | |
| 32 | VIEW | VW_VT_E41ECF03 | 0 | 3536 | 0 |00:00:00.01 | 0 | 0 | | | |
| 33 | HASH GROUP BY | | 0 | 3536 | 0 |00:00:00.01 | 0 | 0 | 1107K| 1107K| 1197K (0)|
| 34 | PX RECEIVE | | 0 | 3536 | 0 |00:00:00.01 | 0 | 0 | | | |
| 35 | PX SEND HASH | :TQ30000 | 0 | 3536 | 0 |00:00:00.01 | 0 | 0 | | | |
| 36 | VECTOR GROUP BY | | 0 | 3536 | 0 |00:00:00.01 | 0 | 0 | 98304 | 98304 | 175K (0)|
| 37 | HASH GROUP BY | | | 3536 | | | | | 1647K| 1647K| |
| 38 | KEY VECTOR USE | :KV0000 | 0 | 9972K| 0 |00:00:00.01 | 0 | 0 | 299M| 5750K| |
| 39 | KEY VECTOR USE | :KV0001 | 0 | 9972K| 0 |00:00:00.01 | 0 | 0 | 256M| 5340K| |
| 40 | PX BLOCK ITERATOR | | 0 | 10M| 0 |00:00:00.01 | 0 | 0 | | | |
|* 41 | TABLE ACCESS FULL | FACT_SALES | 0 | 10M| 0 |00:00:00.01 | 0 | 0 | | | |
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(ROWNUM<=30)
11 - access(:Z>=:Z AND :Z<=:Z)
20 - access(:Z>=:Z AND :Z<=:Z)
24 - filter(ROWNUM<=30)
27 - filter(ROWNUM<=30)
28 - access("ITEM_8"=INTERNAL_FUNCTION("C0"))
30 - access("ITEM_9"=INTERNAL_FUNCTION("C0"))
41 - access(:Z>=:Z AND :Z<=:Z)
Note
-----
- dynamic statistics used: dynamic sampling (level=AUTO (SYSTEM))
- Degree of Parallelism is 8 because of table property
- vector transformation used for this statement
78 rows selected.
Elapsed: 00:00:00.051
SQL>
SQL> var prb_after number
SQL> var slr_after number
SQL> begin
2 select
3 max(case when n.name = 'physical read bytes' then s.value end),
4 max(case when n.name = 'session logical reads' then s.value end)
5 into :prb_after, :slr_after
6 from v$sesstat s join v$statname n on n.statistic# = s.statistic#
7 where s.sid = sys_context('USERENV','SID')
8 and n.name in ('physical read bytes','session logical reads');
9 end;
10* /
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.023
SQL>
SQL> select
2 (:prb_after - :prb_before) as delta_physical_read_bytes,
3 (:slr_after - :slr_before) as delta_session_logical_reads
4* from dual;
DELTA_PHYSICAL_READ_BYTES DELTA_SESSION_LOGICAL_READS
_________________________ ___________________________
81936908288 10003886
Elapsed: 00:00:00.004
SQL>
● Step 4. IM ON(メモリ読み)をコールドで測定
1) SYSで buffer cache flush
conn / as sysdba
alter system flush buffer_cache;
2) imdemoユーザーで In-Memory=ON で SQL実行
統計差分のスナップショット→実行→差分
conn imdemo/...
alter session set inmemory_query = enable;
var prb_before number
var slr_before number
begin
select
max(case when n.name = 'physical read bytes' then s.value end),
max(case when n.name = 'session logical reads' then s.value end)
into :prb_before, :slr_before
from v$sesstat s join v$statname n on n.statistic# = s.statistic#
where s.sid = sys_context('USERENV','SID')
and n.name in ('physical read bytes','session logical reads');
end;
/
-- デモSQL(結果は30行)∂
select /*+ gather_plan_statistics
parallel(f 8) full(f)
parallel(p 8) full(p)
parallel(r 8) full(r)
no_result_cache
*/
r.region_name,
p.category,
sum(f.amount) as sum_amount
from fact_sales f
join dim_region r on r.region_id = f.region_id
join dim_product p on p.prod_id = f.prod_id
where f.sale_dt >= date'2025-05-01'
and f.sale_dt < date'2025-07-01'
group by r.region_name, p.category
order by sum_amount desc
fetch first 30 rows only;
select * from table(dbms_xplan.display_cursor(null,null,'ALLSTATS LAST +IOSTATS'));
var prb_after number
var slr_after number
begin
select
max(case when n.name = 'physical read bytes' then s.value end),
max(case when n.name = 'session logical reads' then s.value end)
into :prb_after, :slr_after
from v$sesstat s join v$statname n on n.statistic# = s.statistic#
where s.sid = sys_context('USERENV','SID')
and n.name in ('physical read bytes','session logical reads');
end;
/
select
(:prb_after - :prb_before) as delta_physical_read_bytes,
(:slr_after - :slr_before) as delta_session_logical_reads
from dual;
SQL> alter session set inmemory_query = enable;
Session altered.
Elapsed: 00:00:00.002
SQL> var prb_before number
r - :slr_before) as delta_session_logical_reads
from dual;^[[201~SQL> var slr_before number
SQL> begin
2 select
3 max(case when n.name = 'physical read bytes' then s.value end),
4 max(case when n.name = 'session logical reads' then s.value end)
5 into :prb_before, :slr_before
6 from v$sesstat s join v$statname n on n.statistic# = s.statistic#
7 where s.sid = sys_context('USERENV','SID')
8 and n.name in ('physical read bytes','session logical reads');
9 end;
10* /
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.022
SQL>
SQL> -- デモSQL(結果は30行)
SQL> select /*+ gather_plan_statistics
2 parallel(f 8) full(f)
3 parallel(p 8) full(p)
4 parallel(r 8) full(r)
5 no_result_cache
6 */
7 r.region_name,
8 p.category,
9 sum(f.amount) as sum_amount
10 from fact_sales f
11 join dim_region r on r.region_id = f.region_id
12 join dim_product p on p.prod_id = f.prod_id
13 where f.sale_dt >= date'2025-05-01'
14 and f.sale_dt < date'2025-07-01'
15 group by r.region_name, p.category
16 order by sum_amount desc
17* fetch first 30 rows only;
REGION_NAME CATEGORY SUM_AMOUNT
___________ ________ ___________
REGION_100 CAT_01 54950021.57
REGION_099 CAT_50 54850089.18
REGION_098 CAT_49 54749867.81
REGION_097 CAT_48 54650005.96
REGION_096 CAT_47 54550089.65
REGION_095 CAT_46 54450035.33
REGION_094 CAT_45 54349934.09
REGION_093 CAT_44 54249968.86
REGION_092 CAT_43 54150124.27
REGION_091 CAT_42 54050007.81
REGION_090 CAT_41 53949878.89
REGION_089 CAT_40 53849952.7
REGION_088 CAT_39 53749973.5
REGION_087 CAT_38 53650081.76
REGION_086 CAT_37 53549950.97
REGION_085 CAT_36 53449992.04
REGION_084 CAT_35 53350022.29
REGION_083 CAT_34 53249868.64
REGION_082 CAT_33 53150025.78
REGION_081 CAT_32 53050125.4
REGION_080 CAT_31 52950155.09
REGION_079 CAT_30 52849982.15
REGION_078 CAT_29 52750038.22
REGION_077 CAT_28 52650104.43
REGION_076 CAT_27 52549911.77
REGION_075 CAT_26 52450047.17
REGION_074 CAT_25 52349919.56
REGION_073 CAT_24 52250070.28
REGION_072 CAT_23 52150014.54
REGION_071 CAT_22 52049971.3
30 rows selected.
Elapsed: 00:00:00.137
SQL>
SQL> select * from table(dbms_xplan.display_cursor(null,null,'ALLSTATS LAST +IOSTATS'));
PLAN_TABLE_OUTPUT
__________________________________________________________________________________________________________________________________________________________________
SQL_ID cdmv00926pnnx, child number 8
-------------------------------------
select /*+ gather_plan_statistics parallel(f 8) full(f)
parallel(p 8) full(p) parallel(r 8) full(r)
no_result_cache */ r.region_name, p.category,
sum(f.amount) as sum_amount from fact_sales f join dim_region r on
r.region_id = f.region_id join dim_product p on p.prod_id = f.prod_id
where f.sale_dt >= date'2025-05-01' and f.sale_dt < date'2025-07-01'
group by r.region_name, p.category order by sum_amount desc fetch first
30 rows only
Plan hash value: 3727651337
------------------------------------------------------------------------------------------------------------------------------------------------------------------
| Id | Operation | Name | Starts | E-Rows | A-Rows | A-Time | Buffers | Reads | OMem | 1Mem | Used-Mem |
------------------------------------------------------------------------------------------------------------------------------------------------------------------
| 0 | SELECT STATEMENT | | 1 | | 30 |00:00:00.09 | 44 | 2 | | | |
|* 1 | COUNT STOPKEY | | 1 | | 30 |00:00:00.09 | 44 | 2 | | | |
| 2 | PX COORDINATOR | | 1 | | 30 |00:00:00.09 | 44 | 2 | 9216 | 9216 | 8192 (0)|
| 3 | PX SEND QC (ORDER) | :TQ10006 | 0 | 100 | 0 |00:00:00.01 | 0 | 0 | | | |
| 4 | VIEW | | 0 | 100 | 0 |00:00:00.01 | 0 | 0 | | | |
|* 5 | SORT ORDER BY STOPKEY | | 0 | 100 | 0 |00:00:00.01 | 0 | 0 | 18432 | 18432 | 2048 (0)|
| 6 | PX RECEIVE | | 0 | 30 | 0 |00:00:00.01 | 0 | 0 | | | |
| 7 | PX SEND RANGE | :TQ10005 | 0 | 30 | 0 |00:00:00.01 | 0 | 0 | | | |
|* 8 | SORT ORDER BY STOPKEY | | 0 | 30 | 0 |00:00:00.01 | 0 | 0 | 18432 | 18432 | 2048 (0)|
| 9 | HASH GROUP BY | | 0 | 100 | 0 |00:00:00.01 | 0 | 0 | 1041K| 1041K| 1448K (0)|
| 10 | PX RECEIVE | | 0 | 100 | 0 |00:00:00.01 | 0 | 0 | | | |
| 11 | PX SEND HASH | :TQ10004 | 0 | 100 | 0 |00:00:00.01 | 0 | 0 | | | |
| 12 | HASH GROUP BY | | 0 | 100 | 0 |00:00:00.01 | 0 | 0 | 974K| 974K| |
|* 13 | HASH JOIN | | 0 | 100 | 0 |00:00:00.01 | 0 | 0 | 3666K| 1834K| 1371K (0)|
| 14 | JOIN FILTER CREATE | :BF0000 | 0 | 100 | 0 |00:00:00.01 | 0 | 0 | | | |
| 15 | PX RECEIVE | | 0 | 100 | 0 |00:00:00.01 | 0 | 0 | | | |
| 16 | PX SEND BROADCAST | :TQ10003 | 0 | 100 | 0 |00:00:00.01 | 0 | 0 | | | |
|* 17 | HASH JOIN BUFFERED | | 0 | 100 | 0 |00:00:00.01 | 0 | 0 | 3217K| 2681K| 4259K (0)|
| 18 | PX RECEIVE | | 0 | 100 | 0 |00:00:00.01 | 0 | 0 | | | |
| 19 | PX SEND HYBRID HASH | :TQ10001 | 0 | 100 | 0 |00:00:00.01 | 0 | 0 | | | |
| 20 | STATISTICS COLLECTOR | | 0 | | 0 |00:00:00.01 | 0 | 0 | | | |
| 21 | VIEW | VW_GBC_11 | 0 | 100 | 0 |00:00:00.01 | 0 | 0 | | | |
| 22 | HASH GROUP BY | | 0 | 100 | 0 |00:00:00.01 | 0 | 0 | 1232K| 1232K| 1438K (0)|
| 23 | PX RECEIVE | | 0 | 100 | 0 |00:00:00.01 | 0 | 0 | | | |
| 24 | PX SEND HASH | :TQ10000 | 0 | 100 | 0 |00:00:00.01 | 0 | 0 | | | |
| 25 | HASH GROUP BY | | 0 | 100 | 0 |00:00:00.01 | 0 | 0 | 3692K| 1516K| 1754K (0)|
| 26 | PX BLOCK ITERATOR | | 0 | 10M| 0 |00:00:00.01 | 0 | 0 | | | |
|* 27 | TABLE ACCESS INMEMORY FULL| FACT_SALES | 0 | 10M| 0 |00:00:00.01 | 0 | 0 | | | |
| 28 | PX RECEIVE | | 0 | 100 | 0 |00:00:00.01 | 0 | 0 | | | |
| 29 | PX SEND HYBRID HASH | :TQ10002 | 0 | 100 | 0 |00:00:00.01 | 0 | 0 | | | |
| 30 | PX BLOCK ITERATOR | | 0 | 100 | 0 |00:00:00.01 | 0 | 0 | | | |
|* 31 | TABLE ACCESS INMEMORY FULL | DIM_REGION | 0 | 100 | 0 |00:00:00.01 | 0 | 0 | | | |
| 32 | JOIN FILTER USE | :BF0000 | 0 | 5000 | 0 |00:00:00.01 | 0 | 0 | | | |
| 33 | PX BLOCK ITERATOR | | 0 | 5000 | 0 |00:00:00.01 | 0 | 0 | | | |
|* 34 | TABLE ACCESS INMEMORY FULL | DIM_PRODUCT | 0 | 5000 | 0 |00:00:00.01 | 0 | 0 | | | |
------------------------------------------------------------------------------------------------------------------------------------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
1 - filter(ROWNUM<=30)
5 - filter(ROWNUM<=30)
8 - filter(ROWNUM<=30)
13 - access("P"."PROD_ID"="ITEM_2")
17 - access("R"."REGION_ID"="ITEM_1")
27 - inmemory(:Z>=:Z AND :Z<=:Z)
31 - inmemory(:Z>=:Z AND :Z<=:Z)
34 - inmemory(:Z>=:Z AND :Z<=:Z AND SYS_OP_BLOOM_FILTER(:BF0000,"P"."PROD_ID"))
filter(SYS_OP_BLOOM_FILTER(:BF0000,"P"."PROD_ID"))
Note
-----
- dynamic statistics used: dynamic sampling (level=AUTO (SYSTEM))
- Degree of Parallelism is 8 because of table property
- statistics feedback used for this statement
72 rows selected.
Elapsed: 00:00:00.047
SQL>
SQL> var prb_after number
SQL> var slr_after number
SQL> begin
2 select
3 max(case when n.name = 'physical read bytes' then s.value end),
4 max(case when n.name = 'session logical reads' then s.value end)
5 into :prb_after, :slr_after
6 from v$sesstat s join v$statname n on n.statistic# = s.statistic#
7 where s.sid = sys_context('USERENV','SID')
8 and n.name in ('physical read bytes','session logical reads');
9 end;
10* /
PL/SQL procedure successfully completed.
Elapsed: 00:00:00.022
SQL>
SQL> select
2 (:prb_after - :prb_before) as delta_physical_read_bytes,
3 (:slr_after - :slr_before) as delta_session_logical_reads
4* from dual;
DELTA_PHYSICAL_READ_BYTES DELTA_SESSION_LOGICAL_READS
_________________________ ___________________________
139264 10003032
Elapsed: 00:00:00.003
SQL>
■ 結果
| 項目 | IM OFF (Diskアクセス) |
IM ON (In-Memoryアクセス) |
|---|---|---|
| Elapsed | 02:41.7 | 00:00.1 |
| delta physical read bytes | 81,936,908,288(約76.3GB) | 139,264(約136KB) |
| 圧縮(Disk→IM) | P05:38.15GB→0.44GB P06:38.15GB→0.44GB (各パーティション87.23倍圧縮) |
同左 |
● 圧縮率(Disk vs In-Memory)
今回、パーティション P05 と P06 は、それぞれ Disk 上では 38.15GB だったのに対し、In-Memory 上では 0.44GB まで圧縮されました。
つまり、1 パーティションあたり約 87 倍の圧縮率となっています。
2つの パーティション合計で見ると、Disk 上では約 76.3GB のデータが、In-Memory 上では約 0.88GB に収まっていることが確認できました。
V$IM_SEGMENTS(FACT_SALESのP05/P06)より
P05:Disk 38.15GB → IM 0.44GB(87.23x)
P06:Disk 38.15GB → IM 0.44GB(87.23x)
● In-Memory=OFF(Disk読み) vs In-Memory=ON(In-Memory読み)の測定結果
実行時間は、In-Memory OFF では 161.706 秒、In-Memory ON では 0.137 秒でした。
今回の条件では、約 1,180 倍の高速化が確認できました。
また、物理 I/O についても、In-Memory OFF では約 76.3GB の読み取りが発生した一方で、In-Memory ON では 136KB 程度に抑えられていました。
・ 実行時間(Elapsed)
約 1,180倍 高速化(161.706 / 0.137 ≒ 1180)
In-Memory=OFF:00:02:41.706(= 161.706秒)
In-Memory=ON :00:00:00.137(= 0.137秒)
・ 物理I/O(delta_physical_read_bytes)
IM OFFは約76GBをディスクから読み、IM ONはほぼ0
In-Memory=OFF:81,936,908,288 bytes ≒ 76.3GB 読み取り
In-Memory=ON :139,264 bytes ≒ 136KB 読み取り
■ まとめ
回は Oracle AI Database In-Memory を有効化し、Disk 読み取り時と In-Memory 読み取り時の差を比較してみました。
その結果、
- 実行時間は 161.706 秒 → 0.137 秒
- 物理読み取りは 約 76.3GB → 約 136KB
- データサイズは 38.15GB/partition → 0.44GB/partition
となり、In-Memory によって大幅な高速化と高い圧縮効果を確認できました。
また、In-Memory はテーブル単位だけでなく、パーティション単位でも柔軟に設定できるため、必要なデータだけを効率よくメモリに載せる運用が可能です。
分析クエリを高速化したい場面では、非常に強力な選択肢になりそうです。
■ 参考
-
概要
・ Database In-Memory
・ Oracle AI Database 26ai の新しいデータベース インメモリ機能 -
ドキュメント
・ Database In-Memory Guide
・ DBIM Resources
・ Oracle AI Database In-Memoryの概要
・ Oracle Exadata インメモリー列形式のサポート -
Whitepaper
・ Oracle Database In-Memory with Oracle Database 19c Technical Overview・ -
ワークショップ
・ LiveLabsワークショップ-- Oracle Database In-Memoryによる分析パフォーマンスの向上 -
Oracle Blog
・ Oracle Database 21c Enhanced In-Memory External Table Support
・ Oracle Database 21c In-Memory Full Text Columns
・ Oracle Analytics Cloud Uses Database In-Memory -
YouTube
・ Database In-Memory YouTubeチャンネル
・ Database In-Memory Office Hours -
Data Sheet
・ Oracle Database In-Memory
Exadata Database Service on Dedicated Infrastructure X11M
■ 技術解説
■ おまけ
今回の内容を、ずんだもん達に紹介してもらう漫画も作ってみました。 技術記事の補足として、少しでも楽しく読んでもらえたらうれしいです。

※ 本漫画は筆者による非公式の二次創作です。
※ 使用キャラクター:ずんだもん / 四国めたん / 春日部つむぎ
※ キャラクターの権利は各権利元に帰属します。
※ クレジット
- ずんだもん / 四国めたん:東北ずん子・ずんだもんプロジェクト関連ガイドラインに基づいて利用
- 春日部つむぎ:公式利用規約に基づいて利用