Database Support Blog

  • Oracle Database
2026.09.03

【Oracle AI Database 26ai】現在の統計に影響を与えず過去の統計情報をエクスポート!AS_OF_TIMESTAMPを試してみた

【Oracle AI Database 26ai】現在の統計に影響を与えず過去の統計情報をエクスポート!AS_OF_TIMESTAMPを試してみた

パフォーマンス調査やテストのために、「過去のある時点のオプティマイザ統計(以下、統計情報)を取り出したい」と感じたことはありませんか。

従来のOracle Databaseでは、過去の統計情報を取り出すには現在の統計情報をいったん過去の状態に戻してからエクスポートし、その後、元へ戻す必要がありました。
特に本番環境では、現在の統計情報に影響する可能性があるため、慎重な対応が必要でした。

Oracle AI Database 26aiでは、DBMS_STATS.EXPORT_TABLE_STATSにAS_OF_TIMESTAMPパラメータが追加されました。

このパラメータを使用することで、過去の任意時点の統計情報を現在の統計情報に影響を与えることなくエクスポートできるようになりました。

本記事では、AS_OF_TIMESTAMPを使用した過去の統計情報のエクスポート方法とそのメリットを、業務シナリオに沿った検証手順とともにご紹介します。

※本記事で紹介しているAS_OF_TIMESTAMPパラメータは、Oracle AI Database 26aiで追加された新機能です。


はじめに:統計情報の移行が有用な場面

SQLの実行計画を本番環境に近い条件で調査したい場合、統計情報を別環境へエクスポート/インポートする方法が有効です。例えば、本番環境と同等のデータ量を用意できないテスト環境でも、本番環境の統計情報をインポートすることで実行計画を再現しやすくなります。

統計情報の移行が特に有用なのは、「少し前までは速かったが、現在は遅くなってしまった」という性能劣化の調査です。

統計情報の更新後にSQLの実行計画が変わって性能が悪化した場合、劣化前の時点の統計情報を検証環境へ持ち出すことができれば、当時の実行計画を再現しながら原因を調査することができます。

こうした調査では、単に現在の統計を移行できるだけでなく、過去の特定時点の統計情報を安全に取り出せることに大きな価値があります。

従来手順(~21c)の課題

前章で述べた通り、過去の任意時点の統計情報をエクスポートすることが有用な場面は多くあります。

しかしながら、従来のOracle Databaseでは過去の任意時点の統計情報をエクスポートする際に現在の統計情報を一時的に上書きしてしまうという課題がありました。

21c以前のDBMS_STATS.EXPORT_TABLE_STATSには、過去の時点を指定するパラメータが存在せず、現在の統計情報を取り出すことしかできませんでした。そのため、過去の統計情報をエクスポートするには次の手順が必要でした。



つまりエクスポートのためには「リストア → エクスポート → リストア」という操作が必要であり、リストアの際に現在の統計情報が書き換えられてしまいます。特に本番環境ではこの操作を実施すると、SQLの実行計画が変わり性能劣化を引き起こすリスクがあるため、実施には慎重な判断と事前の調整が求められていました。

AS_OF_TIMESTAMPとは

AS_OF_TIMESTAMPは、26aiでEXPORT_SCHEMA_STATSおよびEXPORT_TABLE_STATSに新たに追加されたパラメータです。

AS_OF_TIMESTAMP

My Oracle Support『KB390831』(オラクル社のサイトに移動します/My Oracle Supportへのログインが必要です)
23ai 新機能 - DBMS_STATS.EXPORT_SCHEMA_STATSおよびDBMS_STATS.EXPORT_TABLE_STATSの新しいパラメータAS_OF_TIMESTAMP
※Oracle Database 23aiは「Oracle AI Database 26ai」へ置き換えられました。

任意の時点を指定して、その時点で有効だった統計情報をリストアなしでエクスポートできます。これにより、従来必要だった「リストア → エクスポート → リストア」の手順が不要になり、一番の問題点であった現在の統計情報を変更せずに過去時点の統計情報をエクスポートできるようになりました。

AS_OF_TIMESTAMPを用いた新手順

本章では、AS_OF_TIMESTAMPを使用することで本番環境の統計情報に一切影響を与えることなく、過去の任意時点の統計情報をテスト環境へ持ち込んで性能問題を再現できることを確認します。

