17.00 - 17.05 - AMPソフトウェアが統計を収集する方法 - Advanced SQL Engine - Teradata Database

Teradata Vantage™ - SQLリクエストおよびトランザクション処理

Product
Advanced SQL Engine
Teradata Database
Release Number
17.00
17.05
Published
2020年6月
Content Type
プログラミング リファレンス
ユーザー ガイド
Publication ID
B035-1142-170K-JPN
Language
日本語 (日本)
統計およびデモグラフィックを収集する際に、AMPソフトウェアによって、COLLECT STATISTICS文で指定されるテーブルの列を要約するさまざまな統計およびデモグラフィックを格納するDBC.StatsTblに統計行が作成されます。これらの行には次の4つの基本的な種類があります。
  • 正則統計の行(AMP当たり1行以上)
  • NULLの行(AMP当たりゼロまたは1行)
  • 全NULLの行(AMP当たりゼロまたは1行)
  • 値ごとの平均行数(AMP当たり1行)

これらの行の情報は、最適化ルーチンが初期カーディナリティを見積もる際に使用する統計間隔ヒストグラムを読み込む際に使用されます(間隔ヒストグラムを参照)。

次の統計はヒストグラムにおいてグローバルであり、1度だけ報告されます。
  • nullsの行の数
  • 全nullの行の数
  • 各AMPにおける、値ごとの平均行数の総平均

これらの統計はHELP STATISTICSリクエストを使用するとレポートできます(HELP STATISTICS文の詳細については、<Teradata Vantage™ - SQLデータ定義言語-構文規則および例、B035-1144>の「HELP STATISTICS」を参照)。HELP STATISTICSは、サマリー統計のみをレポートすることに注意してください。詳細統計をレポートするには、SHOW STATISTICSリクエストを実行する必要があります。

Nullおよび全Nullの統計とデモグラフィック

nullおよび全null統計では、次のカーディナリティ情報が提供されます。

統計 定義
NumNulls 1つ以上の列がnullに設定されている列セット内の行数
NumAllNulls 列のすべてがnullに設定されている複合列セット内の行数

Teradata Databaseは、複合セット内のすべての列がNULLの場合のみ、複合列セットのNumAllNulls統計を収集します。つまり、NumAllNulls値を収集できるのは、複数列統計を収集する場合か、複合インデックスで統計を収集し、そのセット内のどの列にも値が含まれていない場合のみです。

以下の例では、null行および全nullの行によって何が示されるのかについて説明しています。テーブルt_exの列、b、c、およびdに3列のUSI (Unique Secondary Index: 固有セカンダリ インデックス)を作成するとします。USIで統計を収集すると、USIを構成する3列すべてがnullである1行が検出されます。これは、統計ではall-nullsのインスタンスとして報告されます。テーブル内に、すべての列がnullであるUSIを含む行が複数存在することはありません。このため、この例は不自然ですが、一般的なものではないとしても例としては有用です。

この行の列は次のように表示され、nullは疑問符で表わされます。

t_ex          
a b c d e f
PI USI    
355 ? ? ? 25000.00 375000.00

次の例は複合列セットで統計が収集されている場合とは対称的で、特定の行についての統計が収集されている列のうち、1列以上(全列である必要はない)がnullです。

前述のテーブルとインデックス定義があるとします。USIで統計を収集するとき、USIを構成する3列のうち1列または2列(すべてではない)がnullである行は、統計ではnull列の発生として報告されます。

t_ex内の次の行のすべてがnullフィールドとして報告されます。これは、すべての行でUSI列のいずれかに1つ以上のnullが存在しているためです。一方、最初の行のみが、全nullフィールド統計に報告されます。

t_ex            
a b c d e f  
PI USI      
355 ? ? ? 25000.00 375000.00 <--   all-null fields (tan)
             
685 ? 155 ? 45000.00 495000.00 <--   null fields (orange)
900 325 ? ? 32000.00 400000.00
275 ? ? 760 55000.00 575000.00
597 ? 254 893 10000.00 150000.00
322 679 ? 204 75000.00 650000.00
781 891 357 ? 32000.00 400000.00

