SQL Serverが単純なバイジェクションでインデックスを使用できない


11

これは別のクエリオプティマイザーの難問です。

たぶん私はクエリオプティマイザーを過大評価しているのかもしれませんし、何か不足しているかもしれません。

シンプルなテーブルがあります

CREATE TABLE [dbo].[MyEntities](
  [Id] [uniqueidentifier] NOT NULL,
  [Number] [int] NOT NULL,
  CONSTRAINT [PK_dbo.MyEntities] PRIMARY KEY CLUSTERED ([Id])
)

CREATE NONCLUSTERED INDEX [IX_Number] ON [dbo].[MyEntities] ([Number])

インデックスとそこに数千行あり、Number値0、1、2に均等に分散されています。

今、このクエリ:

SELECT * FROM
    (SELECT
        [Extent1].[Number] AS [Number],
        CASE
        WHEN (0 = [Extent1].[Number]) THEN 'one'
        WHEN (1 = [Extent1].[Number]) THEN 'two'
        WHEN (2 = [Extent1].[Number]) THEN 'three'
        ELSE '?'
        END AS [Name]
        FROM [dbo].[MyEntities] AS [Extent1]
        ) P
WHERE P.Number = 0;

IX_Number予想どおりにインデックスが検索されます。

where句が

WHERE P.Name = 'one';

ただし、スキャンになります。

ケース句は明らかに全単射であるため、理論的には、最適化によって2番目のクエリから最初のクエリプランを差し引くことができるはずです。

また、純粋に学術的なものでもありません。列挙型の値をそれぞれのわかりやすい名前に変換することでクエリが発想を得ています。

クエリオプティマイザー(特にSql Serverのオプティマイザー)から何が期待できるかを知っている人からの連絡を希望します。単に期待しすぎですか。

以前にクエリのわずかな変化が最適化を突然明らかにする場合があったので、私は尋ねています。

SQL Server 2016 Developer Editionを使用しています。

回答:


18

単に期待しすぎですか?

はい。少なくとも現在のバージョンの製品では。

SQL Serverが離れて取得しませんCASE声明を、逆エンジニアはそれは計算列の結果がある場合ことを発見し'one'た後[Extent1].[Number]でなければなりません0

述語を検索引数として記述できるようにする必要があります。それはほとんど常にそれが形になっていることを含みます。basetable_column_name comparison_operator expression

わずかな逸脱でも検索可能性が損なわれます。

WHERE P.Number + 0 = 0;

CASE式より単純化する方が簡単ですが、インデックスシークも使用しません。

文字列名を検索してシークオン番号を取得する場合は、名前と番号を含むマッピングテーブルが必要であり、クエリでそれに結合します。その場合、プランにはマッピングテーブルでのシークとそれに続く相関シークがあります。[dbo].[MyEntities]最初のシークから返された番号をオンにします。


6

enumをケースステートメントとして投影しないでください。次のように、それを派生テーブルとして投影します。

SELECT * FROM
   (SELECT
      [Extent1].[Number] AS [Number],
      enum.Name
   FROM
      [dbo].[MyEntities] AS [Extent1]
      LEFT JOIN (VALUES
         (0, 'one'),
         (1, 'two'),
         (2, 'three')
      ) enum (Number, Name)
         ON Extent1.Number = enum.Number
   ) P
WHERE
   P.Name = 'one';

より良い結果が得られると思います。(?パフォーマンスの向上を妨げる可能性があるため、欠落している場合はNameを変換しませんでした。ただし、WHERE外部クエリ内に句を移動して述語をenumテーブルに置くか、またはから2つの列を返すことができます。 1つは述語用、もう1つは表示用の内部クエリで、NULL一致する列挙値がない場合の述語です。)

ただし、そのため[Extent1]、Entity FrameworkやLinq-To-SQLなどのORMを使用していると思います。このような予測をネイティブで実現する方法を説明することはできませんが、別の手法を使用することができます。

私の1つのプロジェクトでは、列挙型の値をデータベースにマージするカスタムビルドクラスを介して、データベース内の実際のテーブルにコードの列挙型の値を反映しました。(列挙値を明示的にリストする必要があるというルールを尊重する必要があります。テーブルを確認せずに削除することはできません。また、決して変更することはできませんが、現在の設定でこれの少なくとも一部を確認する必要があります) 。

今、私はIdentifier多くの異なる具象サブクラスを持つ基本クラスの列挙型を使用していましたが、それが単純なバニラ列挙型で実行できなかった理由はありません。次に使用例を示します。

new EnumOrIdentifierProjector<CodeClassOrEnum, PrivateDbDtoObject>(
   _sqlConnector.Connection,
   "dbo.TableName",
   "PrimaryKeyId",
   "NameColumnName",
   dtoObject => dtoObject.PrimaryKeyId,
   dtoObject => dtoObject.NameField,
   EnumerableOfIdentifierOrTypeOfEnum
)
   .Populate();

データベースの値を読み書きするために必要なすべての情報を渡したことがわかります。(現在のリクエストにすべての現存する値が含まれていない可能性があるため、現在読み込まれているセットだけでなく、データベースから追​​加の値を返す必要がありました。また、データベースにIDを割り当てさせましたが、列挙型の場合はおそらくそれが欲しい。)

アイデアは、起動時に一度だけ読み取り/書き込みが行われ、すべての列挙値が確実に含まれるテーブルを作成したら、他のテーブルと同じように結合するだけで、パフォーマンスが向上するということです。

これらのアイデアがあなたが改善するのに十分であることを望みます。