今回の検証では、EXPLAIN PLANを用いて、統計情報の差によって実行計画が変化する様子を再現します。なお、ここでは実行時間やI/Oを実測して性能差そのものを確認するのではなく、性能劣化が発生した状況を想定したうえで、統計情報の差がオプティマイザの選択にどのような影響を与えるかを確認します。
※実行計画に影響する要素は、オブジェクトの統計情報のほかにも、実際のデータ量や分布、索引構成、システム統計、初期化パラメータなど様々です。そのため、統計情報のみを移行した場合に、必ずしも本番環境と同一の実行計画を再現できるとは限らない点に注意が必要です。

具体的には、本番環境(PDB01)で発生した性能問題を題材に、問題発生時および通常稼働時(問題発生前)の統計情報をAS_OF_TIMESTAMPでエクスポートし、テスト環境(PDB02)へ持ち込んで実行計画を再現するまでの手順を紹介します。従来であればRESTORE_TABLE_STATSを実行して現在の統計を上書きする必要がありましたが、AS_OF_TIMESTAMPを使うことでリストアが不要となります。

検証手順1:性能劣化の発生

本番環境(PDB01)のPRODUCTS表に対するSELECT文で性能問題が発生したと仮定します。
PRODUCTS表には、100,000件のデータが存在します。price < 500を条件とする検索クエリでは、正常時にインデックスレンジスキャンが選択されていました。

    
SQL> EXPLAIN PLAN FOR
  2  SELECT product_id, product_name, price FROM products WHERE price < 500;
  
解析されました。
  
SQL> SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);
  
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------------
Plan hash value: 774836932
  
----------------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name               | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |                    |   802 | 21654 |    11   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| PRODUCTS           |   802 | 21654 |    11   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN                  | IDX_PRODUCTS_PRICE |   802 |       |     3   (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------
  
Predicate Information (identified by operation id):
---------------------------------------------------
   2 – access("PRICE"<500)
  
14行が選択されました。
  

しかし、夜間のデータ洗い替えバッチ中、一時的にPRODUCTS表のデータ量や分布が通常時とは異なる状態となっているタイミングで、統計収集が実行されたとしましょう。その後データ件数は業務時間帯の通常状態(100,000件)に戻りましたが、データが戻った後に統計収集は実施されず、統計情報は夜間バッチ中の状態を反映したままとなっています。このような状況では、オプティマイザが業務時間帯の実データとはズレた統計情報をもとに実行計画を選択してしまう可能性があります。

  
SQL> EXPLAIN PLAN FOR
  2  SELECT product_id, product_name, price FROM products WHERE price < 500;
  
解析されました。
  
SQL> SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);
  
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------
Plan hash value: 1954719464
  
------------------------------------------------------------------------------
| Id  | Operation         | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |          |   500 | 10500 |     3   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| PRODUCTS |   500 | 10500 |     3   (0)| 00:00:01 |
------------------------------------------------------------------------------
  
Predicate Information (identified by operation id):
---------------------------------------------------
   1 – filter("PRICE"<500)
  
13行が選択されました。
  

上記の例では、インデックスレンジスキャンから、索引を使用しないで全表走査するフルテーブルスキャンへと実行計画が変化しました。

夜間バッチの翌朝、検索クエリの実行が長時間化していることが判明します。問題の対処として統計情報を手動で再収集し、性能劣化自体は解決しました。

しかし、実際の現場では、この時点で「性能劣化時にどのような実行計画が選択されていたのか」や、「統計情報の変化が性能劣化の一因だったのか」は分かっていません。そこで今回は、①正常に動作していた時点、②性能劣化していた時点の2つの過去時点の統計情報をテスト環境に持ち込み、統計情報の差によって実行計画がどのように変化するかを確認します。

検証手順2:統計情報のエクスポート

まず、統計情報の収集履歴を確認します。

  
SQL> SELECT TABLE_NAME, STATS_UPDATE_TIME
  2  FROM   USER_TAB_STATS_HISTORY
  3  WHERE  TABLE_NAME = 'PRODUCTS';
  
TABLE_NAME           STATS_UPDATE_TIME
-------------------- ----------------------------------------
PRODUCTS             26-07-17 10:14:55.307667 +09:00
PRODUCTS             26-07-18 00:16:54.297802 +09:00
PRODUCTS             26-07-18 10:22:05.629765 +09:00
  

