¿EntityFieldQuery es realmente tan ineficiente?

11

Soy un novato admitido en la API de Entity, pero estoy tratando de curar eso. Estoy trabajando en un sitio que usa varios tipos de contenido con varios campos adjuntos; nada sofisticado. Entonces, cuando quiero recuperar un conjunto de entradas, he estado, en mi ignorancia, llamando directamente a la base de datos y haciendo algo como esto:

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

(sí, probablemente podría colapsar un montón de estas líneas en una sola $the_questions->declaración; por favor, ignore eso por ahora).

Intentando reescribir esto con EntityFieldQuery, se me ocurre:

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

lo que me da los resultados deseados y seguramente es mucho más bonito.

Entonces, ahora me pregunto sobre el rendimiento. Como comienzo, lanzo cada uno de esos bits de código en un for()bucle estúpido , capturando time()antes y después de la ejecución. Ejecuto cada versión 100 veces sobre una base de datos no muy grande, y obtengo algo como esto:

  • Versión directa: 110 ms
  • Versión EFQ: 4943 ms

Obviamente obtengo resultados diferentes cuando vuelvo a ejecutar la prueba, pero los resultados están consistentemente en el mismo estadio.

Yikes ¿Estoy haciendo algo mal aquí, o es solo el costo de usar EFQ? No he realizado ningún ajuste especial de la base de datos con respecto a los tipos de contenido; son justo lo que viene de definir los tipos de contenido de la forma habitual basada en formularios. ¿Alguna idea? El código EFQ es definitivamente más limpio, pero realmente no creo que pueda permitirme un éxito de rendimiento 40x.

Jim Miller
fuente
3
¿Se pueden volcar las consultas SQL generadas?
Andre Baumeier
1
Vea este si no está seguro de cómo sacar el SQL de un EFQ
Clive
2
OK, hay progreso: lo que está sucediendo aquí es que mi sitio tiene un montón de reglas de acceso a nodos que aumentan bastante el tamaño de la consulta. Esos se aplicaban automáticamente a la consulta EFQ (aunque no haya ninguno ->addTag('node_access')en la consulta ??). Volví a realizar la consulta "directa" con una etiqueta node_access, y los tiempos de ejecución están mucho más cerca: el tiempo de EFQ ahora es solo un factor 2 mayor que el enfoque directo, lo que parece razonable dado el SQL relativo que ambos están bombeando (que Puedo publicar si a la gente todavía le importa). (continúa en el siguiente comentario ...)
Jim Miller
Entonces, la pregunta, supongo, es ¿por qué obtengo automáticamente el material node_access en la versión EFQ? ¿Pensé que tenía que pedirlo explícitamente a través de la cláusula addTag ()?
Jim Miller

Respuestas:

10

La EntityFieldQueryclase es tan eficiente como sus requisitos lo permiten. Debe ser compatible con cualquier clase de almacenamiento de campo, incluso con aquellos que usan un motor NoSQL para almacenar los datos de campo, como el que usa MongoDB . Por esa razón, EntityFieldQueryno se puede consultar directamente la base de datos, ya que el backend de almacenamiento de campo actual podría no utilizar una base de datos SQL.

Incluso en el caso de que el almacenamiento de campo utilice un motor SQL para almacenar sus datos, el equivalente de $query->leftJoin('field_data_field_user_role', 'role', 'n.nid = role.entity_id'); $query->condition('role.field_user_role_value', $some_value);la EntityFieldQueryclase requiere:

  • Código para construir el nombre de la tabla de la base de datos a partir del nombre del campo
  • Código para generar la condición que se utilizará para unir la tabla que contiene los datos del campo con la tabla que contiene los datos de la entidad
  • Código para construir el nombre de la fila de la base de datos que contiene los datos del campo

La diferencia es inmediatamente visible: en un caso está utilizando tres cadenas literales, mientras que en el otro caso hay un código que (en el caso más simple) está concatenando cadenas.

Según su comentario sobre el código que verifica si el usuario tiene permiso para acceder a los campos, puede omitir eso usando la siguiente línea al código usando la EntityFieldQueryclase.

$query->addTag('DANGEROUS_ACCESS_CHECK_OPT_OUT');

Esto funciona si está utilizando Drupal 7.15 o superior; para versiones anteriores, debe usar el siguiente código.

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

Como de costumbre, no debe omitir el permiso de acceso si el código puede mostrar al usuario información a la que el usuario no debería tener acceso. Esto es similar a lo que se hace desde Drupal cuando un nodo no publicado solo se muestra a los usuarios que tienen permiso para ver nodos no publicados. Si el propósito del código es, por ejemplo, seleccionar algunas entidades que se eliminan sucesivamente (por ejemplo, durante las tareas cron), eludir el control de acceso no causa ningún daño, y es la única forma de proceder.

kiamlaluno
fuente
Debo admitir que probablemente no estoy en lo cierto, ya que la primera consulta también usa un buscapersonas (no me di cuenta ->extend('PagerDefault');al principio)
mojzis
Vaya, tienes razón.
kiamlaluno
esto me interesó mucho, así que estoy intentando algo similar al experimento anterior y no puedo confirmar la gran diferencia en los números ... ¿podría alguien intentarlo también, por favor?
mojzis
Entonces, solo para confirmar: las llamadas EFQ SIEMPRE invocan las reglas de acceso a los nodos del sitio a menos que haga algo para evitar que eso suceda (como se describió anteriormente). ¿Derecho?
Jim Miller
@JimMiller Eso es correcto, y es la razón por la cual la etiqueta "DANGEROUS_ACCESS_CHECK_OPT_OUT" se ha agregado a Drupal 7.15.
kiamlaluno