全NULLとは、統計が複合列セットで収集されており、特定の行の、その統計が収集されているすべての列がNULLである場合を意味します。この情報を使用することにより、最適化ルーチンはセット内の固有値の実際の数をより正確に見積もることができ、結合の計算をより正確に行なうことができます。

例えば、テーブルt1に列x1およびy1があり、x1とy1に値があるとします。

  t1      
  x1 y1    
  10 10    
         
  20 ?   <--      null fields (orange)
  ? 30    
         
  ? ?   <--      all-null fields (tan)
  ? ?    

この場合も、全nullの複合フィールドはオレンジで、部分的にnullの複合フィールドは緑になっています。

NumNulls統計のみを収集できる場合で、複合列で統計を収集し、1列以上がnullだった場合、その行はnull行としてカウントされます。これは単一テーブルのカーディナリティの見積もりについての問題ではありません。nullに対する比較は常にFALSEとして評価され、非一致行として処理されるからです。

例えば、NumNulls統計しか収集できない場合、(x1, y1)上のヒストグラムでは、nullの数が4になり、固有値の数が2になり、行の総数が5になります。列のxとyが結合列として使用されている場合は、最適化ルーチンで本当は4つあるデータ内の固有値を2つしかないと誤って仮定して結合原価計算が評価されることになります。これにより、再配置コストやスキュー検出などのシナリオで問題が生じる可能性があります。

この問題を回避するために、Teradata Databaseは固有の部分的NULLの実際の数を計算し、それをNumPNullsとしてヒストグラムに保存します。

Teradata Databaseは次の計算式を使用して、部分的NULLの実際の固有値を計算します。


部分的NULLの実際の固有値の式
等式要素 説明
NumUniqueValues 部分的null行の実際の固有値の数。
NumValues 部分的null行の非固有値の数。
NumPNullValues 部分的null行間での実際の固有値の数。
1 すべてのnullのカーディナリティ>0の場合の、見積もりエラーの調整。

この計算を行なうため、Teradata Databaseはマーカーを差し込み、次の集約クエリーを使用して(x1,y1)に対する統計を収集します。次のリクエストでは、 null_markerのfieldIDは2に設定され、cntのfieldIDは3に設定されます。

SELECT CASE
           WHEN x1 IS NULL
           AND  y1 IS NULL
           THEN 1
           ELSE IF x1 IS NULL
           OR
                   y1 IS NULL
           THEN 2
       ELSE 0
       END AS null_marker ,x1, y1, COUNT(*) AS cnt
FROM t_coll_stats
GROUP BY 1, 2;

サンプル統計

サンプル統計を収集するために、Teradata Databaseはサンプリングを実行する取得リクエストを生成します。その後で、サンプリングされた行を後続の集約ステップに渡します。テーブルがハッシュ分散されていて、固定順序のテーブルかNoPIテーブルの任意のの場合、取得リクエストではTOP n操作を使用して行を取得できます。RowKey値に基づいてソートされているPPIテーブルについては、Teradata Databaseは実際のサンプル ステップを生成できます。

パーティション統計

集約ステップでは、次のように、クエリーにパーティション番号とカーディナリティを使用して詳細行を作成します。

SELECT PARTITION, COUNT(*)
FROM t_coll_stats;

UDF統計

確定的UDFに関する統計は収集できますが、不確定的UDFに関する統計は収集できません。

次のような確定的UDFを考えてみましょう。

CREATE FUNCTION months_between(date1 TIMESTAMP, date2 TIMESTAMP)
RETURNS FLOAT
LANGUAGE C
NO SQL
DETERMINISTIC
SPECIFIC months_between_tt
EXTERNAL NAME 'CS!months_between_tt!$PGMPATH$/months_between_tt.c'
PARAMETER STYLE SQL;

以下の文は、UDF months_between()に関する統計を収集します。

COLLECT STATISTICS
COLUMN months_between(BEGIN(policy_duration),
                      END(policy_duration)) AS 
                      Stats_MthsBetweenBegAndEnd