3時点の統計収集履歴が確認できました。ここでは最も古いものを正常稼働時の統計情報(問題発生前に取得)、2番目を問題発生時の統計情報(深夜帯に取得)、3番目を対処後の統計情報と考え検証していきます。

AS_OF_TIMESTAMPでエクスポート

まず、エクスポートした統計情報を格納する表を2つ作成します。

  
SQL> EXEC DBMS_STATS.CREATE_STAT_TABLE(ownname => 'TEST', stattab => 'STATS_1');
  
SQL> EXEC DBMS_STATS.CREATE_STAT_TABLE(ownname => 'TEST', stattab => 'STATS_2');
  

次に、新機能AS_OF_TIMESTAMPに取り出したい時刻を指定してエクスポートします。該当の統計情報が最新であった時間を指定します。基本的には、統計収集時刻の少し後を指定するとよいでしょう。


-- 統計① 正常時(7⁄17 10:14:55 に取得の統計情報)をSTATS_1へエクスポート 
SQL> BEGIN
  2      DBMS_STATS.EXPORT_TABLE_STATS(
  3          ownname         => 'TEST',
  4          tabname         => 'PRODUCTS',
  5          stattab         => 'STATS_1',
  6          AS_OF_TIMESTAMP => TO_TIMESTAMP_TZ(
  7                                 '2026-07-17 10:15:00 +09:00',
  8                                 'YYYY-MM-DD HH24:MI:SS TZH:TZM'
  9                             )
 10      );
 11  END;
 12  /
  
PL/SQLプロシージャが正常に完了しました。
  
  
-- 統計② 問題発生時(7⁄18 00:16:54 に取得の統計情報)をSTATS_2へエクスポート
SQL> BEGIN
  2      DBMS_STATS.EXPORT_TABLE_STATS(
  3          ownname         => 'TEST',
  4          tabname         => 'PRODUCTS',
  5          stattab         => 'STATS_2',
  6          AS_OF_TIMESTAMP => TO_TIMESTAMP_TZ(
  7                                 '2026-07-18 00:17:00 +09:00',
  8                                 'YYYY-MM-DD HH24:MI:SS TZH:TZM'
  9                             )
 10      );
 11  END;
 12  /
  
PL/SQLプロシージャが正常に完了しました。
  

エクスポートされた内容を確認します。

  
SQL> SELECT type, c1 AS table_name, n1 AS num_rows FROM stats_1 WHERE type = 'T';
  
TYP   TABLE_NAME                     NUM_ROWS
----- ------------------------------ ------------
T     PRODUCTS                        100,000
  
SQL> SELECT type, c1 AS table_name, n1 AS num_rows FROM stats_2 WHERE type = 'T';
  
TYP   TABLE_NAME                     NUM_ROWS
----- ------------------------------ ------------
T     PRODUCTS                            500
  

過去2時点の統計が正しく取り出せました。ここで、現在の統計情報が影響を受けていないことを確認します。

  
SQL> SELECT table_name, last_analyzed, num_rows
  2  FROM   user_tables
  3  WHERE  table_name = 'PRODUCTS';
  
TABLE_NAME           LAST_ANALYZED           NUM_ROWS
-------------------- ------------------- ------------
PRODUCTS             2026-07-18 10:22:05      100,000
  
SQL> SELECT index_name, last_analyzed, num_rows
  2  FROM   user_indexes
  3  WHERE  table_name = 'PRODUCTS'
  4  ORDER  BY index_name;
  
INDEX_NAME                          LAST_ANALYZED         NUM_ROWS
----------------------------------- ------------------- ----------
IDX_PRODUCTS_PRICE                  2026-07-18 10:22:05     100000
PK_PRODUCTS                         2026-07-18 10:22:05     100000
  

本番環境の現在の統計は7/18 10:22時点と最新のままとなっており、今回確認したUSER_TABLESとUSER_INDEXESのLAST_ANALYZED(統計情報が最後に収集された日時)およびNUM_ROWS(統計情報上の行数)に変化がなく、保持されていることが確認できました。

検証手順3:テスト環境へ転送

