「どちらのORMを使用すべきか」という質問は、大規模なアプリケーションでの全体的なデータアクセス戦略とパフォーマンスの最適化に関して、本当に大きな氷山の一角を狙っています。
データベースの設計と保守
これは、データ駆動型アプリケーションまたはWebサイトのスループットの最も重要な決定要因であり、プログラマによって完全に無視される場合がほとんどです。
適切な正規化手法を使用しないと、サイトは運命づけられます。主キーがない場合、ほとんどすべてのクエリが犬のように遅くなります。正当な理由がない限り、キーと値のペア(エンティティ属性値)のテーブルを使用するなど、よく知られているアンチパターンを使用すると、物理的な読み取りと書き込みの数が急増します。
ページ圧縮、FILESTREAM
ストレージ(バイナリデータ用)、SPARSE
列、hierarchyid
階層用など(すべてのSQL Serverの例)など、データベースが提供する機能を利用しない場合、あなたが見ることができるパフォーマンス。
データベースを設計し、少なくとも当分の間はデータベースが可能な限り優れていると確信した後、データアクセス戦略について心配する必要があります。
Eager vs. Lazy Loading
ほとんどのORMは、リレーションシップの遅延読み込みと呼ばれる手法を使用しました。つまり、既定では、一度に1つのエンティティ(テーブル行)を読み込み、1つ以上の関連する(外部のキー)行。
これは良いことでも悪いことでもありません。むしろ、データを実際にどのように処理するか、そしてどの程度事前に知っているかに依存します。時々、遅延読み込みは絶対に正しいことです。たとえば、NHibernate は何も照会せず、特定のIDのプロキシを単に生成することを決定する場合があります。必要なのがIDだけである場合、なぜさらに要求する必要があるのですか?一方、3レベルの階層内のすべての要素のツリーを印刷しようとすると、遅延読み込みはO(N²)操作になり、パフォーマンスが著しく低下します。
「純粋なSQL」(つまり、生のADO.NETクエリ/ストアドプロシージャ)を使用することの興味深い利点の1つは、基本的に、特定の画面またはページを表示するために必要なデータを正確に考えることです。オームズと遅延ロード機能はありません防ぐためにこれをやってからあなたを、彼らはないあなたになる機会を与えて...よく、怠惰な、と誤ってあなたが実行するクエリの数を爆発します。そのため、ORMの熱心な読み込み機能を理解し、特定のページリクエストに対してサーバーに送信するクエリの数に常に注意する必要があります。
キャッシング
すべての主要なORMは、「アイデンティティキャッシュ」とも呼ばれる第1レベルのキャッシュを保持します。つまり、同じエンティティをそのIDで2回要求する場合、2回目の往復は必要ありません。 )オプティミスティックな同時実行性を使用できます。
L1キャッシュはL2SとEFではかなり不透明です。動作していることを信頼する必要があります。NHibernateはそれについてより明示的です(Get
/ Load
vs. Query
/ QueryOver
)。それでも、できる限りIDで照会しようとする限り、ここで問題ありません。多くの人がL1キャッシュを忘れて、ID以外の何か(つまり、ルックアップフィールド)で同じエンティティを繰り返し検索します。これを行う必要がある場合は、将来の検索のためにIDまたはエンティティ全体を保存する必要があります。
レベル2キャッシュ(「クエリキャッシュ」)もあります。NHibernateにはこのビルトインがあります。Linq to SQLとEntity Frameworkにはクエリがコンパイルされており、クエリ式自体をコンパイルすることでアプリサーバーの負荷を大幅に削減できますが、データはキャッシュされません。Microsoftは、これをデータアクセスの問題ではなくアプリケーションの問題と考えているようです。これは、L2SとEFの両方の大きな弱点です。言うまでもなく、「生の」SQLの弱点でもあります。基本的にNHibernate以外のORMで非常に優れたパフォーマンスを得るには、独自のキャッシュファサードを実装する必要があります。
EF4用のL2キャッシュ「拡張」もありますが、これは大丈夫ですが、実際にはアプリケーションレベルのキャッシュの大規模な代替ではありません。
クエリ数
リレーショナルデータベースはデータセットに基づいています。短時間で大量のデータを生成することは非常に得意ですが、すべてのコマンドにある程度のオーバーヘッドが伴うため、クエリの待機時間の点ではそれほど優れていません。適切に設計されたアプリは、このDBMSの長所を活用し、クエリの数を最小限に抑え、それぞれのデータ量を最大化するよう努める必要があります。
これで、1行だけが必要な場合にデータベース全体を照会することは言っていません。あなたが必要な場合は、私が言っていることは、あるCustomer
、Address
、Phone
、CreditCard
、そしてOrder
、あなたがすべき、単一のページを提供するために同時に行すべてを聞いて、同時にそれらすべてに対して個別に各クエリを実行しません。それよりも悪いこともあります。同じCustomer
レコードを連続して5回クエリするコードがあります。最初にを取得しId
、次にName
、次にEmailAddress
、それから...とんでもないほど非効率的です。
すべてがまったく異なるデータセットで動作する複数のクエリを実行する必要がある場合でも、通常はすべてを単一の「スクリプト」としてデータベースに送信し、複数の結果セットを返す方が効率的です。これは、データの総量ではなく、関心のあるオーバーヘッドです。
これは常識のように聞こえるかもしれませんが、多くの場合、アプリケーションのさまざまな部分で実行されているすべてのクエリを追跡するのは本当に簡単です。メンバーシッププロバイダーはユーザー/ロールテーブルを照会し、ヘッダーアクションはショッピングカートを照会し、メニューアクションはサイトマップテーブルを照会し、サイドバーアクションは注目製品リストを照会します。 Order History、Recently View、Category、Inventoryの各テーブルに個別にクエリを実行します。それを知る前に、ページの提供を開始する前に20のクエリを実行しています。パフォーマンスを完全に破壊するだけです。
いくつかのフレームワーク-と私はここに主にNHibernateはと思っていますが-このことについて非常に巧妙であり、あなたはと呼ばれるものを使用することができ先物たバッチ全体のクエリアップをし、最後の可能な分で、一度にすべてを実行してみてください。私の知る限り、Microsoftテクノロジーのいずれかでこれを実行したい場合は、自分で作業します。アプリケーションロジックに組み込む必要があります。
インデックス付け、述語、射影
私が話す開発者の少なくとも50%、および一部のDBAでさえ、インデックスをカバーするという概念に問題があるようです。「まあ、Customer.Name
列にはインデックスが付けられているので、名前に対して行うすべてのルックアップは高速でなければなりません。」Name
インデックスが検索している特定の列をカバーしない限り、それはそのようには機能しません。SQL Serverでは、これINCLUDE
はCREATE INDEX
ステートメントで行われます。
SELECT *
どこでもナイーブに使用している場合-プロジェクションを使用して明示的に指定しない限り、すべてのORMが多かれ少なかれそれを行います-DBMS は、カバーされていない列を含むため、インデックスを完全に無視することを選択できます。プロジェクションとは、たとえば、これを行う代わりに:
from c in db.Customers where c.Name == "John Doe" select c
代わりにこれを行います:
from c in db.Customers where c.Name == "John Doe"
select new { c.Id, c.Name }
そして、この意志は、最も近代的なオームズのために、それだけで行くと照会するように指示Id
し、Name
おそらくインデックスで覆われている列を(ではなくEmail
、LastActivityDate
または他のものは何でもそこに固執する起こっ列)。
また、不適切な述語を使用することで、インデックス作成のメリットを完全に吹き飛ばすことも非常に簡単です。例えば:
from c in db.Customers where c.Name.Contains("Doe")
...前のクエリとほとんど同じように見えますが、実際にはに変換されるため、完全なテーブルまたはインデックスのスキャンになりLIKE '%Doe%'
ます。同様に、疑わしく単純に見える別のクエリは次のとおりです。
from c in db.Customers where (maxDate == null) || (c.BirthDate >= maxDate)
にインデックスがあると仮定するとBirthDate
、この述語は完全に役に立たない可能性が高くなります。ここでの私たちの仮想プログラマーは、明らかに一種の動的クエリを作成しようとしました(「そのパラメーターが指定されている場合のみ誕生日をフィルター処理する」)が、これは正しい方法ではありません。代わりにこのように書かれています:
from c in db.Customers where c.BirthDate >= (maxDate ?? DateTime.MinValue)
...これで、DBエンジンはこれをパラメーター化してインデックスシークを行う方法を認識します。クエリ式に対する些細な、一見些細な変更がパフォーマンスに大きく影響する可能性があります。
残念ながら、一般的にLINQは、このような不正なクエリを記述することがあまりにも簡単になり、時にはプロバイダは、クエリを実行し、最適化しようとしていた、そして時にはそうでないものを推測することができます。そのため、単純に古いSQLを記述しただけでは、(経験豊富なDBAにとっては)目がくらむほど明白な結果となる、いらいらするほど一貫性のない結果になります。
基本的には、生成されたSQLとそれらが導く実行計画の両方に本当に注意を払わなければならないという事実に帰着します。期待する結果が得られない場合は、バイパスすることを恐れないでください。 ORMレイヤーはたまにSQLを手動でコーディングします。これは、EFだけでなく、すべての ORMに当てはまります。
トランザクションとロック
ミリ秒までの最新データを表示する必要がありますか?たぶん-それは依存します-おそらくそうではありません。残念なことに、Entity Frameworkからはnolock
READ UNCOMMITTED
、トランザクションレベルでのみ使用できます(テーブルレベルでは使用できません)。実際、これについて特に信頼できるORMはありません。ダーティリードを実行する場合は、SQLレベルにドロップダウンし、アドホッククエリまたはストアドプロシージャを記述する必要があります。要するに、フレームワーク内でそれを行うのがどれほど簡単かということです。
エンティティフレームワークはこの点で長い道のりを歩んできました-EF(.NET 3.5)のバージョン1は非常にひどく、「エンティティ」の抽象化を突破するのが非常に困難でしたが、今ではExecuteStoreQueryとTranslateがあります。悪くない。あなたは彼らをたくさん使うので、これらの人と友達を作りましょう。
また、書き込みロックとデッドロックの問題、およびデータベースにロックをできるだけ短時間保持する一般的な慣行もあります。この点で、ほとんどのORM(Entity Frameworkを含む)は、実際には作業単位パターン(EFではSaveChanges)をカプセル化するため、生のSQL よりも優れている傾向があります。言い換えると、作業ユニットをコミットするまで実際に変更がデータベースにプッシュされないという知識で、必要なときにいつでもエンティティを「挿入」、「更新」、または「削除」することができます。
UOWは、長時間実行されるトランザクションに類似していないことに注意してください。UOWはORMの楽観的同時実行機能を引き続き使用し、メモリ内のすべての変更を追跡します。最終コミットまで単一のDMLステートメントは発行されません。これにより、トランザクション時間ができるだけ短くなります。生のSQLを使用してアプリケーションを構築する場合、この遅延動作を実現することは非常に困難です。
EFにとってこれが具体的に意味すること:作業単位をできるだけ粗くし、絶対に必要になるまでコミットしないでください。これを行うと、個々のADO.NETコマンドをランダムに使用する場合よりもロックの競合がはるかに少なくなります。
EFは、他のすべてのフレームワークが高トラフィック/高性能アプリケーションに適しているのと同様に、高トラフィック/高性能アプリケーションに適しています。重要なのは、それをどのように使用するかです。最も人気のあるフレームワークとそれらが提供する機能のパフォーマンスの簡単な比較を以下に示します(凡例:N =サポートなし、P =部分的、Y =はい/サポート)。
ご覧のとおり、EF4(現在のバージョン)はそれほど悪くありませんが、パフォーマンスが主な関心事である場合はおそらく最適ではありません。NHibernateはこの分野でより成熟しており、Linq to SQLでさえ、EFがまだ提供していないパフォーマンス強化機能を提供します。生のADO.NETは、非常に特定のデータアクセスシナリオではより高速になることがよくありますが、すべての要素をまとめると、実際にはさまざまなフレームワークから得られる多くの重要な利点を提供しません。