行数にわずかな違いがあるほとんど同じテーブルがあります。 1つは2012年、もう1つは2016年です。インデックス作成は同じです。これらのVMは、OSとSQL Serverのバージョンがアップグレードされたまったく同じ環境にあります。同じ数のvcore、同じメモリ、同じサーバー設定(並列処理の最大次数= 8、並列処理のコストしきい値= 30)。
このドロップデッドシンプルクエリは、単一の列をフィルターに使用し、単一の列を返します。 whereフィルターの列は、インデックスの唯一の列です。
2016バージョンには8254356行あります
2012バージョンには8254427行あります
それらは同じクエリです。 2016にはインデックスがありません。明確な理由もなく、フルテーブルスキャンを実行しています。 2012は、インデックススキャン後にテーブルでRIDルックアップ(ヒープ)を実行します。
2016サーバーでWITH (index = CONTACT_RC_NUI1)
を試したところ、コストが991から1889に急上昇しました。2012年のコストは29でした。
AND 1 = (SELECT 1)
を追加してみましたが、違いはありませんでした。OPTION (RECOMPILE)
を使用して、可能性のある問題としてパラメータスニッフィングを削除しようとしましたが、違いはありませんでした。
DBAはデータベースの復元後にインデックスの再構築を実行しました。どちらのサーバーにもかなり最近のインデックス統計の更新がありました(Olaのインデックス更新スクリプトを実行しています)。また、2016年のExplainプランに影響を与えなかった2016年のインデックスを再構築したことを確認します。
クエリに以下のヒントを追加しました...
_select address1_stateorprovince
from dla.dcrm.CONTACT_RC WITH (index = CONTACT_RC_NUI1)
where wv_partyid = 343083;
_
その結果、2012年のExplainプラン(2016年は並列処理(ストリームの収集)が追加されただけ)とほぼ同じであるにもかかわらず、991から1889にコストがかかりました。
2016が行っているように見えるのは、インデックスのコストは1%だけですが、RIDルックアップは99%です。 2012年にこれは逆転した。 2016はインデックスを使用してすべてのエントリをスキャンし、テーブル内のすべてのRIDを検索したようです。それは本当だろうか? 2016年のオプティマイザーは非常に強力なものを喫煙していると思います。
wv_party_id
_はnvarchar(100)
ですSQL Server 2014以降、SQL Serverに 新しいカーディナリティエスティメータ があるため、異なる計画が表示されます。そして、新しいCEにいくつかの SQL Server 2016の新機能 を追加しました。
最初に、表示されているものを再現するためのいくつかのテストデータ。
_create table dbo.T(C1 char(10) default '', C2 varchar(11));
go
insert into dbo.T(C2)
select top(800000) row_number() over(order by (select null))
from sys.columns as c1, sys.columns as c2, sys.columns as c3
go
create index IX_T_C2 on dbo.T(C2)
_
また、同じバージョンのSQL Serverでそれらを比較できるように、2つの異なるプランを生成するクエリ。
_-- Table scan version
select C1
from dbo.T with (index = 0)
where C2 = 100000
option (maxdop 1);
-- Index Scan version
select C1
from dbo.T with (index = IX_T_C2)
where C2 = 100000
option (maxdop 1);
_
SQL Server 2012のテーブルスキャンバージョンは、すべての行をスキャンして1つを返します。そこに驚きはありません。
SQL Server 2012のインデックススキャンバージョンは、インデックス内のすべての行をスキャンし、1つの行を返します。さらに調査する必要があるものがいくつかありますが、今のところは、インデックススキャンオペレーターの推定行数をさらに調べる必要があります。
SQL Server 2016のテーブルスキャンバージョンは2012年のバージョンと同じです。すべての行をスキャンして1行を返します。
インデックススキャンバージョンは2012年と同じように見えますが、コストははるかに高くなります。これは、推定行数が2012年よりもはるかに高いためです。
そのため、SQL Serverは1行を返すために80000 RID Lookupを実行する必要があると考えています。そのため、SQL Server 2016で新しいカーディナリティエスティメータを使用してテーブルスキャンを選択します。
新しい推定器は、述語where CONVERT_IMPLICIT(int,C2) = 100000
を見て、あきらめます。これは、等価述語に対して10%の選択性という標準のguessを使用します。800,000行の10%= 80,000です。元の推定器は、より複雑なロジックを使用して、1行の推定ではない(正確な)推定値を生成しました。
次に、インデックススキャンの問題について説明します。それはおそらくあなたが望むものではありません。 SQL Serverに、インデックスシークを実行させて、探している行を見つけてもらいます。現在、SQL Serverはそれを行うことができません。なぜなら、where句に型の不一致があり、クエリプランでそれに関する警告が表示されるためです。これを修正すると、SQL Serverの両方のバージョンで、インデックスシークとRIDルックアップを含むプランが表示されます。
実行プランのコストの割合は、実際の実行時の情報ではなく、常にオプティマイザの見積もりに基づいていることにも注意してください。
エリックとミカエルによるこの質問に対する思慮深い回答に感謝します。完全な答えは、両方の回答といくつかの追加情報と私が実施したテストの結果の組み合わせにあると思います。ここにある他のブログとstackexchangeに関する別のメモ カーディナリティエスティメータの問題 も参考になります。
要約すると、新しいカーディナリティは非常に賢いものだと思いますが、Erikが言うように、それは推定された計画に依存しています。そして、その計画が間違っていれば、当然の結果は間違っています。私の場合、1つのクエリで致命的な誤りがあり、数分ではなく数時間にわたって定期的に実行されていました。暗黙のデータ型変換が含まれている場合(ご存じのとおり)、計画は最も不正確に思われます。
したがって、私が発見したのは、データウェアハウスの負荷で最も一般的な問題は、2016年の計画がクエリを並列ではなく直列に実行するように指示した場合です。もちろん、これを修正するには、データベースを古いカーディナリティエスティメータ(推奨されません)に切り替えるか、ブレントのスタッフが最近ポッドキャストで言ったように、ケースバイケースで問題を取り除くようクエリにヒントを与えます。
もちろん、オプション(querytraceon 9481)を使用できますが、これには昇格されたアクセス許可が必要です。特に通常のヒントで問題が解決する場合は、querytraceを使用する前に常にためらいます。
私の場合、私はOPTION(USE HINT( 'ENABLE_PARALLEL_PLAN_PREFERENCE'))を使用しましたが、これは私の最大の問題クエリのターボボタンのようなものであり、シリアルで実行されることがあるという問題を解消するようです。