EntityFieldQuery est-il vraiment aussi inefficace?

11

Je suis un débutant admis à l'API Entity, mais j'essaie de remédier à cela. Je travaille sur un site qui utilise un certain nombre de types de contenu avec différents champs attachés à eux; Rien d'extraordinaire. Donc, quand je veux récupérer un ensemble d'entrées, j'ai, dans mon ignorance, appelé directement dans la base de données et fait quelque chose comme ceci:

$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();

(Oui, je pourrais probablement regrouper un tas de ces lignes en une seule $the_questions->déclaration; veuillez ignorer cela pour l'instant.)

En essayant de réécrire cela avec EntityFieldQuery, je trouve:

$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();
}

ce qui me donne les résultats escomptés et est sûrement beaucoup plus joli.

Donc, maintenant je me pose des questions sur les performances. Pour commencer, je jette chacun de ces bits de code dans une for()boucle stupide , capturant time()avant et après l'exécution. J'exécute chaque version 100 fois sur une base de données pas très grande et j'obtiens quelque chose comme ceci:

  • Version directe: 110 msec
  • Version EFQ: 4943 msec

Évidemment, j'obtiens des résultats différents lorsque je relance le test, mais les résultats sont toujours dans le même stade.

Oui. Suis-je en train de faire quelque chose de mal ici, ou est-ce juste le coût d'utilisation de l'EFQ? Je n'ai effectué aucun réglage spécial de la base de données en ce qui concerne les types de contenu; ils sont juste ce qui vient de la définition des types de contenu de la manière habituelle, basée sur un formulaire. Des pensées? Le code EFQ est certainement plus propre, mais je ne pense vraiment pas pouvoir me permettre un rendement 40 fois supérieur.

Jim Miller
la source
3
pouvez-vous vider les deux requêtes SQL générées?
Andre Baumeier
1
Voir celui-ci si vous ne savez pas comment extraire le SQL d'un EFQ
Clive
2
OK, il y a des progrès: ce qui se passe ici, c'est que mon site a un tas de règles d'accès aux nœuds qui augmentent un peu la taille de la requête. Ceux-ci étaient automatiquement appliqués à la requête EFQ (même s'il n'y en a pas ->addTag('node_access')dans la requête ??). J'ai relancé la requête "directe" avec une balise node_access, et les temps d'exécution sont beaucoup plus proches: le temps d'EFQ est maintenant seulement environ un facteur 2 supérieur à l'approche directe, ce qui semble raisonnable compte tenu du SQL relatif que les deux pompent (ce qui Je peux poster si les gens se soucient encore). (suite au commentaire suivant ....)
Jim Miller
Alors maintenant, la question, je suppose, est pourquoi j'obtiens automatiquement les éléments node_access dans la version EFQ? Je pensais que vous deviez le demander explicitement via la clause addTag () ??
Jim Miller

Réponses:

10

La EntityFieldQueryclasse est aussi efficace que ses exigences le lui permettent. Il doit être compatible avec toutes les classes de stockage de champ, même avec celles qui utilisent un moteur NoSQL pour stocker les données de champ, comme celle qui utilise MongoDB . Pour cette raison, EntityFieldQueryne peut pas interroger directement la base de données, car le backend de stockage de champ actuel peut ne pas utiliser du tout de base de données SQL.

Même dans le cas où le stockage sur le terrain utilise un moteur SQL pour stocker ses données, l'équivalent de $query->leftJoin('field_data_field_user_role', 'role', 'n.nid = role.entity_id'); $query->condition('role.field_user_role_value', $some_value);pour la EntityFieldQueryclasse nécessite:

  • Code pour construire le nom de la table de base de données à partir du nom du champ
  • Code pour créer la condition à utiliser pour joindre la table contenant les données de champ avec la table contenant les données d'entité
  • Code pour construire le nom de la ligne de base de données contenant les données de champ

La différence est immédiatement visible: dans un cas, vous utilisez trois chaînes littérales, tandis que dans l'autre, il existe un code qui (dans le plus simple des cas) concatène des chaînes.

Selon votre commentaire sur le code qui vérifie si l'utilisateur a l'autorisation d'accéder aux champs, vous pouvez contourner cela en utilisant la ligne suivante pour le code en utilisant la EntityFieldQueryclasse.

$query->addTag('DANGEROUS_ACCESS_CHECK_OPT_OUT');

Cela fonctionne si vous utilisez Drupal 7.15 ou supérieur; pour les versions antérieures, vous devez utiliser le code suivant.

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

Comme d'habitude, vous ne devez pas contourner l'autorisation d'accès si le code peut montrer à l'utilisateur des informations auxquelles l'utilisateur ne devrait pas avoir accès. Ceci est similaire à ce qui est fait à partir de Drupal lorsqu'un nœud non publié n'est affiché qu'aux utilisateurs qui ont l'autorisation de voir les nœuds non publiés. Si le but du code est, par exemple, de sélectionner des entités qui sont supprimées successivement (par exemple pendant les tâches cron), le contournement du contrôle d'accès ne fait aucun mal et c'est la seule façon de procéder.

kiamlaluno
la source
je dois admettre que je n'ai probablement pas raison, car la première requête utilise également un pager (je ->extend('PagerDefault');
n'ai
Oups, vous avez raison.
kiamlaluno
cela m'a vraiment intéressé, donc j'essaie quelque chose dans le sens de l'expérience ci-dessus et je ne peux pas confirmer l'énorme différence de chiffres ... quelqu'un pourrait-il l'essayer aussi, s'il vous plaît?
mojzis
Donc, juste pour confirmer: EFQ appelle TOUJOURS invoquer les règles d'accès au nœud du site à moins que vous ne fassiez quelque chose pour empêcher cela (comme décrit ci-dessus). Droite?
Jim Miller
@JimMiller C'est exact, et c'est la raison pour laquelle la balise "DANGEROUS_ACCESS_CHECK_OPT_OUT" a été ajoutée à Drupal 7.15.
kiamlaluno