読者です 読者をやめる 読者になる 読者になる

しばやん雑記

ASP.NET とメイドさんが大好きなフリーランスのプログラマーのブログ

LightSpeed で空のシーケンスに Contains を呼び出すと腐る件について

通常は Entity Framework や LightSpeed などの LINQ に対応した ORM では、配列への Contains メソッド呼び出しは SQL でいう IN に変換されます。

// 取得したい ID をコレクションとして用意
var ids = new[] { "ALFKI", "BOLID", "LAUGB" };

// CustomerID IN ("ALFKI", "BOLID", "LAUGB") という SQL に変換される
var result = uow.Customers.Where(p => ids.Contains(p.Id)).ToList();

WHERE に OR で条件を大量に書かれるのと比べると、パフォーマンスが良さそうですね。

実際にコードを書いていると動的に取得したい ID を決定することが多いと思いますが、0 件のコレクションに対して Contains を呼び出すケースが出てくると思います。

// 0 件にしてみる
var ids = new string[0];

// CustomerID IN () は出来ないので、どうなる?
var result = uow.Customers.Where(p => ids.Contains(p.Id)).ToList();

当然ながら実行結果は 0 件となるので、高速に結果が返ってくると思っていました。しかし LightSpeed を使っていると、条件が 0 件以外の時よりもクエリの実行に時間がかかっていました。

調べたところ、先程のコードを実行したときには LightSpeed 5 は以下のような SQL を生成します。

SELECT
  Customers.CustomerID AS [Customers.CustomerID],
  Customers.Address AS [Customers.Address],
  Customers.City AS [Customers.City],
  Customers.CompanyName AS [Customers.CompanyName],
  Customers.ContactName AS [Customers.ContactName],
  Customers.ContactTitle AS [Customers.ContactTitle],
  Customers.Country AS [Customers.Country],
  Customers.Fax AS [Customers.Fax],
  Customers.Phone AS [Customers.Phone],
  Customers.PostalCode AS [Customers.PostalCode],
  Customers.Region AS [Customers.Region]
FROM
  Customers
WHERE
  Customers.CustomerID <> Customers.CustomerID

/(^o^)\

0 件を返すだけなのに、一目見ただけで効率が悪そうなクエリですね。実行プランを確認しておきます。

f:id:shiba-yan:20130601165756p:plain

思った通りクラスタ化インデックススキャンが行われているので、件数によってはコストの高いクエリとなっています。0 件を返すだけなのに。

ちなみに、先日公開されたばかりの Entity Framework 6 beta 1 では以下のような SQL を生成します。

SELECT 
  CAST(NULL AS varchar(1)) AS [C1], 
  CAST(NULL AS varchar(1)) AS [C2], 
  CAST(NULL AS varchar(1)) AS [C3], 
  CAST(NULL AS varchar(1)) AS [C4], 
  CAST(NULL AS varchar(1)) AS [C5], 
  CAST(NULL AS varchar(1)) AS [C6], 
  CAST(NULL AS varchar(1)) AS [C7], 
  CAST(NULL AS varchar(1)) AS [C8], 
  CAST(NULL AS varchar(1)) AS [C9], 
  CAST(NULL AS varchar(1)) AS [C10], 
  CAST(NULL AS varchar(1)) AS [C11]
FROM
  ( SELECT 1 AS X ) AS [SingleRowTable1]
WHERE
  1 = 0

テーブルすら参照せずに、0 件のデータを返すようなクエリになっていますね。

実行プランも一応確認しておきましょう。

f:id:shiba-yan:20130601165405p:plain

Constant Scan となっているので、コストがほぼかかっていません。Entity Framework 6 は優秀です。

まだ Entity Framework 6 はベータが公開された段階ですが、async / await を使った非同期処理が実装されていたりするので、じわじわと LightSpeed の地位も怪しくなるんじゃないかなと思いました。