Linq to Databaseに関する7つの神話

Linqは2007年に登場し、最初のIQueryableプロバイダーも登場しました。Linq2SQLはMS SQL Serverでのみ動作し、かなり遅くなり、すべてのシナリオからはほど遠いものでした。 ほぼ7年が経過し、さまざまなDBMSで動作するいくつかのLinqプロバイダーが登場し、テクノロジーの「小児疾患」のほぼすべてを打ち負かし、数年の間、Linq to Database(一般的なプロバイダーの総称)が産業で使用できるようになりました。



それでも、誰もがLinq to Databaseを使用しているわけではありません。これは、プロジェクトが古く、linqに書き直すのが非常に難しいという事実だけでなく、さまざまな神話を議論としてもたらします。 これらの神話は、ある会社から別の会社にさまようものであり、多くの場合インターネット上で広まります。



この投稿では、最も人気のある神話とそれらへの反論を集めました。





神話番号1



データベースは、すべての要求を行う特別に訓練されたDBAによって処理され、プログラマーはコードを記述するため、Linq to Databaseは不要です。


神話の魅力にもかかわらず、このアプローチは通常機能しません。 効果的なクエリを作成するには、DBAはプログラムで何が行われているか、各シナリオでどのデータが必要かを非常によく理解する必要があります。



DBAがそのような知識を持っていない場合、通常は、DBAが各エンティティのCRUDストレージの小さなセットと最も「厚い」クエリの複数のストレージを作成するという事実に帰着します。 そして、残りはすでにコードのプログラマーによって行われています。 これは、特定のシナリオに必要なデータよりも平均してはるかに多くのデータがプルされるため、ほとんどの場合、非効率的に機能します。 そして最適化するのは難しいです。



DBAが各シナリオを知っている場合、2つのオプションがあります。

a)それぞれ特定のシナリオ用に大量のストレージ(ほぼ同一)を作成し、それらをサポートするのは苦痛です。

b)パラメーターの束を持ついくつかのユニバーサルストレージを作成し、その中に文字列を接着して最適なクエリを形成します。 さらに、リクエストに追加のパラメーターを追加することは非常に困難なプロセスになります。



DBAの両方のオプションは非常に複雑であるため、多くの場合、いくつかの非常に複雑なストレージファイルを持つハイブリッドバージョンが判明し、その他はすべて一般的なCRUDです。 Linqを使用すると、同じ文字列のステッチングをはるかに効率的に行うことができるため、プログラムコードで最適なクエリを生成したり、最適なクエリに近いクエリを生成したりできます。



DBAは、バッチ処理用のストアドプロシージャと同様に、アプリケーションコードからのクエリで使用されるビューと関数を作成できます。 ただし、クエリの設計はアプリケーション側に任せるのが最善です。



神話番号2



Linqは非効率的なSQLクエリを生成します。


非常に頻繁に繰り返される神話。 しかし、Linqクエリの非効率性のほとんどは人々によって作成されています。



この理由は簡単です:

1)LinqとSQLとの違いを人々は理解していません。 Linqは順序付きシーケンスで動作し、SQLは無秩序なセットで動作します。 したがって、一部のLinq操作では、非常に非効率的なソート演算子がSQLに追加されます。

2)人々は、IQuryableプロバイダーの作業のメカニズムと、DBMSでのクエリの実行方法を理解していません。 前の投稿の詳細-habrahabr.ru/post/230479



しかし、プロバイダーにはバグがあり、最適化にはほど遠い要求が生成されます。



たとえば、Entity Frameworkでは、ナビゲーションプロパティの使用時にバグがあります。

context.Orders .Where(o => o.Id == id) .SelectMany(o => o.OrderLines) .Select(l => l.Product) .ToList();
      
      





このようなクエリは、次のSQLを生成します。

