web-dev-qa-db-ja.com

同一のテーブル、同一のクエリ、完全に異なる実行時間

同一の列とインデックスを持つ2つのテーブルがあります(基本的にインデックスはありません)。同じクエリを実行します。元のテーブルの場合、実行に5秒かかります。新しいテーブルの場合、30分間実行してからクエリを強制終了しました。

統計を更新しましたが、結果はありませんでした。新しいテーブルを再構築して、最適化が役立つかどうかを確認しましたが、効果もありませんでした。

直感的に、そこで何も変更されないかどうかを確認するために、両方のテーブルを同じデータベースにエクスポートしましたが、まったく同じ結果が得られました。

私はこれがどのようになり得るのか、ちょっと困惑しています。さらに興味深いことに、元のテーブルには新しいテーブルよりも多くのデータが含まれています。これは、理論的には新しいテーブルがクエリをより速く終了することを意味します。

誰かが可能な説明を持っていますか?私はDBAとして何年も働いてきましたが(今は数年ではありません)、率直に言って、なぜこれが起こり得るのかについて困惑しています。

一部のコメントへの回答として、問題のテーブル定義は次のとおりです。

CREATE TABLE [dbo].[Fact_SubscriptionDetail_Test](
[CustomerSellTo_Key] [int] NULL,
[CustomerContactSellTo_Key] [int] NULL,
[CustomerRefTo_Key] [int] NULL,
[WebAuthUser_Key] [int] NULL,
[Country_Key] [int] NULL,
[Date_Key] [int] NULL,
[SnapshotDate_Key] [int] NULL,
[Product_Key] [int] NULL,
[License_Key] [int] NULL,
[Subscription_Key] [int] NULL,
[SubscriptionTypeLostSeatsType_Key] [int] NULL,
[M_SubscriptionDetail_NewSeats] [int] NULL,
[M_SubscriptionDetail_WinbackSeats] [int] NULL,
[M_SubscriptionDetail_RenewedFlexSeats] [int] NULL,
[M_SubscriptionDetail_RenewedCommitSeats] [int] NULL,
[M_SubscriptionDetail_RenewedSeats] [int] NULL,
[M_SubscriptionDetail_ActiveSeatsEndMonth] [int] NULL,
[M_SubscriptionDetail_LostNonPaymentSeats] [int] NULL,
[M_SubscriptionDetail_LostGraceInactiveSeats] [int] NULL,
[M_SubscriptionDetail_LostOtherSeats] [int] NULL,
[M_SubscriptionDetail_LostSeats] [int] NULL,
[M_SubscriptionDetail_GrossBookings] [numeric](38, 20) NULL,
[M_SubscriptionDetail_ActiveFlexSeats] [int] NULL,
[M_SubscriptionDetail_ActiveCommitSeats] [int] NULL,
[M_SubscriptionDetail_ActiveSeats] [int] NULL,
[DateCreated] [datetime] NULL 
    CONSTRAINT [DF__Fact_Subs__DateC__gtfhjCC]  DEFAULT (getdate())
) ON [DATA]

2つのテーブルのデータは完全に同一ではありませんが、性質は非常に似ています。このクエリで実行するクエリは、他のいくつかのテーブル(主にデータウェアハウスディメンション)に関連付けられます。

単純に実行することで、デフラグを実行しました(同意するが、ヒープにはあまり意味がありませんが、害はないと考えました)。

ALTER TABLE Fact_SubscriptionDetail_Test REBUILD. 

最初にクエリプランを確認したとき、Testテーブルにインデックスを追加することを提案しました(ただし、元の高速なテーブルは追加していません)。また、テストテーブルにクラスター化インデックス(PK)を追加しようとしましたが、それも実行プランによる推奨インデックスも効果がありませんでした。

実行計画は次のとおりです。

http://Pastebin.com/XagvSxjj (元のテーブル、高速)
http://Pastebin.com/LSgCsvUe (新しいテーブル;遅い)

実行パスに違いがあり、元のテーブルのデータのカーディナリティーは多少良いと思います。元のテーブルには約1.4 mio行(230 MB)が含まれており、そのうちの400kはクエリによって処理されます。新しいテーブルには、400,000行(52 MB)が含まれています。

これは完全なクエリです: http://Pastebin.com/bYiaGW1d (わずかに編集して機密情報を削除します)。

サーバーの「並列処理のコストしきい値」の値は5です。

4
SchmitzIT

...元のテーブルには新しいテーブルよりも多くのデータが含まれています。これは理論的には、新しいテーブルがクエリをより速く終了することを意味します。

実行計画が同じである場合、これは真実である可能性が高いですが、そうではありません。予想される行数(および統計によるデータの分布)は、クエリオプティマイザーによって選択される戦略に影響します。

小さいテーブル

ヒープテーブルに398,399行が含まれている場合、オプティマイザは、ヒープテーブルに直接影響するものを除くすべての結合操作に対して、ネストされたループを使用してシリアルプランを選択します。これらの結合はハッシュ結合を採用しています。

Small table plan

クエリは複雑であるため、カーディナリティ(行数)の推定値は不正確になる可能性が高く、ネストされたループ戦略は最終的には惨事になります。オプティマイザは並列プランを検討しましたが、シリアルネストループオプションよりもコストがかかるため拒否しました。

大きなテーブル

ヒープテーブルに1,750,640行が含まれている場合、コスト見積もりの​​変更は、オプティマイザが並列計画を使用して ハッシュ結合を評価し、最適化したことを意味しますビットマップフィルター は、より優れた戦略になります。

Larger plan

この計画の形状は、カーディナリティー推定エラーに対してはるかに回復力があります。ハッシュ結合ビルド入力はディスクに流出する可能性がありますが、最悪の場合は、(ネストされたループを使用して)サブツリー全体を膨大な回数実行するよりもはるかに優れています。

解決策1

一般に、ビットマップフィルターを使用した並列プランが最良の選択であることがわかっている場合は、プランガイドを使用してこれを強制するか、OPTION (HASH JOIN, MERGE JOIN)クエリヒントを使用してそのようなプランを推奨できます。テーブルが小さい場合は、常に並列処理が行われるとは限りませんが、パフォーマンスは依然として向上するはずです(一般的には予測可能性が高くなります)。

解決策2

また、最初は共通テーブル式の境界線に沿って、クエリをより簡単なセクションに分割することもできます。適切なサイズの中間結果を一時テーブルに具体化することは、次のことを意味します。

  • クエリの各部分は個別に最適化できます
  • オプティマイザには統計と正確なカーディナリティ情報があります
  • 有益であることが証明された場合、一時テーブルに追加のインデックスを追加できます。
  • 冗長なCTE評価はスキップできます
4
Paul White 9