Empieza tu proyecto con $200 USD Gratis (Válido por 60 días)

Digital Ocean es la plataforma que uso para hostear la mayoría de mis proyectos, incluido este blog, por lo que puedo recomendártelo sinceramente, usar este enlace no generará un costo extra sobre ti, me ayuda a mantener bajos los costos del blog y tú obtendrás $200 USD en crédito, válidos por 2 meses (60 días)

Índice del contenido

Diferencias entre select_related y prefetch_related en Django

Diferencias entre select_related y prefetch_related en Django

Los métodos select_related y prefetch_related se usan para reducir el número de queries que se realizan a la base de datos. Lo anterior se traduce en tiempo de respuesta para cada vista. Además, usar estos métodos es una de las acciones a implementar para mejorar el rendimiento de una aplicación de Django.

Solo ten en mente que hay mejores cosas que optimizar en tu aplicación que obsesionarte con su rendimiento , pero sí insistes considera echarle un vistazo a aggregate y annotate, demás de tener cuidado con usar este último pues las subqueries pueden volver tus queries increíblemente lentas.

Si quieres hostear una aplicación de Django de forma barata y con buen rendimiento, DO tiene VPS (ellos les llaman Droplets) desde $4 usd el mes

El método select_related se usa para seguir una relación de tipo ForeignKey o OneToOneField hacia los respectivos objetos a los que apunta y obtenerlos.

Al usar select_related tendremos una consulta más larga, sin embargo, la ventaja consiste en que ya no será necesario acceder nuevamente a la base de datos para obtener los objetos del modelo relacionado.

Esquema del funcionamiento de select_related

Esquema simplificado del funcionamiento de select_related

Considera este ejemplo:

from django.db import models

class Principal(models.Model):
    name = models.CharField(max_length=256)


class Derivado(models.Model):
    name = models.CharField(max_length=256)
    principal = models.ForeignKey(
        "Principal", related_name="derivados", on_delete=models.CASCADE
    )

Si intentamos acceder al objeto al que apunta la relación Foreign Key, se generará una nueva consulta a la base de datos. select_related evita esa consulta extra por cada objeto.

{% for object in queryset %}
    <p>{{object.name}}</p>
    <small>{{object.principal.name}}</small>
{% endfor %}

Por ejemplo, si tenemos tres objetos Derivados relacionados a un único objeto principal:

  • Una consulta principal que obtiene todos los objetos Derivado
  • Tres consultas, exactamente iguales, una para cada vez que accedemos al objeto principal a partir del objeto Derivado.

Uso en una consulta

Para usar select_related lo llamamos a partir de nuestra consulta, pasándole el nombre del campo que corresponde a nuestra relación con el otro modelo.

Derivado.objects.select_related("principal")

¿Cómo funciona select_related internamente?, select_related reemplaza las consultas múltiples que se realizan por un único INNER JOIN a nivel de la base de datos:

SELECT "my_app_derivado"."id",
       "my_app_derivado"."name",
       "my_app_derivado"."principal_id"
  FROM "my_app_derivado"

SELECT "my_app_principal"."id",
       "my_app_principal"."name"
  FROM "my_app_principal"
 WHERE "my_app_principal"."id" = '1'

SELECT "my_app_principal"."id",
       "my_app_principal"."name"
  FROM "my_app_principal"
 WHERE "my_app_principal"."id" = '1'

SELECT "my_app_principal"."id",
       "my_app_principal"."name"
  FROM "my_app_principal"
 WHERE "my_app_principal"."id" = '1'

De esta manera se reducen las múltiples consultas SQL a una sola consulta más larga.

SELECT "my_app_derivado"."id",
       "my_app_derivado"."name",
       "my_app_derivado"."principal_id",
       "my_app_principal"."id",
       "my_app_principal"."name"
  FROM "my_app_derivado"
 INNER JOIN "my_app_principal"
    ON ("my_app_derivado"."principal_id" = "my_app_principal"."id")

