Diferencias entre select_related y prefetch_related en Django

Índice del contenido

Diferencias entre select_related y prefetch_related en Django

Los métodos de Django, 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.

select_related prefetch_related
Relaciones Foreign key o One to One Many to Many
Número de queries 1 2
Unión de los objetos Directo con SQL Usando Python

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

...

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'
...

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
Eduardo Zepeda
Llámame simplemente Ed. Desarrollador web y pregonador de Linux (GNU/Linux). Me gustan los Frameworks aburridos y predecibles; de esos que te ahorran escribir cientos de boilerplate. Creo que Django es la mejor herramienta para desarrollar MVPs. Solía creer en las ventajas de las criptomonedas más allá de la especulación monetaria.