ON policy_types;
UDF統計の極めて重要な課題は、それらの収集ではなく、それらの削除です。確定的UDFに関する統計の収集後にそのUDFを削除すると、その統計が使用不能になります。UDFはどのテーブルまたは列にも属していないため、それらに関して収集されたすべての統計を削除するには多くのコストがかかります。その代わり、Teradata Databaseでは、次のような、確定的UDFに関して収集された統計を削除するための冗長なアプローチが使用されます。
  • Teradata DatabaseがテーブルのDBC.StatsTblから統計ヘッダー情報をロードしてユーザー問合わせまたは別の統計関連文を処理する場合は、その問合わせが解決されたときにシステムで無効な式が検出されます。

    解決不可能な式に対して収集された統計の場合は、DBC.StatsTbl内の対応する統計に関するvalidStatsフィールドがFALSEに更新されます。

  • ヒストグラムを取得する場合、Teradata Databaseは、有効な統計のもののみを取得します。これにより、問合わせ処理時間中に無効な統計を解析するオーバーヘッドが最小化されます。
  • SHOW STATISTICS文とHELP STATISTICS文は有効な統計しか報告しません。
  • テーブルに対するDROP STATISTICSリクエストは、Teradata Databaseに有効と無効の両方の統計をすべて削除するように強制します。
  • SHOW STATISTICS … COLUMNリクエストは、無効な統計に対して実行された場合に警告メッセージを返します。
  • DROP STATISTICS … COLUMNリクエストは、それが無効な統計に対して実行された場合に警告メッセージを返します。
  • UDFに関する統計を再収集すると、その時点でそのUDFが存在していれば、その統計が再検証されます。
  • DBC.StatsVシステム ビューに問合わせて、validStats列をチェックすることによって、無効な統計を判断できます。

    個別の無効な統計は削除できません。

    無効な統計を削除するには、テーブルに関する全ての統計を削除してから、有効な統計を再収集する必要があります。

  • UDFを削除してから、それを再作成して別の型を返すと、Teradata Databaseは最適化中にその関数に関する既存の統計を無視します。

値ごとの平均AMP行数の統計

値ごとの平均AMP行数の統計は、統計が収集されているNUSI列セットの値ごとの行数(RPV)における、各AMPの値ごとのカーディナリティについてのシステム全体の正確な平均数です。この統計はNUSI列についてのみ計算され、ネストされている結合のコスト計算に使用されます。

初回統計収集のEXPLAINテキストの例

次のEXPLAINテキストは、ヒストグラムを作成し、テーブルt1から列x1の統計収集を初めて実行するときにTeradata Databaseが取るステップのレポートです。このレポートに埋め込まれたコメントは、プロセスの重要なステップで何が起こるかを明確に示すためのものです。

EXPLAIN COLLECT STATISTICS COLUMN x1 ON t1;
Explanation
-----------------------------------------------------------------------
  1) First, we lock QSTATS.t1 for access.
  2) Next, we do an all-AMPs SUM step to aggregate from DF2.t1 by way 
     of an all-rows scan with no residual conditions,
     grouping by field1 ( RK.t1.x1). Aggregate Intermediate Results
     are computed locally, then placed in Spool 9. The size of Spool 9
     is estimated with low confidence to be 2 rows (44 bytes). The
     estimated time for this step is 0.03 seconds.
  3) We do an all-AMPs RETRIEVE step from Spool 9 (Last Use) by way of
     an all-rows scan into Spool 5 (all_amps), which is built locally
     on the AMPs. The size of Spool 5 is estimated with low confidence
     to be 2 rows (52 bytes). The estimated time for this step is 0.04
     seconds.
  4) Then we save the UPDATED STATISTICS from Spool 5 (Last Use) into
     Spool 3, which is built locally on the AMP derived from
     DBC.StatsTbl by way of the primary index. /*Hash of t1*/
  5) We lock DBC.StatsTbl for write on a RowHash.
   /*Raise a retryable error if the generated stats id is already 
   used*/
   /*by some other collect stats on the same table.*/
   /*The error step can also be done by USI on (TableId, StatsId)*/
  6) We do a single-AMP ABORT test from DBC.StatsTbl by way of the
     primary index "DBC.StatsTbl.TableId = <t1 Id> with a residual
     condition of ("DBC.StatsTbl.StatsId = <generated stats id>").
   /*Insert the histogram row with generated stats id*/
  7) We do a single-AMP MERGE into DBC.StatsTbl from Spool 3 (Last 
     Use). The size is estimated with low confidence to be 1 row. The
     estimated time for this step is 0.31 seconds.
   /*Update or insert the master record with the updated table level 
    demographics*/
  8) We do a single-AMP UPDATE from Spool 3 by way of the primary
     index "DBC.StatsTbl.TableId = <t1 Id>" with a residual condition 
     of DBC.StatsTbl.StatsId = 0. The size is
     estimated with low confidence to be 1 row. The estimated time for
     this step is 0.02 seconds. If the row cannot be found, then we do
     an INSERT into DBC.StatsTbl.
  9) We spoil the parser's dictionary cache for the table.
  10) Finally, we send out an END TRANSACTION step to all AMPs involved
     in processing the request.
  -> No rows are returned to the user as the result of statement 1.