はい、私はEntityFrameworkを使用しており、ソリューションが本当に最適な世界にあるはずです。それが起こる前に、あなたの提案は私が信じる最良の回避策の1つです。
John

5

私はあなたがオプティマイザに一般的に興味を持っているが、SQL Serverには特に関心があると質問を解釈します。私はあなたのシナリオをdb2 LUW V11.1でテストしました:

]$ db2 "create table myentities ( id int not null, number int not null )"
]$ db2 "create index ix_number on myentities (number)"
]$ db2 "insert into myentities (id, number) with t(n) as ( values 0 union all select n+1 from t where n<10000) select n, mod(n,3) from t"

DB2のオプティマイザは、2番目のクエリを最初のクエリに書き換えます。

Original Statement:
------------------
SELECT 
  * 
FROM 
  (SELECT 
     number,

   CASE 
   WHEN (0 = Number) 
   THEN 'one' 
   WHEN (1 = Number) 
   THEN 'two' 
   WHEN (2 = Number) 
   THEN 'three' 
   ELSE '?' END AS Name 
   FROM 
     MyEntities
  ) P 
WHERE 
  P.name = 'one'


Optimized Statement:
-------------------
SELECT 
  Q1.NUMBER AS "NUMBER",

CASE 
WHEN (0 = Q1.NUMBER) 
THEN 'one' 
WHEN (1 = Q1.NUMBER) 
THEN 'two' 
WHEN (2 = Q1.NUMBER) 
THEN 'three' 
ELSE '?' END AS "NAME" 
FROM 
  LELLE.MYENTITIES AS Q1 
WHERE 
  (0 = Q1.NUMBER)

計画は次のようになります。

Access Plan:
-----------
        Total Cost:             33.5483
        Query Degree:           1


      Rows 
     RETURN
     (   1)
      Cost 
       I/O 
       |
      3334 
     IXSCAN
     (   2)
     33.1861 
     4.66713 
       |
      10001 
 INDEX: LELLE   
    IX_NUMBER
       Q1

他のオプティマイザについてはあまり知りませんが、DB2オプティマイザは競合他社の間でもかなり良いと考えられているように感じます。


それは楽しい。「最適化された声明」がどこから来たのか、いくつかの光を当てることができますか?db2自体はそれをあなたに返しますか?-また、私は計画を読むのに苦労しています。この場合、「IXSCAN」はインデックススキャンを意味しないと思いますか?
ジョン

1
ステートメントを説明するようにDB2に指示できます。収集された情報は一連のテーブルに保存され、視覚的な説明を使用するか、この場合はユーティリティdb2exfmt(または独自のユーティリティを作成)を使用できます。さらに、ステートメントを監視し、プランの推定カーディナリティを実際のプランと比較できます。この計画では、それが実際にインデックススキャン(IXSCAN)であり、この演算子からの推定出力が3334行であることがわかります。これはSQLサーバーで問題ですか?スタートキーとストップキーを認識しているため、DB2の関連する行のみをスキャンします。
Lennart

したがって、スキャンと呼ばれるものはシークを含み、正直に言うと、Sql Serverの同等のプランの説明では、シークと呼ばれるスキャンと呼ばれることもあれば、シークと呼ばれることもあります。何が何であるかを理解するには、常に行数を調べる必要があります。db2の出力には明らかに3334があるため、私が望んでいたことを確実に実行します。とても興味深い。
ジョン

はい、時々混乱します。何が起こっているのかを本当に理解するには、各オペレーターのより詳細な情報を見る必要があります。
Lennart

0

この特定のクエリでは、CASEステートメントがあったとしても、ばかげています。特定のケースに絞り込んでいます!おそらく、これは指定した特定のクエリ例の詳細にすぎませんが、そうでない場合は、このクエリを記述して同等の結果を得ることができます。

SELECT
    [Extent1].[Number] AS [Number],
    'one' AS [Name]
FROM [dbo].[MyEntities] AS [Extent1]
WHERE [Extent1].[Number] = 0;

これにより、まったく同じ結果セットが得られますCASE。いずれにしても、ステートメントの値をすでにハードコーディングしているので、ここで保守性を失うことはありません。


1
ポイントを逃していると思います。これは、文字列表現を介して列挙型で機能するバックエンドコードベースから生成されたSQLです。SQLを投影しているコードがクエリに対して暴力を振るっています。質問者がSQLを自分で作成している場合は、より優れたクエリを作成できると思います。したがって、CASEORMがそのようなことをしているので、ステートメントを持つことはまったく愚かではありません。ばかげているのは、問題のこれらの単純な側面を認識しなかったということです...(間接的に
ブレインレス

@ErikE とにかくC#を想定して、列挙型の数値だけを使用できるので、少しばかげています。(SQL Serverについて話していることを考えると、かなり安全な仮定です。)
jpmc26

しかし、ユースケースが何であるかはわかりません。おそらく、数値に切り替えるのは大きな変化でしょう。おそらく列挙型は既存の巨大なコードベースに改造されたのでしょう。知識なしで批判するのはばかげています。
ErikE

@ErikEばかげているなら、なぜあなたはそれをしているのですか?=)ユースケースが質問の例のように単純である場合(これは私の回答の序文で明確に指定されている)、CASE指摘は完全に欠点なく削除できることを指摘するために答えただけです。コースがあり、未知の因子であることが、彼らは、不特定だかもしれません。
jpmc26

私はあなたの答えの事実の部分に異議はなく、主観的に特徴付ける部分だけに反対します。私が知識なしで批判しているのかどうかについては、私は慎重にクリーンなロジックを使用できなかった、または明らかに間違っている仮定をした方法を理解することに耳を
傾けてい
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.