1. はじめに
Autonomous Database には、ロードした表のオプティマイザ統計を自動で収集する仕組みがあります。公式にはこう書かれています1。
レイクハウス・ワークロードを使用したAutonomous AI Databaseでは、SQLに発行されたダイレクト・パス操作でロードされた表のオプティマイザ統計が自動的に収集されます
読むと条件が 2つ付いています。ワークロードが Lakehouse であること、そして「SQL に発行されたダイレクト・パス操作」であることです。どのような方法でロードすれば、この条件に当てはまるのか。CREATE TABLE ... AS SELECT や INSERT INTO ... SELECT、impdp はそれぞれどちらに入るのか。そして 取得されたかどうかを、どうやって確認すればよいのか。
Transaction Processing ワークロードの Autonomous Database に対して、SQL のロードを 13パターン、impdp を 8パターン実行して確かめました。
先に断っておくと、この記事に書くのは 1環境での実測結果です。Oracle の仕様として一般化できるところまでは詰めていません。とくに公式の記述と一致しなかった項目(7.3 章)は、仕様が変わったのか、Autonomous Database 固有なのか、この環境だけの事情なのかを切り分けていません。
1.1. 結論(先出し)
- 統計がロード時に取得されるかは、そのロードが SQL 経由の direct path かどうかでほぼ決まる。例外は
NO_GATHER_OPTIMIZER_STATISTICSヒントで抑えた場合 -
INSERT INTO ... SELECT(APPENDヒントなし)では取得されない。MERGEとINSERT ... VALUESも同じ -
impdpはACCESS_METHODで結果が 3通りに分かれる。DIRECT_PATHを指定すると表統計と列統計が取得されない - 取得されたかどうかは
USER_TAB_COL_STATISTICSのNOTES列で見る。表統計側のNOTESは全ケースで NULL だった
impdp は ACCESS_METHOD の指定で結果が変わります。名前からは DIRECT_PATH のほうが統計を取りそうに見えますが、実際に取得されたのは EXTERNAL_TABLE のほうでした。
上に引いた公式の記述は Lakehouse ワークロード向けのものです。今回測ったのは Transaction Processing ワークロードなので、Autonomous Data Warehouse では違う結果になる可能性があります。
1.2. 検証ゴール
| # | 確かめること | 確認できれば OK の条件 |
|---|---|---|
| 1 | どのロード方法だと統計がロード時に取得されるのか | ロード方法ごとに、取得されたかどうかが、統計ビューの値だけで判別できる |
| 2 | 取得された/されていないを実務でどう確認するか | 見るべきビューと列、時刻の読み方、使えない確認手段までが示せる |
| 3 | 公式ドキュメントの記述と実測が一致するか | 一致しない項目について、公式の記述と実測値の両方を並べて示せる |
2. 検証環境
2.1. 対象の Autonomous Database
| 項目 | 内容 |
|---|---|
| データベース | Oracle Autonomous Database(Serverless) |
| ワークロード | Transaction Processing(db-workload は OLTP) |
| バージョン | Oracle AI Database 26ai 23.26.3.2.0 |
| CPU | 2 ECPU |
| リージョン | ap-tokyo-1 |
| 接続サービス |
_low(parallel_degree_policy は MANUAL)と _medium(同 AUTO) |
| SQL クライアント | SQLcl(wallet / mTLS 接続) |
| Data Pump クライアント | Oracle Instant Client 23.26.3.0.0(Basic + Tools, Windows x64) |
ワークロード種別を最初に確認したのは、公式の記述がワークロードごとに分かれているためです。同じページに「Lakehouse はオプティマイザヒントと PARALLEL ヒントをデフォルトで無視し、Transaction Processing と JSON Database は考慮する」ともあり1、どちらの環境かで前提が変わります。
2.2. 統計まわりの設定値
検証を始める前に、統計に関係する初期化パラメータと DBMS_STATS のプリファレンスを実測しました。すべてデフォルト値のままです。
| パラメータ | 値 |
|---|---|
optimizer_real_time_statistics |
FALSE |
optimizer_ignore_hints |
FALSE |
optimizer_ignore_parallel_hints |
FALSE |
optimizer_adaptive_statistics |
FALSE |
optimizer_features_enable |
23.1.0 |
statistics_level |
TYPICAL |
optimizer_ignore_hints が FALSE なので、この環境では /*+ APPEND */ などのヒントが有効になります。上に書いた「Transaction Processing はヒントを考慮する」という公式の記述と一致していました。
DBMS_STATS のグローバルプリファレンスと自動統計タスクの設定は次のとおりです。
| 項目 | 値 |
|---|---|
OPTIONS |
GATHER |
METHOD_OPT |
FOR ALL COLUMNS SIZE 254 |
ESTIMATE_PERCENT |
DBMS_STATS.AUTO_SAMPLE_SIZE |
STALE_PERCENT |
10 |
AUTO_TASK_STATUS |
ON |
AUTO_TASK_INTERVAL |
900(秒) |
自動統計収集タスクが有効で、15 分間隔で動きます。この間隔は 5 章の結果に関係します。
3. 統計が自動で取得される仕組みと、確認に使うビュー
3.1. 3つの仕組みを分けて考える
Oracle には統計を自動で取得する仕組みが 3つあり、収集される条件がそれぞれ違います。混同すると実測結果が読めなくなるので、先に整理しておきます。
| 仕組み | 収集される場面 | この環境でのデフォルト |
|---|---|---|
| オンライン統計収集 |
CREATE TABLE ... AS SELECT と direct-path insert のロード中2
|
有効 |
| リアルタイム統計 | 通常の(conventional な)DML 中に、最大値など主要な統計を動的に算出2 |
無効(optimizer_real_time_statistics が FALSE) |
| 自動統計収集タスク | ロードとは非同期に、統計が無い表・古い表をまとめて処理 | 有効・15 分間隔 |
リアルタイム統計は Exadata 系でのみ使える機能で3、初期化パラメータのデフォルト値は false です4。Autonomous Database の機能一覧には項目として載っていますが5、そこには「デフォルトで有効」の記載がありません。実測でも FALSE でした。
3.2. 確認ポイント
統計は表・列・索引の 3つに分かれていて、どれか 1つを見ただけでは判断できません。6.2 章のように、表統計と列統計が無いのに索引統計だけが付いているケースがあります。3つとも見ます。
SELECT 'TAB' AS kind, table_name AS name, num_rows,
TO_CHAR(last_analyzed, 'YYYY-MM-DD HH24:MI:SS') AS last_analyzed, notes
FROM user_tab_statistics WHERE table_name = 'YOUR_TABLE' AND object_type = 'TABLE'
UNION ALL
SELECT 'COL', column_name, num_distinct,
TO_CHAR(last_analyzed, 'YYYY-MM-DD HH24:MI:SS'), notes
FROM user_tab_col_statistics WHERE table_name = 'YOUR_TABLE'
UNION ALL
SELECT 'IDX', index_name, num_rows,
TO_CHAR(last_analyzed, 'YYYY-MM-DD HH24:MI:SS'), NULL
FROM user_ind_statistics WHERE table_name = 'YOUR_TABLE';
そのうえで、ロード時に取得されたかを見分けるのは列統計の NOTES です。見るのは NOTES 列と LAST_ANALYZED 列です。NOTES にはその統計がどの経路で収集されたかを示す値が入ります。
NOTES の値 |
意味 |
|---|---|
STATS_ON_LOAD |
ロード中のオンライン統計収集で取得された |
STATS_ON_CONVENTIONAL_DML |
リアルタイム統計が既存の統計を補強した |
表統計のビュー(USER_TAB_STATISTICS)にも NOTES 列はありますが、今回の実測では全ケースで NULL でした。公式ドキュメントも STATS_ON_LOAD については列統計側のビューを名指ししていて6、実測と一致しています。STATS_ON_LOAD は表統計側には入りません。
3.3. direct path かどうかは実行計画で見る
そのロードが direct path になるかどうかは、実行計画で見分けられます。EXPLAIN PLAN はオプティマイザが「こう実行するつもり」と示す計画なので、実行前に確かめる用途に向きます。実行後に確かめるなら V$SQL_PLAN を参照します。
EXPLAIN PLAN SET STATEMENT_ID = 'D1' FOR
INSERT /*+ APPEND */ INTO t_d1 SELECT * FROM src_data;
SELECT id, LPAD(' ', depth) || operation || ' ' || NVL(options, '') || ' ' || NVL(object_name, '')
FROM plan_table WHERE statement_id = 'D1' ORDER BY id;
APPEND ありとなしで、次のように分かれました。
APPEND あり
INSERT STATEMENT
LOAD AS SELECT T_D1
OPTIMIZER STATISTICS GATHERING
TABLE ACCESS STORAGE FULL SRC_DATA
APPEND なし
INSERT STATEMENT
LOAD TABLE CONVENTIONAL T_D2
TABLE ACCESS STORAGE FULL SRC_DATA
LOAD AS SELECT が direct path、LOAD TABLE CONVENTIONAL が conventional です。direct path 側にだけ OPTIMIZER STATISTICS GATHERING という行が出ています。
ただしこれは EXPLAIN PLAN の出力、つまり実行前の計画です。実行時の計画を V$SQL_PLAN から取ろうとしましたが、今回は取得できませんでした。したがって「この計画のとおりに実行された」ことまでは確かめていません。以降で direct path と書いているのは、この計画と、列統計に STATS_ON_LOAD が付いたこと(公式によれば direct path insert と CTAS でしか起きない2)の 2つを合わせた読みです。
4. SQL でロードして実測する
4.1. 測り方
100万行のソース表を用意し、ロード方法ごとに使い捨ての表を作って実行しました。観測条件は次のとおりです。
- ロード先の空表は
CREATE TABLE(列定義)で作る。CTAS ... WHERE 1=0で作ると CTAS でも統計が取得され、「統計が無い空表」という前提が成り立たなくなると考えたため。実際にはCTAS ... WHERE 1=0で作った表にも統計は取得されず、その後のロードの結果も列定義で作った場合と同じだった - ロード直後(数秒以内)に観測する。15 分間隔の自動統計タスクの実行と重ならないようにするため
-
_lowと_mediumの両サービスで同じスクリプトを実行する
4.2. 結果
_low と _medium で数値は完全に一致したので、1つの表にまとめます。
| ロード方法 | 統計 | 列統計の NOTES
|
|---|---|---|
CREATE TABLE ... AS SELECT |
取得された | STATS_ON_LOAD HYPERLOGLOG |
INSERT /*+ APPEND */ ... SELECT(空表へ) |
取得された | STATS_ON_LOAD HYPERLOGLOG |
INSERT /*+ APPEND */ ... SELECT(1000行入った表へ) |
取得された | STATS_ON_LOAD HYPERLOGLOG |
INSERT /*+ APPEND */ ... SELECT(TRUNCATE した表へ) |
取得された | STATS_ON_LOAD HYPERLOGLOG |
INSERT INTO ... SELECT |
取得されない | 列統計の行が返らない |
INSERT INTO ... SELECT(統計がある表へ追加) |
更新されない | 変化なし |
INSERT ... VALUES を 10万行(FORALL) |
取得されない | 列統計の行が返らない |
MERGE で 100万行 |
取得されない | 列統計の行が返らない |
索引つきの表へ INSERT /*+ APPEND */
|
取得された(索引統計も) | STATS_ON_LOAD HYPERLOGLOG |
パラレル DML の INSERT ... SELECT(APPEND なし) |
取得された | STATS_ON_LOAD HYPERLOGLOG |
NOTES の値が STATS_ON_LOAD HYPERLOGLOG と 2つの語の並びになっている点に注意してください。空白区切りの複数フラグなので、notes = 'STATS_ON_LOAD' の等値比較では一致しません。HYPERLOGLOG のほうは公式ドキュメントに記載を見つけられませんでした。
ヒントで指定して制御した場合はこうなりました。
| 指定 | 結果 |
|---|---|
INSERT /*+ APPEND NO_GATHER_OPTIMIZER_STATISTICS */ |
取得されない(収集を止められた) |
CREATE TABLE ... AS SELECT /*+ NO_GATHER_OPTIMIZER_STATISTICS */ |
取得されない(収集を止められた) |
INSERT /*+ GATHER_OPTIMIZER_STATISTICS */(APPEND なし) |
取得されない |
収集は止められましたが、conventional なロードに収集を要求するヒントを付けても統計は取得されませんでした。
4.3. INSERT INTO ... SELECT では取得されない
統計を持たない空表に、100万行を INSERT INTO ... SELECT で入れました。
CREATE TABLE t_l4 (id NUMBER, c_ndv1000 NUMBER, c_ndv10 NUMBER,
c_str VARCHAR2(20), c_date DATE, c_withnull NUMBER);
INSERT INTO t_l4 SELECT * FROM src_data;
COMMIT;
直後に観測すると、2つのビューで見え方が違います。USER_TAB_STATISTICS には行が返りますが、num_rows も last_analyzed もすべて NULL です。USER_TAB_COL_STATISTICS のほうは行そのものが返りません。30 秒待っても同じでした。
[L4_before] TAB T_L4 num_rows=NULL blocks=NULL sample=NULL last_analyzed=NULL notes=NULL stale=NULL
[L4_before] REAL T_L4 actual_rows=0
[L4_after] TAB T_L4 num_rows=NULL blocks=NULL sample=NULL last_analyzed=NULL notes=NULL stale=NULL
[L4_after] REAL T_L4 actual_rows=1000000
[L4_after_30s] TAB T_L4 num_rows=NULL blocks=NULL sample=NULL last_analyzed=NULL notes=NULL stale=NULL
上の出力に列統計の行が 1つも出ていないことが、そのまま USER_TAB_COL_STATISTICS が空だったことを示しています。統計が取得されたケース(4.4 章)では、同じ観測で列ごとの行が並びます。
一方 USER_TAB_MODIFICATIONS には INSERTS=1000000 が記録されていました。100万行の INSERT は記録される一方、統計は収集されていません。
MERGE と INSERT ... VALUES も同じで、統計は取得されませんでした。
4.4. APPEND を付けると取得される
同じロードでも /*+ APPEND */ を付けると結果が変わります。
INSERT /*+ APPEND */ INTO t_l2 SELECT * FROM src_data;
COMMIT;
[L2_after] TAB T_L2 num_rows=1000000 blocks=4856 sample=1000000
[L2_after] COL T_L2.ID ndv=971092 hist=HYBRID notes=STATS_ON_LOAD HYPERLOGLOG
ヒストグラム(HYBRID)まで作られていました。索引つきの表で試すと索引統計も付いています。
[L7_before] IDX IX_L7_ID num_rows=NULL dist_keys=NULL last_analyzed=NULL
[L7_after] IDX IX_L7_ID num_rows=1000000 dist_keys=1000000 last_analyzed=(表と同時刻)
APPEND を付けなくても、パラレル DML を有効にすると統計が取得されました。パラレル insert はデフォルトで direct path になるためです2。
ALTER SESSION ENABLE PARALLEL DML;
INSERT /*+ PARALLEL(t_l10, 4) */ INTO t_l10 SELECT /*+ PARALLEL(4) */ * FROM src_data;
4.5. リアルタイム統計を有効にしても、空表には取得されない
optimizer_real_time_statistics はデフォルトで FALSE でしたが、Autonomous Database でもセッション単位で変更できました。有効にして測り直すと、次のようになります。
| ロード先 | 結果 |
|---|---|
| 統計を持たない空表 | 統計は取得されない |
| 既に統計がある表に追加 |
ID 列の統計が 2行になった。元の 100000 の行はそのままで、上限値 999994 で NOTES が STATS_ON_CONVENTIONAL_DML の行が増えた。num_rows は 100000 のままで STALE_STATS が YES
|
後者では、後続クエリの実行計画の Note に次の行が出ました。
Note
-----
- dynamic statistics used: statistics for conventional DML
リアルタイム統計は既存の統計を補強するもので、統計を新しく作る機能ではありません。公式にもこう書かれています2。
リアルタイム統計は従来の統計を置き換えるのではなく、これを増強するものです。
増強する対象が無い状態、つまり統計を持たない表への初回ロードでは統計が作られない、と読むのが実測と合います。
5. 取得されなかった表に、いつ統計が取得されるのか
5.1. 実測した待ち時間
ロード時に統計が取得されなくても、後から自動統計収集タスクが収集します。どれくらい待つのかを測りました。
100万行を INSERT INTO ... SELECT で入れた表を放置し、18 分待ってから観測しています。
| 時刻 | 状態 |
|---|---|
| 20:41:58 | ロード完了。統計なし |
| 20:43:03 | まだ統計なし |
| 20:50:14 |
num_rows=1000000 の統計が取得された。NOTES に STATS_ON_LOAD は無い |
ロードから 約 8 分後 に取得されました。同じスクリプトで作った他の表も 20:50:12 から 20:50:19 の間にまとめて統計が取得されており、1 回のタスク実行でまとめて処理されたように見えます。AUTO_TASK_INTERVAL が 900 秒であることと矛盾しません。ただしタスクの実行履歴を参照できなかったため、高頻度タスクが処理したとは断定できません。
重要なのは NOTES の違いです。STATS_ON_LOAD が入るのはロード時の収集で付いた統計だけで、後から付いた統計には入りません。この差で 3つの状態を見分けられます。
| 観測される状態 | 意味 |
|---|---|
NOTES に STATS_ON_LOAD がある |
ロード時に取得された |
NOTES に無く、LAST_ANALYZED がロード時刻より後 |
後から自動統計タスクなどが収集した |
| 列統計の行が返らない | まだどちらも起きていない |
5.2. 待てば全部に取得されるのか
大量の表をまとめてインポートしたあと、しばらく待てば全部に統計が取得されるのか。公式の記述からは「待てば揃う」とは言えません。
理由は 2つあります。1つ目は、高頻度の自動統計収集タスクの対象です7。
高頻度タスクは「軽量」なものであり、失効した統計のみを収集します。
「統計が無い表」を含むという記述はありません。インポート直後の表は統計が失効しているのではなく、そもそも無い状態です。この記述をそのまま読むと対象に含まれません。今回の実測では統計を持たない表に約 8 分後に統計が取得されましたが、実行履歴を参照できなかったため、どの仕組みが収集したのかは確かめられていません。
2つ目は、打ち切られたときの扱いです。AUTO_TASK_MAX_RUN_TIME に達して打ち切られた場合、残りが次の実行へ持ち越されるのか、どの順序で処理されるのかは、公式に記述を見つけられませんでした。DBA_AUTO_STAT_EXECUTIONS に TIMED_OUT(タイムアウトしたオブジェクトの数)という列があるので、打ち切り自体は起こり得ます。表が数百・数千ある移行では、1 回の実行で処理しきれるかどうかが分かりません。
確実に揃えたいなら、待つのではなく自分で収集します。
BEGIN
DBMS_STATS.GATHER_SCHEMA_STATS(ownname => 'YOUR_SCHEMA',
options => 'GATHER EMPTY');
END;
/
OPTIONS の値には公式の定義があり8、GATHER EMPTY が「現在統計情報がないオブジェクト」、GATHER STALE が失効オブジェクト、GATHER AUTO が自動判別です。インポート直後で統計が付いていない表があるなら GATHER EMPTY が該当します。
ただし ADB のドキュメントは、Transaction Processing と JSON のワークロードについて「オプティマイザ統計が自動的に収集されるため、このタスクを手動で実行する必要はなく、標準のメンテナンス・ウィンドウで実行されます」としています1。すぐに統計が必要かどうかで判断が変わります。移行の直後に本番と同じクエリを実行するなら自分で収集し、しばらく様子を見られるなら任せる、という切り分けになります。
6. impdp で実測する
Data Pump のドキュメントには、ロード時の統計収集について次の一文があります9。
Oracle Data Pumpでは、ロードに関する統計がデフォルトでは収集されません。ただし、Oracle Autonomous Databaseなどの一部の環境では、ロードに関する統計がデフォルトで収集されます。
Autonomous Database は例外的に収集する側だと書かれています。ただしどのロード経路でそうなるかまでは書かれていないので、ACCESS_METHOD を変えて測りました。
6.1. 測り方
impdp では、インポート後に見えた統計が ダンプファイルに入っていたものなのか、ロード時に収集されたものなのか を区別する必要があります。時刻で見分けようとすると後述の問題(7.2 章)があるため、値で判別できるようにしました。
エクスポート元の表に、実データと一致しない統計を設定します。
INSERT INTO t_imp_a SELECT * FROM src_data WHERE id <= 200000; -- 実データは 200,000 行
COMMIT;
EXEC DBMS_STATS.GATHER_TABLE_STATS(USER, 'T_IMP_A');
-- 実データと一致しない値に差し替える
BEGIN
DBMS_STATS.SET_TABLE_STATS(USER, 'T_IMP_A', numrows => 12345, numblks => 999);
DBMS_STATS.SET_COLUMN_STATS(USER, 'T_IMP_A', 'ID', distcnt => 4242);
END;
/
これでインポート後の num_rows を見るだけで判別できます。
インポート後の num_rows
|
意味 |
|---|---|
12345 |
ダンプファイルに入っていた統計がそのまま入った |
200000 |
このインポートで収集された |
| 行が無い | どちらも起きていない |
統計をまったく持たない表と、索引つきの表も併せて用意し、3表をまとめて 1つのダンプファイルにエクスポートしました。インポートは TABLE_EXISTS_ACTION=REPLACE を付けて、毎回 3表を作り直しています。
6.2. 結果
8通りのインポートを順に実行しました。「使われた経路」は impdp のログに出力される値です。
| 指定 | 使われた経路 | インポート後の num_rows
|
判定 |
|---|---|---|---|
(指定なし=AUTOMATIC) |
external_table |
200000 | ロード時に収集 |
ACCESS_METHOD=DIRECT_PATH |
direct_path |
行が無い | 表統計・列統計なし |
ACCESS_METHOD=EXTERNAL_TABLE |
external_table |
200000 | ロード時に収集 |
ACCESS_METHOD=CONVENTIONAL |
conventional |
行が無い | 表統計・列統計なし |
DIRECT_PATH + EXCLUDE=STATISTICS
|
direct_path |
行が無い | 表統計・列統計なし |
CONVENTIONAL + EXCLUDE=STATISTICS
|
conventional |
行が無い | 表統計・列統計なし |
DIRECT_PATH + DATA_OPTIONS=DISABLE_STATS_GATHERING
|
direct_path |
12345 | ダンプファイル由来 |
EXTERNAL_TABLE + EXCLUDE=STATISTICS
|
external_table |
200000 | ロード時に収集 |
統計が収集された行では、列統計の NOTES が STATS_ON_LOAD HYPERLOGLOG になっていました。SQL でロードしたときと同じ値です。
判定の列を「表統計・列統計なし」と書いたのは、索引統計だけは別で、どの指定でも付いていたためです。索引つきの T_IMP_C を見ると、表統計も列統計も無い DIRECT_PATH のケースでも索引統計は実データと一致していました。
D2_DIRECT_PATH IX_IMP_C_ID num_rows=200000 leaf_blocks=473 distinct_keys=200000
impdp が索引を作り直すときに計算されたもので、ロード時の統計収集とは別の経路です。DATA_OPTIONS=DISABLE_STATS_GATHERING を付けたケースだけは、索引統計もダンプファイル由来の num_rows=12345 になりました。
impdp のログには、収集しようとして実行されなかったケースで警告が出ます。
W-1 Warning: Statistics not gathered while loading data for table "<スキーマ>.T_IMP_A"
W-1 . . imported "<スキーマ>"."T_IMP_A" 6.2 MB 200000 rows in -32397 seconds using direct_path
external_table が使われたケースにはこの警告が出ませんでした。所要時間が負になっているのは、7.2 章で触れる時刻基準の不一致と同じ現象と考えられます。統計の取得結果には関係しません。
6.3. DIRECT_PATH で取得されず、EXTERNAL_TABLE で取得される理由
DIRECT_PATH という名前からは統計が取得されそうに見えますが、実際に取得されたのは EXTERNAL_TABLE のほうでした。それぞれの実体を並べると説明が付きます。
| 指定 | 実体 | 統計 |
|---|---|---|
DIRECT_PATH |
Direct Path API。SQL のデータ処理をバイパスする | 取得されない |
EXTERNAL_TABLE |
外部表から INSERT /*+ APPEND */ ... SELECT。SQL 経由の direct path |
取得される |
CONVENTIONAL |
外部表から 1行ずつ INSERT | 取得されない |
公式ドキュメント 2 か所を合わせて読むと説明できます。1つは Data Pump 側の EXTERNAL_TABLE の説明です9。
データ・ポンプによって、ダンプ・ファイルに格納されているデータに対する外部表が作成され、SQL文INSERT AS SELECTを使用してデータが表にロードされます。データ・ポンプによって、APPENDヒントがINSERT文に適用されます。
外部表経路の実体は INSERT /*+ APPEND */ ... SELECT で、SQL 経由の direct path です。もう 1つは、1 章で引いた Autonomous Database 側の記述に続く括弧の中です1。
(SQL*Loaderダイレクト・パスなど、SQLデータ処理をバイパスのダイレクト・パス・インポート操作で、統計は収集されません)
Data Pump の DIRECT_PATH はここに当てはまります。名前に DIRECT_PATH と付いていても、SQL を経由しないため統計は収集されない、と読めます。CONVENTIONAL のほうは公式の説明が「外部表から 1つずつ行が読み取られます」9 とあるとおりで、direct path になりません。
さらに注意したいのは、DIRECT_PATH と CONVENTIONAL では ダンプファイルに入っていた統計も入らない 点です。EXCLUDE=STATISTICS を指定していないのに、インポート後の表に表統計も列統計もありません。
6.4. 統計がどこから来るか
紛らわしい点を先に書きます。この環境では「ダンプファイルに統計を入れておけば、ロード時に収集されなくても統計は入る」とは言えませんでした。
以下は impdp の内部仕様の説明ではなく、8通りの指定で測った結果です。
DIRECT_PATH と CONVENTIONAL は EXCLUDE=STATISTICS を指定していないのに、ダンプファイル由来の統計も入っていません。ダンプファイル側に統計があることは、エクスポートのログで確認できます。
W-1 Processing object type TABLE_EXPORT/TABLE/STATISTICS/TABLE_STATISTICS
W-1 Completed 3 TABLE_STATISTICS objects in 0 seconds
ダンプファイル由来の統計が入ったのは、DATA_OPTIONS=DISABLE_STATS_GATHERING を付けたときだけでした。設定した num_rows=12345 がそのまま入っています。
T_IMP_A num_rows=12345 blocks=999 ID 列 ndv=4242 (実データは 200,000 行)
逆に収集を止めたい場合、EXCLUDE=STATISTICS では止まりません。EXTERNAL_TABLE に除外指定を付けたケースでも統計が収集されました。これはダンプファイル内の統計メタデータを除外する指定であって、ロード中の収集とは別だからです。止めるのも DATA_OPTIONS=DISABLE_STATS_GATHERING です。
7. 考察
7.1. 1つの条件で説明できる
今回測った 21通りのうち 19通りは、「そのロードが SQL 経由の direct path か」という 1つの条件で説明できました。CREATE TABLE ... AS SELECT・INSERT /*+ APPEND */・パラレル DML・impdp の EXTERNAL_TABLE はすべて SQL 経由の direct path で、統計が取得されています。INSERT INTO ... SELECT・MERGE・INSERT ... VALUES・impdp の CONVENTIONAL は SQL 経由ですが direct path ではありません。impdp の DIRECT_PATH は direct path ですが SQL を経由しません。どちらも取得されませんでした。
残りの 2通りは例外です。NO_GATHER_OPTIMIZER_STATISTICS ヒントを付けた INSERT /*+ APPEND */ と CREATE TABLE ... AS SELECT は、SQL 経由の direct path でありながら統計が取得されませんでした(4.2 章)。ヒントで止めた場合は、この条件より優先されます。
この読み方は、Autonomous Database 側の「SQL で発行された direct path 操作で自動取得する」「SQL のデータ処理をバイパスする direct path ロードは収集しない」という記述1と一致します。
impdp の DIRECT_PATH と CONVENTIONAL で ダンプファイル由来の統計まで入らなかった理由は、公式ドキュメントに記載を見つけられませんでした。収集する前提でダンプファイル内の統計を取り込まず、しかし経路の都合で収集が実行されなかった、という順序ではないかと考えられます。DISABLE_STATS_GATHERING を付けたケースだけ ダンプファイル由来の統計が入り、かつそのケースだけ Statistics not gathered の警告が出ないことは、この読み方と矛盾しません。ただし内部の処理順序を確認したわけではないため、推測にとどまります。
7.2. 実務での確認手順
確認は列統計のビューを見るのが基本です。そのうえで、今回つまずいた点が 5つありました。
| つまずいた点 | 内容 |
|---|---|
表統計の NOTES を見ても分からない |
USER_TAB_STATISTICS の NOTES は全ケースで NULL。STATS_ON_LOAD は USER_TAB_COL_STATISTICS にしか入らない |
NOTES は空白区切りの複数フラグ |
実測値は STATS_ON_LOAD HYPERLOGLOG。notes = 'STATS_ON_LOAD' の等値比較では一致しない |
NOTES だけでは今回のロードで取れたか判定できない |
非空表への後続のバルクロードでは NOTES がリセットされない6。前のロードで収集された値が残る |
後から収集し直すと STATS_ON_LOAD が消える |
GATHER AUTO の実行後、STATS_ON_LOAD HYPERLOGLOG だった列が HYPERLOGLOG だけになった |
LAST_ANALYZED に別の時計が混ざることがある |
DBMS_STATS.SET_TABLE_STATS で手動設定した表統計だけ UTC で記録され、同じ実行の列統計(JST)と 9 時間ずれた |
| 1つの列に複数の行が返ることがある | リアルタイム統計が働いた列では、元の行と STATS_ON_CONVENTIONAL_DML の行の 2行が返った(4.5 章) |
2つ目があるので、検索するときは部分一致にします。
SELECT table_name, column_name, num_distinct, notes
FROM user_tab_col_statistics
WHERE notes LIKE '%STATS_ON_LOAD%';
5つ目は、この環境の事情によるものでした。セッションの SYSTIMESTAMP は JST(20:54:31)を返すのに、DBMS_STATS.SET_TABLE_STATS で手動設定した表統計の LAST_ANALYZED だけが UTC(11:54:34)で記録されていました。同じ実行の列統計は JST(20:54:34)です。手動で設定した統計に限った話で、収集された統計の LAST_ANALYZED が一般にずれるという意味ではありません。 ただしサーバの時計とセッションのタイムゾーンが違う環境では、時刻の突き合わせで足をすくわれることがあります。
3つ目と 5つ目が重なると、どちらか片方だけでは判定できません。NOTES には前のロードの値が残り、LAST_ANALYZED は上のように別の時計が混ざることがあるためです。4つ目のとおり後から収集し直すとこの値は消えるので、確かめたいならロード直後に見るのが確実です。6.1 章で値による判別方法にしたのも同じ理由です。
逆に、使えなかった確認手段もありました。自動統計タスクの実行履歴です。DBA_AUTO_STAT_EXECUTIONS と DBA_OPTSTAT_OPERATIONS が SELECT_CATALOG_ROLE を持つユーザーから 0行しか返らず、いつ何が収集されたかを追えませんでした。ADMIN なら参照できるのかは確かめていません。
7.3. 公式ドキュメントの記述と一致しなかった点
公式が「そうならない」と書いている項目のうち、4つで違う結果になりました。いずれもこの 1環境での観測です。 26ai の仕様が変わったのか、Autonomous Database 固有なのか、この環境だけの事情なのかは切り分けていません。「26ai ではこうなる」と一般化して読まないでください。
| 論点 | 公式の記述 | 今回の実測 |
|---|---|---|
| ヒストグラム | オンライン統計収集はヒストグラムを作らない2 | ロード直後に HYBRID / FREQUENCY が付いていた |
| 索引統計 | オンライン統計収集は索引統計を取らない6 | 索引統計も付いた(表と同じ LAST_ANALYZED) |
| 空表限定 | バルクロードで統計が取得されるのは対象が空のときだけ2 | 1000行入った表への APPEND でも取得・更新された |
| ロールバック | ロールバックすると収集した統計は自動削除される2 | ロールバック後も統計が残っていた |
ヒストグラムと索引統計については、どの仕組みが作ったのかを切り分けていません。観測したのは「ロードの直後に付いていた」ことだけで、オンライン統計収集が作ったと確かめたわけではありません。列統計の NOTES に STATS_ON_LOAD が入っているので同じ収集の一部だと読めますが、NOTES は列統計の属性であって、ヒストグラムや索引統計がどこから来たかを示すものではありません。
索引統計については、もう少し具体的に切り分けができていません。ロード前の索引には統計が無く、direct path ロードの後に実データと一致する値が付きました。ただしこれがオンライン統計収集によるものか、direct path ロードが索引を作り直す際に計算されたものかは判別できていません。6.2 章の impdp では、表統計も列統計も無いケースで索引統計だけが付いており、後者の経路が存在することは確かです。
パーティション表でも差がありました。表全体へのロードでグローバル統計だけが取得され、パーティション別の統計は取得されない点は公式の記述どおりです2。一致しなかったのはパーティションを指定したロードのほうで、公式はこう書いています2。
特定のパーティションまたはサブパーティションに行を挿入するために、パーティション拡張構文を使用するという別のケースについて考えてみます。データベースは、挿入中にパーティションの統計を収集します。ただし、グローバル統計は収集しません。
実測では逆でした。INSERT /*+ APPEND */ INTO t_p2 PARTITION (p1) SELECT ... を実行すると、グローバル統計(num_rows=250000)だけが取得され、パーティション別の統計は取得されません。
PARTITION_NAME NUM_ROWS LA
P1
P2
P3
P4
250000 2026-08-31 20:43:00
PARTITION_NAME が空の行がグローバル統計で、そこにだけ値が入っています。
これらが 26ai での仕様変更なのか、Autonomous Database 固有の挙動なのかは、この 1 環境では切り分けられません。ヒストグラムと索引統計に関する記述は 12.2 のドキュメントに由来するもので、その後のバージョンにもそのまま載っている可能性があります。
4つのうち、ロールバックの件は実害があります。公式にはこう書かれています2。
統計は挿入の直後に使用できます。ただし、トランザクションをロールバックすると、バルク・ロード中に収集された統計は自動的に削除されます。
100万行を INSERT /*+ APPEND */ で入れてロールバックした表を観測すると、次の状態でした。
TAB T_X5 num_rows=1000000 blocks=100 stale=YES
COL T_X5.ID ndv=971092 hist=HYBRID notes=STATS_ON_LOAD
REAL T_X5 actual_rows=0
実際には 0行の表が「100万行」という統計を持っています。STALE_STATS は YES になりますが、値そのものは残ります。direct path のロードが失敗してロールバックした表は、統計を収集し直す必要があります。
なお公式が挙げる制限のうち、統計がロックされている場合・PUBLISH が FALSE の場合・マルチテーブル INSERT の場合は、記述どおり統計が取得されませんでした。一時表(ON COMMIT PRESERVE ROWS)では取得されましたが、公式が除外しているのは ON COMMIT DELETE ROWS なので、こちらも記述と一致しています。
8. まとめ
Autonomous Database(Transaction Processing・26ai)で、ロード方法ごとに統計が自動取得されるかを実測しました。
- 取得されるのは SQL 経由の direct path のロード。
CREATE TABLE ... AS SELECT、INSERT /*+ APPEND */、パラレル DML、impdpのEXTERNAL_TABLEが該当する -
INSERT INTO ... SELECTでは取得されない。列統計の行ができず、自動統計タスクが収集するまで(今回は約 8 分)統計が無い状態が続く -
impdpはACCESS_METHODの指定で結果が変わり、DIRECT_PATHとCONVENTIONALではダンプファイル由来の統計も入らず、表統計と列統計が無い状態になる(索引統計だけは索引の作成に伴って付く) - 確認は
USER_TAB_COL_STATISTICSのNOTESとLAST_ANALYZEDを併せて見る。表統計側のNOTESは使えない - 移行のように大量の表を入れたあと、待てば全部取得されるとは公式の記述からは言えない。確実に揃えたいなら
GATHER_SCHEMA_STATSを自分で実行する
大きなロードのあとに実行計画が想定と違うときは、統計が取得されていない状態を疑ってみてください。impdp で ACCESS_METHOD=DIRECT_PATH を指定している場合は特に、インポート直後の表に統計が無い可能性があります。
今回の結果は Transaction Processing ワークロードで測ったものです。公式の記述が Lakehouse ワークロード向けである以上、Autonomous Data Warehouse では違う結果になる可能性があります。また、公式と一致しなかった 4点が 26ai の仕様変更なのか Autonomous Database 固有なのかは、本記事の実測の範囲外です。
参考
-
Autonomous AI Database に関するオプティマイザ統計の管理(SQL で発行された direct path 操作で統計を自動取得する、という記述。ワークロード別のヒントのデフォルトもここ。なお日本語版は「ダイレクト・パス・インポート操作」と訳しているが、英語版は import に限定せず direct path load operations と書いている) ↩ ↩2 ↩3 ↩4 ↩5
-
オプティマイザ統計の概念(26ai)(オンライン統計収集の対象と 6つの制限、リアルタイム統計、パーティション表での粒度) ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9 ↩10 ↩11
-
Oracle Database 26ai ライセンス情報(Real-Time Statistics は Exadata 系でのみ利用可) ↩
-
OPTIMIZER_REAL_TIME_STATISTICS(デフォルトは
false。19c Release Update 19.10 以降で利用可) ↩ -
Autonomous AI Database の機能一覧(高頻度の自動統計収集には「デフォルトで有効」の記載があるが、リアルタイム統計には無い) ↩
-
オプティマイザ統計の概念(12.2)(
STATS_ON_LOADが列統計のビューに入ること、非空表への後続ロードでNOTESがリセットされないこと、索引統計とヒストグラムが対象外であること) ↩ ↩2 ↩3 -
オプティマイザ統計の収集(高頻度の自動統計収集タスクが失効した統計のみを収集すること、標準タスクとの違い、
AUTO_TASK_*プリファレンス) ↩ -
DBMS_STATS(
GATHER_SCHEMA_STATSのOPTIONSに指定できる値の定義) ↩ -
Oracle Data Pump Import コマンドライン・モードで使用可能なパラメータ(
ACCESS_METHODの 5つの値と、DATA_OPTIONS=DISABLE_STATS_GATHERINGの説明。6 章の引用もここ) ↩ ↩2 ↩3