このEXPLAINテキストを別の観点から見ると、次のようなことがわかります。
  • ステップ7で、Teradata Databaseは作成されたStatsIdを持つヒストグラム行を挿入します。
  • ステップ8では、Teradata Databaseは固定StatsIdが0の新しいテーブル レベルのデモグラフィックで、マスター レコードを更新しています。
  • マスター レコードは、常に固定StatsIdが0であることに注意してください。

統計再収集のEXPLAINテキストの例

次のEXPLAINテキストは、テーブルt1から列x1の統計を再収集するときにTeradata Databaseが取るステップのレポートです。このレポートに埋め込まれたコメントは、プロセスの重要なステップで何が起こるかを明確に示すためのものです。

     EXPLAIN COLLECT STATISTICS COLUMN x1 ON t1;
Explanation
-----------------------------------------------------------------------
  1) First, we lock QSTATS.t1 for access.
  2) Next, we do an all-AMPs SUM step to aggregate from RK.t1 by way of
     an all-rows scan with no residual conditions,
     grouping by field1 ( RK.t1.x1). Aggregate Intermediate Results
     are computed locally, then placed in Spool 9. The size of Spool 9
     is estimated with low confidence to be 2 rows (44 bytes). The
     estimated time for this step is 0.03 seconds.
  3) We do an all-AMPs RETRIEVE step from Spool 9 (Last Use) by way of
     an all-rows scan into Spool 5 (all_amps), which is built locally
     on the AMPs. The size of Spool 5 is estimated with low confidence
     to be 2 rows (52 bytes). The estimated time for this step is 0.04
     seconds.
  4) Then we save the UPDATED STATISTICS from Spool 1 (Last Use) into
     Spool 3, which is built locally on the AMP derived from
     DBC.StatsTbl by way of the primary index.
  5) We lock DBC.StatsTbl for write on a RowHash. /*Hash of (t1)*/
  6) We do a single-AMP UPDATE from Spool 3 by way of the primary
     index "DBC.StatsTbl.TableId = <t1 Id>" with a residual condition 
     of DBC.StatsTbl.StatsId = <existing stats id>. The size is
     estimated with low confidence to be 1 row. The estimated time for
     this step is 0.02 seconds. If the row cannot be found, then we do
     an INSERT into DBC.StatsTbl.
  7) We do a single-AMP UPDATE from Spool 3 by way of the primary
     index "DBC.StatsTbl.TableId = <t1 Id>" with a residual condition 
     of DBC.StatsTbl.StatsId = 0. The size is
     estimated with low confidence to be 1 row. The estimated time for
     this step is 0.02 seconds. If the row cannot be found, then we do
     an INSERT into DBC.StatsTbl.
  8) We spoil the parser's dictionary cache for the table.
  9) Finally, we send out an END TRANSACTION step to all AMPs involved
     in processing the request.
-> No rows are returned to the user as the result of statement 1.
このEXPLAINテキストを別の観点から見ると、次のようなことがわかります。
  • ステップ6で、Teradata Databaseは既存のStatsIdを持つヒストグラム行を更新します。
  • ステップ7では、Teradata Databaseは固定StatsIdが0の新しいテーブル レベルのデモグラフィックで、マスター レコードを更新しています。
  • マスター レコードは、常に固定の統計IDが0であることに注意してください。