Diferencias entre LAZY y EXTRA_LAZY en Doctrine

Hola buenas,

Estoy haciendo unos ejemplos para la optimización de consultas Doctrine, he empezado por las diferentes opciones que nos permite Doctrine de recuperar los registros: LAZY, EAGER y EXTRA_LAZY.

Tengo dudas sobre EXTRA_LAZY. Según entiendo y he observado en el web profile, hace el mismo número de consultas que LAZY, pero no logro entender ni aclarar conceptos.

Al utilizar algunos métodos de la colección como : Collection#contains, Collection#count, etc. "que no es lo mismo que el número de consultas" repercuten en el rendimiento

En un ejemplo de una entidad Article oneToMany Comments, al utilizar EXTRA_LAZY y hacer una consulta que muestre todos los artículos y el total de comentarios por artículo {{ article.comments.count }} o {{ article.comments | lenght}}.

Aquí según entiendo EXTRA_LAZY ¿solo accedería a la entidad asociada (Comentario) una sola vez, y con LAZY siempre? ¿Hay alguna manera de ver en el profiler dicho rendimiento que no sea el número de consultas?

Sin más... os doy las gracias y espero haberme explicado más o menos. Un saludo Juan Luis

Respuestas

#1

Según esta página de la documentación de Doctrine, la principal diferencia es:

Associations are marked as Lazy by default, which means the whole collection object for an association is populated the first time its accessed. If you mark an association as extra lazy the following methods on collections can be called without triggering a full load of the collection.

Para entender mejor la diferencia, imagina que la colección tiene 1 millón de objetos. Si quieres contar cuántos elementos tiene la colección, con la relación LAZY hay que cargar la colección entera y luego contar (por lo que seguramente el sistema se queda sin memoria). Si la relación es EXTRA_LAZY, entonces puedes contar el número de elementos sin tener que cargarlos todos. Así que en este segundo caso el resultado será inmediato y apenas consumirá memoria.

Según esto, si configuras una relación EXTRA_LAZY, siempre deberías hacer article.comments.count en vez de article.comments|lenght, ya que el primer caso no carga nada y el segundo caso carga la colección entera. He borrado este párrafo porque no era correcto. Leer este comentario para saber por qué.

#2

Muchas gracias Javier

Saludos

#3

Para complementar, expondré varios casos y lo que ocurre en cada uno de ellos:

Con la opción LAZY

Ejemplo 1:

$country->getStates(); // No hace nada.
 
count($country->getStates()); // Provoca una consulta para cargar los estados.
count($country->getStates()); // No genera una nueva consuta, porque ya se cargó la colección.
 
foreach($country->getStates() as $state){ 
   // No hace una consulta, porque ya se cargó la colección.
}

Ejemplo 2:

$country->getStates(); // No hace nada.
 
foreach($country->getStates() as $state){ 
   // Provoca una consulta para cargar los estados.
}
 
count($country->getStates());  // No genera una nueva consuta, porque ya se cargó la colección.

Con la opción EXTRA_LAZY

Ejemplo 1:

$country->getStates(); // No hace nada.
 
count($country->getStates()); // Provoca una consulta "SELECT COUNT(*) FROM ...", y no carga la colección.
count($country->getStates()); // Provoca una nueva consulta "SELECT COUNT(*) FROM ...", y no carga la colección.
 
foreach($country->getStates() as $state){ 
   // Provoca una consulta para cargar los estados.
}
 
foreach($country->getStates() as $state){ 
   // No genera una nueva consuta, porque ya se cargó la colección.
}
 
count($country->getStates()); // No genera una nueva consuta, porque ya se cargó la colección.

Ejemplo 2:

$country->getStates(); // No hace nada.
 
foreach($country->getStates() as $state){ 
   // Provoca una consulta para cargar los estados.
}
 
count($country->getStates());  // No genera una nueva consuta, porque ya se cargó la colección.

Esas son las diferencias entre el uso de la opción LAZY y EXTRA_LAZY.


Por otro lado, hacer count($collection) o $collection->count() es indiferente, y funcionará exactamente igual dependiendo del tipo de asociación.

Por lo que:

{{ article.comments|length }}
{{ article.comments.count }}

Hacen exactamente lo mismo. Saludos!

#4

¡Muchas gracias por la explicación tan detallada @manuel_j555! Tenía dudas con lo del count y el length, así que gracias por resolverlas.

#5

Hola @javiereguiluz y @manuel_j555

Estoy haciendo pruebas sobre utilizar {{article.comments|length}} o {{article.comments.count}}

Con Extra Lazy sucede lo siguiente (El orden si importa):

{{ article.comments|length }} {# Provoca una consulta extra para calcular total de comentarios y no carga la colección #}
 
{{ article.comments|length }} {# Provoca una consulta extra para calcular total de comentarios y no carga la colección #}
 
{{ article.comments.count }} {# Provoca una consulta extra para calcular total de comentarios y carga la colección #}
 
{{ article.comments|length }} {# No provoca una consulta extra por que ya se cargo la colección #}
 
{# Conclusión: una vez realizado article.comments.count si utilizas el filtro |length o .count nuevamente ya no realiza más consultas extras por que la asociación está cargada en la colección.  #}

Con Lazy sucede lo siguiente (el orden no importa):

{{ article.comments|length }} {# Provoca una consulta extra para calcular total de comentarios y carga la colección #}
 
{{ article.comments|length }} {# No provoca una consulta extra por que ya se cargo la colección#}
 
{{ article.comments.count }} {#No provoca una consulta extra por que ya se cargo la colección #}
 
{# Conclusión: Si utilizas .count o |length realizará una consulta extra y cargará la colección, a partir de ahí, todas las veces que utilices .count o |legth no generará una nueva consulta.  #}

Saludos

#6

Efectivamente hay una diferencia entre usar {{ article.comments.count }} y {{ article.comments|length }}.

Donde no hay diferencia es al hacer:

{{ article.comments|length }}
{{ article.comments.count() }} {# con los parentesis #}

Cuando se ejecuta {{ article.comments.count }} se carga la colección (cosa que a mi parecer no debería ocurrir).

Investigando, encontré que se carga debido a que en el método Twig_Template::getAttribute(), para invocar al método count, en algún momento y debido a que no se sabe si lo que el usuario quiere mostrar es un atributo o un método, hay una condición con un isset($object[$key]) donde $key es igual a 'count' y $object es la colección, y es este llamado el provoca que se cargue la colección.

En conclusión, si es diferente usar {{ article.comments.count }} y {{ article.comments|length }} debido a que el primero Si carga la colección.

#7

Muchas gracias @manuel_j555 y @javiereguiluz

He creado un post sobre las diferentes cargas de doctrine (lazy, eager y extra_lazy) con ejemplos y mostrando el resultado que da el web profiler [al final del articulo os lo he agradecido =) ]

Saludos