Cómo forzar la carga ansiosa y evitar problemas N+1 en Laravel

- Andrés Cruz

Cómo forzar la carga ansiosa y evitar problemas N+1 en Laravel

La carga ansiosa es el proceso de obtener todos los datos necesarios de la base de datos de una sola vez para evitar múltiples viajes innecesarios a la base de datos.

Es lo opuesto a la carga diferida, en la que realizamos múltiples viajes a la base de datos para obtener los datos necesarios cuando sea necesario.

Cuando recupera cualquier modelo de la base de datos y luego realiza cualquier tipo de procesamiento en las relaciones del modelo, es importante que utilice la carga inmediata. La carga ansiosa es súper simple usando Laravel y evita que te encuentres con el problema N+1 con tus datos. Este problema se debe a que se realizan N+1 consultas a la base de datos, donde N es el número de elementos que se obtienen de la base de datos. Para explicar esto mejor y darle algo de contexto, veamos el siguiente ejemplo.

Imagine que tiene dos modelos (Comentario y Autor) con una relación uno a uno entre ellos. Ahora imagina que tienes 100 comentarios y quieres recorrer cada uno de ellos y mostrar el nombre del autor.

Sin una carga ansiosa, su código podría verse así:

$comments = Comment::all();
foreach ($comments as $comment) {
 print_r($comment->author->name);
}

¡El código anterior daría como resultado 101 consultas a la base de datos porque los resultados están "cargados de forma diferida"! La primera consulta sería recuperar todos los comentarios. Las otras cien consultas provendrían de obtener el nombre del autor en cada iteración del ciclo. Obviamente, esto puede causar problemas de rendimiento y ralentizar la aplicación. Entonces, ¿cómo mejoraríamos esto?

Al utilizar la carga ansiosa, podríamos cambiar el código para que diga:

$comments = Comment::with('author')->get();
foreach ($comments as $comment) {
 print_r($comment->author->name);
}

Como puede ver, este código tiene casi el mismo aspecto y aún es legible. Al agregar ::with('author'), esto buscará todos los comentarios y luego realizará otra consulta para buscar a los autores a la vez. Entonces, ¡esto significa que habremos reducido la consulta de 101 a 2!

Es posible que incluso quieras llevar esto un paso más allá para optimizar el código y mejorar más el rendimiento cargando solo los campos que necesitas. Por ejemplo, si solo vamos a actuar en los campos de identificación y nombre de los autores, es posible que deseemos escribir la consulta de esta manera:

$comments = Comment::with('author:id,name')->get();
 
foreach ($comments as $comment) {
    print_r($comment->author->name);
}

Al hacer esto, solo los campos de identificación y nombre estarán disponibles para usar en la relación de autor y ninguno de los otros campos en la tabla de autores se habrá obtenido de la base de datos. Como resultado, esto puede tener algunos beneficios de rendimiento debido a la reducción de datos que se transfieren desde la base de datos.

Para obtener más información en la documentación de Laravel sobre carga ansiosa.

Evitar la carga diferida en Laravel

Recientemente se ha fusionado una nueva característica (agregada por Mohamed Said) en el código base de Laravel que le permite evitar que se produzca una carga diferida. Esta característica es increíblemente útil porque debería ayudar a garantizar que las relaciones estén cargadas de entusiasmo. Como resultado de esto, probablemente nos ayudará a mejorar el rendimiento y reducir la cantidad de consultas que se realizan a la base de datos como se muestra en el ejemplo anterior.

Es muy sencillo desactivar la carga diferida en tu aplicación Laravel. Todo lo que necesitamos hacer es agregar la siguiente línea al método boot() de nuestro AppServiceProvider:

Model::preventLazyLoading();

Entonces, en nuestro AppServiceProvider, se vería más o menos así:

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
   public function boot(): void
   {
       // ...
       Model::preventLazyLoading();
       // ...
   }
}

Allowing Lazy Loading in Production Environments

Es posible que solo desee habilitar esta función cuando se encuentre en su entorno de desarrollo local. Al hacer esto, puede alertarlo sobre lugares en su código que utilizan carga diferida mientras crea nuevas funciones, pero no bloquea completamente su sitio web de producción. Por esta misma razón, el método preventLazyLoading() acepta un booleano como argumento, por lo que podríamos utilizar la siguiente línea:

Model::preventLazyLoading(! app()->isProduction());

Entonces, en nuestro AppServiceProvider, podría verse así:

namespace App\Providers;

use Illuminate\Support\ServiceProvider;

class AppServiceProvider extends ServiceProvider
{
   public function boot(): void
   {
       // ...
       Model::preventLazyLoading(! app()->isProduction());
       // ...
   }
}

Al hacer esto, la función se desactivará si su APP_ENV está en producción, de modo que cualquier consulta de carga diferida que se haya escapado no provoque que se generen excepciones en su sitio.

¿Qué pasa si intentamos realizar una carga diferida?

Si tenemos la función habilitada (y hemos deshabilitado la carga diferida en nuestro proyecto Laravel) en nuestro proveedor de servicios e intentamos cargar una relación en un modelo, se generará una excepción Illuminate\Database\LazyLoadingViolationException.

Para darle un poco de contexto a esto, usemos nuestros ejemplos de modelos de Comentario y Autor de arriba. Digamos que tenemos la función habilitada.

El siguiente fragmento generaría una excepción:

$comments = Comment::all();
 foreach ($comments as $comment) {
 print_r($comment->author->name);
}

Sin embargo, el siguiente fragmento no generaría una excepción:

$comments = Comment::with('author')->get();

foreach ($comments as $comment) {
   print_r($comment->author->name);
}

Articulo original

https://ashallendesign.co.uk/blog/how-to-force-eager-loading-and-prevent-n-1-issues-in-laravel?utm_source=newsletter&utm_medium=email&utm_campaign=handy-laravel-collection-methods

Andrés Cruz

Desarrollo con Laravel, Django, Flask, CodeIgniter, HTML5, CSS3, MySQL, JavaScript, Vue, Android, iOS, Flutter

Andrés Cruz en Udemy

Acepto recibir anuncios de interes sobre este Blog.