EntityFieldQueryは本当にこれは非効率ですか?


11

私はEntity APIの初心者ですが、それを解決しようとしています。私は、さまざまなフィールドが付加された多数のコンテンツタイプを使用するサイトで作業しています。派手なものは何もない。したがって、一連のエントリを取得したい場合、私は無知で、データベースを直接呼び出して、次のようなことをしています。

$query = db_select('node', 'n')->extend('PagerDefault');
$query->fields('n', array('nid'));
$query->condition('n.type', 'my_content_type');

$query->leftJoin('field_data_field_user_role', 'role', 'n.nid = role.entity_id');
$query->condition('role.field_user_role_value', $some_value);

$query->leftJoin('field_data_field_withdrawn_time', 'wt', 'n.nid = wt.entity_id');
$query->condition('wt.field_withdrawn_time_value', 0);

$query->orderBy('n.created', 'desc');

$query->limit(10);

$result = $the_questions->execute()->fetchCol();

(はい、私はおそらくこれらの行の束を単一の$the_questions->ステートメントに折りたたむことができます; plsは今のところそれを無視します。)

EntityFieldQueryでこれを書き換えようとすると、次のようになります。

$query = new EntityFieldQuery();
$query
  ->entityCondition('entity_type', 'node')
  ->entityCondition('bundle', 'my_content_type')
  ->fieldCondition('field_user_role', 'value', $some_value)
  ->fieldCondition('field_withdrawn_time', 'value', 0)
  ->propertyOrderBy('created', 'desc')
  ->pager(10);

$result = $query->execute();

if (isset($result['node'])) {
    $result_nids = array_keys($result['node']);
}
else {
    $result_nids = array();
}

これは私に望ましい結果を与え、確かにかなりきれいです。

だから、今、パフォーマンスについて疑問に思っています。まず、これらのコードの各ビットを愚かなfor()ループに入れて、time()実行の前後をキャプチャします。それほど大きくないデータベースで各バージョンを100回実行すると、次のような結果が得られます。

  • 直接バージョン:110ミリ秒
  • EFQバージョン:4943ミリ秒

テストを再実行すると、明らかに異なる結果が得られますが、結果は一貫して同じ範囲内にあります。

うわぁ。私はここで何か悪いことをしていますか、それともこれはEFQを使用するコストだけですか?コンテンツタイプに関して特別なデータベースチューニングは行っていません。これらは、通常のフォームベースの方法でコンテンツタイプを定義することから得られるものです。何かご意見は?EFQコードは間違いなくよりクリーンですが、40倍のパフォーマンスヒットができるとは思えません。


3
生成された両方のSQLクエリをダンプできますか?
Andre Baumeier

1
参照してください。これをあなたがEFQのうち、SQLを取得する方法がわからない場合は
クライヴ

2
OK、進歩があります:ここで起こっているのは、私のサイトにノードアクセスルールがたくさんあり、クエリのサイズがかなり大きくなっていることです。それらはEFQクエリに自動的に適用されてい->addTag('node_access')ました(クエリにない場合でも??)。node_accessタグを使用して「直接」クエリを再実行しました。実行時間ははるかに近くなっています。EFQの時間は、直接アプローチより約2倍大きいだけです。これは、両方がポンプアウトしている相対SQL(これはそれでも気になれば投稿できます)。(次のコメントで続きます...)
ジム・ミラー

だから今の質問は、なぜ私がEFQバージョンでnode_accessのものを自動的に取得しているのでしょうか?addTag()句で明示的に要求する必要があると思いましたか?
ジム・ミラー

回答:


10

このEntityFieldQueryクラスは、その要件が許す限り効率的です。MongoDBを使用するクラスなど、NoSQLエンジンを使用してフィールドデータを格納するクラスであっても、フィールドストレージクラスと互換性がある必要があります。そのEntityFieldQueryため、現在のフィールドストレージバックエンドがSQLデータベースをまったく使用しない可能性があるため、データベースを直接クエリすることはできません。

フィールドストレージがSQLエンジンを使用してデータを格納する場合でも$query->leftJoin('field_data_field_user_role', 'role', 'n.nid = role.entity_id'); $query->condition('role.field_user_role_value', $some_value);EntityFieldQueryクラスのと同等のものが必要です。

  • フィールド名からデータベーステーブル名を作成するコード
  • フィールドデータを含むテーブルとエンティティデータを含むテーブルを結合するために使用する条件を構築するコード
  • フィールドデータを含むデータベース行の名前を作成するコード

違いはすぐにわかります。1つのケースでは3つのリテラル文字列を使用していますが、他のケースでは(最も単純なケースでは)文字列を連結しているコードがあります。

ユーザーがフィールドにアクセスする権限を持っているかどうかをチェックするコードに関するコメントのとおり、次の行を使用して、EntityFieldQueryクラスを使用するコードにバイパスできます。

$query->addTag('DANGEROUS_ACCESS_CHECK_OPT_OUT');

これは、Drupal 7.15以降を使用している場合に機能します。以前のバージョンでは、次のコードを使用する必要があります。

$account = user_load(1);
$query->addMetaData('account', $account);

いつものように、ユーザーがアクセスできないはずの情報をコードがユーザーに表示できる場合は、アクセス許可をバイパスしないでください。これは、非公開ノードを表示する権限を持つユーザーにのみ非公開ノードが表示される場合にDrupalで行われるものと同様です。コードの目的が、たとえばcronタスクなどで連続して削除されるエンティティを選択することである場合、アクセス制御をバイパスしても害はなく、続行する唯一の方法です。


最初のクエリもページャーを使用しているため、私はおそらく正しくないと認めるべきです(->extend('PagerDefault');最初は気付か
なかった

おっと、あなたは正しいです。
kiamlaluno

これは本当に興味を持ったので、上記の実験に沿って何かを試してみて、数の大きな違いを確認できません...誰かも試してみませんか?
mojzis 2013年

したがって、確認するだけです。EFQ呼び出しは、(上記のように)発生を防ぐために何かをしない限り、常にサイトのノードアクセスルールを呼び出します。正しい?
ジム・ミラー

@JimMiller正解です。Drupal7.15に「DANGEROUS_ACCESS_CHECK_OPT_OUT」タグが追加されたのはそのためです。
kiamlaluno
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.