Data Pumpを使用し、テスト環境(今回の検証ではPDB02)へPRODUCTS表と統計表 STATS_1、STATS_2を転送します。
今回は検証を分かりやすくするため、PRODUCTS表内のデータもテスト環境へ転送し、本番環境で起きていた実行計画の変化を再現しやすくしました。
表のエクスポートにはexclude=statisticsを指定し、統計情報を含めないようにします。
※実行結果出力は割愛します。

  
-本番環境(PDB01)からエクスポート
[oracle@Lin97 ~]$ expdp test⁄"Test#2024"@pdb01 \
    tables=products \
    directory=data_pump_dir \
    dumpfile=products_data.dmp \
    exclude=statistics
  
[oracle@Lin97 ~]$ expdp test⁄"Test#2024"@pdb01 \
    tables=stats_1 \
    directory=data_pump_dir \
    dumpfile=stats_1.dmp
  
[oracle@Lin97 ~]$ expdp test⁄"Test#2024"@pdb01 \
    tables=stats_2 \
    directory=data_pump_dir \
    dumpfile=stats_2.dmp
  
-テスト環境(PDB02)へインポート
[oracle@Lin97 ~]$ impdp test⁄"Test#2024"@pdb02 \
    tables=products \
    directory=data_pump_dir \
    dumpfile=products_data.dmp
  
[oracle@Lin97 ~]$ impdp test⁄"Test#2024"@pdb02 \
    tables=stats_1 \
    directory=data_pump_dir \
    dumpfile=stats_1.dmp
  
[oracle@Lin97 ~]$ impdp test⁄"Test#2024"@pdb02 \
    tables=stats_2 \
    directory=data_pump_dir \
    dumpfile=stats_2.dmp
  

テスト環境(PDB02)にて、問題発生時の統計情報(STATS_2)をPRODUCTS表にインポートします。

  
SQL> BEGIN
  2      DBMS_STATS.IMPORT_TABLE_STATS(
  3          ownname => 'TEST',
  4          tabname => 'PRODUCTS',
  5          stattab => 'STATS_2'
  6      );
  7  END;
  8  /
  
PL/SQLプロシージャが正常に完了しました。
  
SQL> SELECT table_name, last_analyzed, num_rows FROM user_tables WHERE table_name = 'PRODUCTS';
  
TABLE_NAME           LAST_ANALYZED           NUM_ROWS
-------------------- ------------------- ------------
PRODUCTS             2026-07-18 00:16:54          500
  

問題発生時の統計情報を、テスト環境で再現することができました。当時の統計情報上の表の行数は、通常時と大きく乖離した500行時点の情報が取得されていたようです。
この状態で、実行計画を生成し確認します。

  
SQL> EXPLAIN PLAN FOR
  2  SELECT product_id, product_name, price FROM products WHERE price < 500;
  
解析されました。
  
SQL> SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);
  
PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------
Plan hash value: 1954719464
  
------------------------------------------------------------------------------
| Id  | Operation         | Name     | Rows  | Bytes | Cost (%CPU)| Time     |
------------------------------------------------------------------------------
|   0 | SELECT STATEMENT  |          |   500 | 10500 |     3   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS FULL| PRODUCTS |   500 | 10500 |     3   (0)| 00:00:01 |
------------------------------------------------------------------------------
  
Predicate Information (identified by operation id):
---------------------------------------------------
   1 - filter("PRICE"<500)
  
13行が選択されました。
  

上記結果より、問題発生時の統計情報をテスト環境へ反映した場合、EXPLAIN PLAN上ではフルテーブルスキャンが選択されることを確認できました。

続いて、同様に問題発生前の統計をPRODUCTS表にインポートし、実行計画を生成します。

  
SQL> BEGIN
  2      DBMS_STATS.IMPORT_TABLE_STATS(
  3          ownname => 'TEST',
  4          tabname => 'PRODUCTS',
  5          stattab => 'STATS_1'
  6      );
  7  END;
  8  /
  
PL/SQLプロシージャが正常に完了しました。
  
SQL> SELECT table_name, last_analyzed, num_rows FROM user_tables WHERE table_name = 'PRODUCTS';
  
TABLE_NAME           LAST_ANALYZED           NUM_ROWS
-------------------- ------------------- ------------
PRODUCTS             2026-07-17 10:14:55      100,000
  
SQL> EXPLAIN PLAN FOR
  2  SELECT product_id, product_name, price FROM products WHERE price < 500;
  
解析されました。
  
SQL> SELECT * FROM TABLE(DBMS_XPLAN.DISPLAY);
  