Si el método select_related recupera un único objeto a partir de un campo de relación única, el método prefetch_related se usa cuando tenemos una relación múltiple con otro modelo, es decir, una relación de tipo ManyToMany o un ForeignKey inverso**.

Esquema del funcionamiento de prefetch_related en django

Esquema simplificado del funcionamiento de prefetch_related

Considera este ejemplo, nota el campo ManyToManyField hacia el modelo Principal.

from django.db import models

class Principal(models.Model):
    name = models.CharField(max_length=256)


class MultiplesPrincipales(models.Model):
    name = models.CharField(max_length=256)
    principales = models.ManyToManyField("Principal", related_name="multiples")

Si accedemos al campo que representa a la relación múltiple de nuestro objeto, sin usar prefetch_related, estaremos impactando la base de datos con una nueva consulta.

{% for object in queryset %}
    <p>{{object.name}}</p>
    {% for principal in object.principales.all %}
      <!-- Una nueva consulta cada vez -->
      <p><small>{{principal.name}}</small></p>
    {% endfor %}
{% endfor %}

Uso en una consulta

Para usar el método prefetch_related llámalo al final de nuestra consulta, eligiendo aquel campo que represente la relación de muchos a muchos en nuestro objeto.

queryset = MultiplesPrincipales.objects.prefetch_related("principales")

¿Cómo funciona internamente prefecth_related? El método prefetch_related reemplaza las múltiples consultas SQL por solo 2 consultas SQL: una para la query principal y la otra para los objetos relacionados, posteriormente, unirá los datos usando Python.

SELECT "my_app_principal"."id",
       "my_app_principal"."name"
  FROM "my_app_principal"
 INNER JOIN "my_app_multiplesprincipales_principales"
    ON ("my_app_principal"."id" = "my_app_multiplesprincipales_principales"."principal_id")
 WHERE "my_app_multiplesprincipales_principales"."multiplesprincipales_id" = '1'

SELECT "my_app_principal"."id",
       "my_app_principal"."name"
  FROM "my_app_principal"
 INNER JOIN "my_app_multiplesprincipales_principales"
    ON ("my_app_principal"."id" = "my_app_multiplesprincipales_principales"."principal_id")
 WHERE "my_app_multiplesprincipales_principales"."multiplesprincipales_id" = '2'

SELECT "my_app_principal"."id",
       "my_app_principal"."name"
  FROM "my_app_principal"
 INNER JOIN "my_app_multiplesprincipales_principales"
    ON ("my_app_principal"."id" = "my_app_multiplesprincipales_principales"."principal_id")
 WHERE "my_app_multiplesprincipales_principales"."multiplesprincipales_id" = '3'

SELECT "my_app_principal"."id",
       "my_app_principal"."name"
  FROM "my_app_principal"
 INNER JOIN "my_app_multiplesprincipales_principales"
    ON ("my_app_principal"."id" = "my_app_multiplesprincipales_principales"."principal_id")
 WHERE "my_app_multiplesprincipales_principales"."multiplesprincipales_id" = '4'

Las múltiples consultas anteriores quedan reducidas a solo 2 consultas SQL.

SELECT "my_app_multiplesprincipales"."id",
       "my_app_multiplesprincipales"."name"
  FROM "my_app_multiplesprincipales"

SELECT ("my_app_multiplesprincipales_principales"."multiplesprincipales_id") AS "_prefetch_related_val_multiplesprincipales_id",
       "my_app_principal"."id",
       "my_app_principal"."name"
  FROM "my_app_principal"
 INNER JOIN "my_app_multiplesprincipales_principales"
    ON ("my_app_principal"."id" = "my_app_multiplesprincipales_principales"."principal_id")
 WHERE "my_app_multiplesprincipales_principales"."multiplesprincipales_id" IN ('1', '2', '3', '4')

Otros recursos relacionados

Eduardo Zepeda
Desarrollador web, entusiasta de los sistemas GNU/Linux y el Software Libre. Py, Ts y Go, pero abierto a otras opciones como el Rustaceanismo. Creo en las bondades de las criptodivisas más allá de la especulación monetaria.
Leer más