SQL Server 2012オプティマイザーは正しく機能しません。
テストケース、概要:
これは簡略化されたテストシナリオです。下部にあるDDLステートメント。
データロギング用の2つのテーブルA
とB
があります。 1:nの関係があります-A
にはa_time
という日時のヘッダーレコードがあり、B
には詳細レコードがあり、フィールドB.akey
はA.id
を参照しています、およびフィールドname
および(data)
。
Aは約25,000,000レコード、B
には近似値があります。 500,000,000レコード。 B
には、A
の各レコードを参照する約200のレコードがあります。 1つのA
と約200個のB
レコードが5分ごとに一度に一緒に挿入され、A.a_time
によって反映されます。
クラスター化インデックスは、主キーid
、タイプint identityです。
B
には、IX_B_akey
にB.akey
という名前の1つの非クラスター化インデックスがあります。
A.a_time
も(非クラスター化)インデックス化されています。
今、このクエリ:
SELECT A.a_time, B.*
FROM B
join A on B.akey = A.id
where
A.a_time > '2017-01-13T01:30:00' and A.a_time < '2017-01-14T07:30:00'
and B.name in ('name33', 'name55', 'name66')
データベースサーバーで約3分かかります。実行計画: ここ (より正確な実行計画については以下を参照)
IX_B_akey
を使用するための簡単なヒントを追加すると:
SELECT A.a_time, B.*
FROM B
with (index(IX_B_akey))
join A on B.akey = A.id
where
A.a_time > '2017-01-13T01:30:00' and A.a_time < '2017-01-14T07:30:00'
and B.name in ('name33', 'name55', 'name66')
1秒未満で実行されます。実行計画: ここ (より正確な実行計画については以下を参照)
両方のテーブルで手動でupdate statistics
を実行しても、これは変わりません。
ヒントのないクエリのクエリプランは、サーバーがB
でテーブルスキャンを実行し、一致するname
sを探すことを示しています。これに時間がかかるのは当然のことです。ヒントを使用して、インデックスを使用し、一致するB
レコードを参照するA
レコードのインデックスを介したルックアップを実行します。これははるかに高速です。
クエリオプティマイザーコードをソフトウェアに挿入したくありません。また、NHibernateを使用しています。可能ですが、NHibernateインターセプターを使用してそのSQLを編集するのは醜いでしょう。
多分オプティマイザは、1つのB
レコードを参照するすべてのA
レコードが物理的に互いに隣接していることを認識していません。同時に挿入されているため、互いに隣接しています。それらがデータベース全体に散在している場合、すべての検索を実行する方がコストがかかる可能性があります。
質問:オプティマイザがクエリのヒントなしで高速プランを選択できるようにするにはどうすればよいですか?ここで役立つ特定の統計を追加できますか?保存されたクエリプランは必要ですか?
参考までに、テーブルの作成に使用されるDDLステートメントを次に示します。
create table A (
id int not null identity(1,1),
a_time datetime,
constraint pkA primary key (id)
)
create table B (
id int not null identity(1,1),
akey int not null references A (id),
name nvarchar(50),
d decimal(5,3),
constraint pkB primary key (id)
)
create index IX_B_akey on B (akey)
create index IX_A_a_time on A (a_time)
更新:name
をインデックスIX_B_Akey
に追加すると効果的ですが、データ量がほぼ2倍になります。これは良いオプションではありません。
実行計画の更新:質問を投稿した後、同じデータ構造でより多くのデータを持つ別のテストシナリオを作成しました。クエリは同じですが、クエリされる日付範囲が拡張されています。データベースには、Aに1つのmioレコード、Bに200のmioレコードが含まれています。これにより、実際の実行計画を提供できます。
悪い計画は難しい選択から来ます。 2つのネストされたループ結合を持つプランと大きな並列ハッシュ結合を持つプランのどちらかをオプティマイザに選択させる代わりに、Bを再編成して、AからBへのアクセスパスを最適化できます。
ここでの最良のインデックスは、Bのクラスター化されたPK(akey、id)を作成することです。その場合、すでに高速の計画にはネストされたループ結合が1つだけあり、並列ハッシュ結合計画よりも明らかに優れています。
計画を強制することもできますが、NHibernateが常にまったく同じクエリを生成することを前提としています(そうすることもできます)。 B)に対して4万2千回の操作を行う必要があるとは考えていません。
問題は、それらのRIDルックアップのコストが非常に高くなることを期待し、スキャンの予想コストが少なくなることです。あまり多くのRIDルックアップを実行する必要がないと説得した場合は、統計を偽造しているため、より適切な計画が得られる可能性があります。ただし、このクエリ以外にも影響を与える可能性があるため、これは一般的に危険です。
だから私は最初に計画を強制することを試みるでしょう。