PLAN_TABLE_OUTPUT
----------------------------------------------------------------------------------------------------------
Plan hash value: 774836932
  
----------------------------------------------------------------------------------------------------------
| Id  | Operation                           | Name               | Rows  | Bytes | Cost (%CPU)| Time     |
----------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                    |                    |   802 | 21654 |    11   (0)| 00:00:01 |
|   1 |  TABLE ACCESS BY INDEX ROWID BATCHED| PRODUCTS           |   802 | 21654 |    11   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN                  | IDX_PRODUCTS_PRICE |   802 |       |     3   (0)| 00:00:01 |
----------------------------------------------------------------------------------------------------------
  
Predicate Information (identified by operation id):
---------------------------------------------------
   2 - access("PRICE"<500)
  
14行が選択されました。
  

正常時の統計情報を反映した場合には、EXPLAIN PLAN上ではインデックスレンジスキャンが選択されることも確認できました。この結果から、データが500件の状態で統計収集されたため、実行計画がインデックスレンジスキャンからフルテーブルスキャンに変更されていたことがわかりました。これが性能劣化の原因であった可能性を切り分けられました。

AS_OF_TIMESTAMPを利用することで、このような調査を本番環境の現在の統計情報を直接書き換えることなく実施できる点が、本機能の大きなメリットといえます。

まとめ

本記事では、Oracle AI Database 26aiで追加された DBMS_STATS.EXPORT_TABLE_STATSのAS_OF_TIMESTAMPパラメータを用いた統計情報のエクスポート手順を紹介しました。

従来は、過去の統計情報を取り出すためにRESTORE_TABLE_STATSにより現在の統計情報を上書きする必要がありました。今回の検証から、AS_OF_TIMESTAMPを使えばその手順が不要になることを確認できました。本番環境の統計情報に影響を与えることなく、過去の任意時点の統計情報を安全に取り出せるこの機能は、性能劣化の調査やテスト環境での実行計画再現において特に有用です。

性能問題の調査や統計情報の管理に課題を感じている方は、ぜひお試しください。


執筆者情報

ささき しゅうま

アシスト
2024年新卒入社し、Oracle Databaseのサポートセンターに配属。ジョブローテーションにてフィールド業務を経験した後、現在は Oracleサポートセンターに復帰しサポート業務を担当。アシストで勤務する傍ら囲碁インストラクターとしても活動。
YouTubeチャンネルを運営しており、著書を2冊出版。...show more


■本記事の内容について
 本記事に記載されている製品およびサービス、定義及び条件は、特段の記載のない限り本記事執筆時点のものであり、予告なく変更になる可能性があります。あらかじめご了承ください。

■商標に関して
 ・Oracle®、Java及びMySQLは、Oracle、その子会社及び関連会社の米国及びその他の国における登録商標です。
 ・Amazon Web Services、AWS、Powered by AWS ロゴ、[およびかかる資料で使用されるその他の AWS 商標] は、Amazon.com, Inc. またはその関連会社の商標です。
  文中の社名、商品名等は各社の商標または登録商標である場合があります。

関連している記事

  • Oracle Database
2026.08.21

【Oracle AI Database 26ai】DBAの相棒はSQLからAgentへ?Select AI Agentでトラブルシュートを加速してみた

Oracle AI Database 26aiのSelect AI Agentをオンプレミス環境で検証。アラートログを分析し、参照ナレッジに基づく対処案を提示する、DBAのトラブルシュート支援の仕組みを解説します。

  • Oracle Cloud
  • Oracle Database
  • AWS
2026.08.04

オラクルユーザー必見!Oracle AI Database@AWSとは?サービス概要とメリットを解説

OCIのOracle AI DatabaseをAWSから利用できる「Oracle AI Database@AWS」について、サービス概要と導入メリットをわかりやすく解説します。マルチクラウド環境でのオラクル活用に関心のある方は、ぜひご活用ください。

  • Oracle Cloud
  • Oracle Database
2026.07.10

気になるテーマから学べる!無料オンデマンド動画「OCI入門者向けウェビナー」のご紹介

OCIを何から学べばよいか迷っている方に向けて、無料オンデマンド動画「OCI入門者向けウェビナー」をご紹介します。全5回の概要とおすすめの視聴ルートを通じて、OCIの基本や強み、データベース、セキュリティ、VMware移行のポイントを分かりやすくご案内します。

ページの先頭へ戻る