たくさんのコード
  [Project1].[Id] AS [Id], [Project1].[OrderDate] AS [OrderDate], [Project1].[UserId] AS [UserId], [Project1].[C1] AS [C1], [Project1].[OrderId] AS [OrderId], [Project1].[ProductId] AS [ProductId], [Project1].[Id1] AS [Id1], [Project1].[Title] AS [Title] FROM ( SELECT [Extent1].[Id] AS [Id], [Extent1].[OrderDate] AS [OrderDate], [Extent1].[UserId] AS [UserId], [Join1].[OrderId] AS [OrderId], [Join1].[ProductId] AS [ProductId], [Join1].[Id] AS [Id1], [Join1].[Title] AS [Title], CASE WHEN ([Join1].[OrderId] IS NULL) THEN CAST(NULL AS int) ELSE 1 END AS [C1] FROM [dbo].[Orders] AS [Extent1] LEFT OUTER JOIN (SELECT [Extent2].[OrderId] AS [OrderId], [Extent2].[ProductId] AS [ProductId], [Extent3].[Id] AS [Id], [Extent3].[Title] AS [Title] FROM [dbo].[OrderLines] AS [Extent2] INNER JOIN [dbo].[Products] AS [Extent3] ON [Extent2].[ProductId] = [Extent3].[Id] ) AS [Join1] ON [Extent1].[Id] = [Join1].[OrderId] WHERE [Extent1].[Id] = @p__linq__0 ) AS [Project1] ORDER BY [Project1].[Id] ASC, [Project1].[C1] ASC
      
      







このクエリでは、計算フィールドとそれによる並べ替えをSQL Serverで最適化することはできません。実際の並べ替えを実行する必要があります。



しかし、結合演算子を使用するというLinqの要求を少し書き直せば、問題はありません。

 var orders1 = from o in context.Orders where o.Id == id join ol in context.OrderLines on o.Id equals ol.OrderId into j from p in j.DefaultIfEmpty() select p.Product; orders1.ToArray();
      
      





結果のSQL:

 SELECT [Extent3].[Id] AS [Id], [Extent3].[Title] AS [Title] FROM [dbo].[Orders] AS [Extent1] LEFT OUTER JOIN [dbo].[OrderLines] AS [Extent2] ON [Extent1].[Id] = [Extent2].[OrderId] LEFT OUTER JOIN [dbo].[Products] AS [Extent3] ON [Extent2].[ProductId] = [Extent3].[Id] WHERE [Extent1].[Id] = @p__linq__0
      
      





インデックスによって十分にカバーされ、SQL Serverによって最適化されます。



NHibernateの非効率なリクエストについても聞きましたが、そのようなバグを見つけるためにそれほど積極的には動作しませんでした。



神話番号3



マッピングが遅い。


DataReaderからオブジェクトのセットへの変換は、各オブジェクトに対して数分の1 マイクロ秒で実行されます。 さらに、linq2dbプロバイダーは、アドバタイズされたDapperよりも高速にこれを行うことができます。



しかし、ゆっくりと動作できるのは、受け取ったオブジェクトを変更追跡コンテキストにアタッチすることです。 ただし、これは、オブジェクトが変更されてデータベースに書き込まれた場合にのみ行う必要があります。 それ以外の場合、オブジェクトがコンテキストに参加しないか、プロジェクションを使用しないことを明示的に指定できます。



神話番号4



クエリはゆっくり生成されています。


実際、LinqからSQLクエリを生成するには、ツリーウォークが必要であり、メタデータのリフレクションと分析に関する多くの作業が必要です。 ただし、すべてのプロバイダーで、この分析は1回実行され、その後データがキャッシュされます。



その結果、単純なクエリの場合、クエリ生成は平均で0.4ms実行されます。 複雑なものの場合、最大数ミリ秒になることがあります。

この時間は通常、合計クエリ実行時間の統計誤差よりも短くなります。



神話番号5



ヒントを使用することはできません。


SQL Serverには、任意のクエリにヒントを添付できるプランガイドメカニズムがあります。 他のDBMSにも同様のメカニズムが存在します。



しかし、それでも、Linqを使用する場合、ヒントはあまり必要ありません。 Linqは、統計、インデックス、および制限がある場合にDBMSが個別に最適化する非常に単純なクエリを生成します。 正しい分離レベルを設定し、要求された行の数を制限することにより、ロックヒントを置き換えることをお勧めします。



神話番号6



LinqはSQLのすべての機能を使用できるわけではありません。


これは部分的に真実です。 ただし、多くのSQL機能は関数またはビューでラップでき、Linqクエリで既に使用されています。



さらに、Entity Frameworkを使用すると、SQLクエリを実行し、変更追跡などの結果をオブジェクトにマッピングできます。



神話番号7



ストアドプロシージャは、Linqによって生成されるアドホックリクエストよりも高速に実行されます。


これは90年代半ばに当てはまりました。 今日、すべてのDBMSは、それがアドホックまたはアドホックリクエストであるかどうかに関係なく、リクエストとキャッシュプランを「コンパイル」します。



ここに出くわす神話の短いセットがあります。 あなたがもっとあれば-サプリメント。



All Articles