# Documentation Django 6.0.7

# Optimisation de l’accès à la base de données

La couche de base de données de Django fournit plusieurs manières d’aider les développeurs à tirer le meilleur de leurs bases de données. Ce document rassemble des liens vers la documentation adéquate et ajoute quelques astuces, réparties dans plusieurs chapitres respectant la chronologie des étapes à suivre lorsqu’on souhaite optimiser l’utilisation de la base de données.

## Priorité au profilage

Comme principe de programmation générale, cela va sans dire. Recherchez quelles sont les requêtes effectuées et ce qu’elles coûtent. Utilisez `QuerySet.explain()` pour comprendre comment les requêtes `QuerySet` sont exécutées par la base de données. Il est aussi possible d’utiliser un projet externe comme django-debug-toolbar ou un outil qui surveille directement la base de données.

Rappelez-vous que l’on peut optimiser pour la rapidité, pour la mémoire ou pour les deux, en fonction des besoins. Parfois, l’optimisation d’un côté va péjorer la situation de l’autre, mais dans d’autres cas, les deux vont en profiter ensemble. De plus, le travail effectué par le processus de la base de données n’a pas toujours le même coût (pour vous) que le même travail effectué dans le processus Python. C’est à vous de décider des priorités, de pondérer avantages et inconvénients et de profiler tout cela selon les besoins car tout dépend de votre application et du serveur.

Avec tout ce qui suit, n’oubliez pas de profiler après chaque modification pour vous assurer que celle-ci est bénéfique et que le bénéfice est suffisant pour compenser la perte de lisibilité du code. Pour toutes les suggestions ci-dessous, il faut tenir compte du risque que le principe général ne s’applique pas dans votre cas de figure, ou que l’effet soit même inversé.

## Techniques standards d’optimisation de base de données

Ceci comprend :

- Des index. C’est la priorité n°1, après avoir déterminé par analyse de performance quels sont les index nécessaires. Utilisez `Meta.indexes` ou `Field.db_index` pour en ajouter à partir de Django. Envisagez l’utilisation d’index aux champs que vous interrogez fréquemment avec `filter()`, `exclude()`, `order_by()`, etc. car les index peuvent aider à accélérer les requêtes. Notez que la recherche des meilleurs index est un thème complexe et dépendant du moteur de base de données, également dépendant de l’application à construire. La charge induite par la maintenance d’un index peut anéantir les gains en terme de vitesse de requête.

- Utilisation appropriée des types de champs.

Nous supposerons que vous avez effectué les opérations mentionnées ci-dessus. La suite de ce document se concentre sur la façon d’utiliser Django de telle manière à ne pas effectuer de travail inutile. Ce document n’aborde cependant pas d’autres techniques d’optimisation qui s’appliquent à des opérations lourdes, comme la mise en cache à portée générale.

## Compréhension des objets `QuerySet`

La compréhension des QuerySet est capitale pour obtenir de bonnes performances avec du code simple. En particulier :

### Compréhension de l’évaluation du `QuerySet`

Pour éviter des problèmes de performance, il est important de comprendre :

- que les QuerySet sont différés.

- quand ils sont évalués.

- comment les données sont conservée en mémoire.

### Compréhension des attributs mis en cache

Tout comme la mise en cache d’un `QuerySet` global, il y a mise en cache du résultat des attributs dans les objets de l’ORM. En général, les attributs qui ne sont pas exécutables seront mis en cache. Par exemple, à partir des modèles d’exemple du blog:

    >>> entry = Entry.objects.get(id=1)
    >>> entry.blog  # Blog object is retrieved at this point
    >>> entry.blog  # cached version, no DB access

Mais généralement, les attributs exécutables provoquent des accès à la base de données à chaque appel :

    >>> entry = Entry.objects.get(id=1)
    >>> entry.authors.all()  # query performed
    >>> entry.authors.all()  # query performed again

Soyez prudent dans la lecture du code des gabarits, le système des gabarits ne permet pas d’utiliser des parenthèses, mais les objets exécutables sont appelés automatiquement, masquant la distinction ci-dessus.

Soyez prudent avec vos propres propriétés personnalisées, il vous revient d’implémenter leur mise en cache au besoin, par exemple en utilisant le décorateur `cached_property`.

### Utilisation de la balise de gabarit `with`

Pour faire usage du comportement de cache de `QuerySet`, il peut être nécessaire d’utiliser la balise de gabarit `with`.

### Utilisation de `iterator()`

Lorsque vous manipulez un grand nombre d’objets, le comportement de cache de `QuerySet` peut provoquer une grosse utilisation de la mémoire. Dans ce cas, `iterator()` peut aider.

### Utilisation de `explain()`

`QuerySet.explain()` apporte des informations détaillées sur la façon dont la base de données exécute les requêtes, y compris les index et jointures utilisées. Ces détails peuvent aider à trouver les requêtes qui pourraient être réécrites plus efficacement ou à identifier des index qui pourraient être ajoutés pour améliorer les performances.

## Travail de base de données en base de données plutôt qu’en Python

Par exemple :

- Au niveau le plus élémentaire, utilisez filter et exclude pour filtrer au niveau de la base de données.

- Use Expressions F() to filter based on other fields within the same model.

- Utilisez les annotations pour effectuer le travail d’agrégation dans la base de données.

Si ces outils ne suffisent pas à générer le code SQL nécessaire :

### Utilisation de `RawSQL`

Une méthode moins portable mais plus puissante est d’utiliser l’expression `RawSQL` qui permet d’ajouter explicitement du code SQL dans une requête. Si cela ne suffit toujours pas :

### Utilisation de SQL brut

Écrivez votre propre code SQL pour sélectionner des données ou remplir les modèles. Utilisez `django.db.connection.queries` pour examiner ce que Django écrit pour vous et considérez cela comme un point de départ.

## Sélection d’objets individuels en utilisant une colonne unique et indexée

Il y a deux raisons d’utiliser une colonne avec `unique` ou `db_index` lorsqu’on utilise `get()` pour récupérer des objets individuels. Premièrement, la requête sera plus rapide en raison de l’index de base de données. De plus, la requête pourrait s’effectuer beaucoup plus lentement si plusieurs objets correspondent à la recherche ; avec une contrainte d’unicité sur la colonne, vous avez la garantie que cela ne se produira jamais.

Ainsi, en se basant sur les modèles d’exemple du blog:

    >>> entry = Entry.objects.get(id=10)

sera plus rapide que :

    >>> entry = Entry.objects.get(headline="News Item Title")

parce qu”`id` est indexé dans la base de données et que son unicité est garantie.

La ligne suivante est potentiellement très lente :

    >>> entry = Entry.objects.get(headline__startswith="News")

Tout d’abord, `headline` n’est pas indexé, ce qui fait que la base de données sous-jacente prendra plus de temps pour trouver la ligne.

Second, the lookup may return multiple results. Even though Django applies a limit of 21 objects when using `get()`, the database may still need to scan everything to evaluate a non-unique condition. Narrowing your query up-front is more efficient.

## Retrieve related objects efficiently

Generally, accessing the database multiple times to retrieve different parts of a single « set » of data is less efficient than retrieving it all in one query. This is particularly important if you have a query that is executed in a loop, and could therefore end up doing many database queries, when only one is needed. Below are some techniques to combine queries for efficiency.

### Use the `FETCH_PEERS` fetch mode

Use the `FETCH_PEERS` fetch mode to make on-demand field access more efficient with bulk-fetching. Enable it for all usage of your models with a custom manager.

Using this fetch mode is easier than declaring fields to fetch with `select_related()` or `prefetch_related()`, especially when it’s hard to predict which fields will be accessed.

### Utilisation de `QuerySet.select_related()` et `prefetch_related()`

When the `FETCH_PEERS` fetch mode is not appropriate or efficient enough, use `select_related()` and `prefetch_related()`. Understand their documentation thoroughly and apply them where needed.

It may be useful to apply these methods in managers and default managers. Be aware when your manager is and is not used; for example, related object access uses the base manager rather than the default manager.

### Use `prefetch_related_objects()`

Where `prefetch_related` would be useful after the queryset has been evaluated, use `prefetch_related_objects()` to execute an extra prefetch.

## Ne pas récupérer des éléments inutilement

### Utilisation de `QuerySet.values()` et `values_list()`

Lorsque vous ne voulez qu’obtenir un dictionnaire ou une liste de valeurs sans avoir besoin d’objets modèles de l’ORM, faites bon usage de `values()`. Cette méthode peut être utile pour remplacer des objets modèles dans le code des gabarits ; tant que les dictionnaires résultants possèdent les mêmes attributs que ceux qui sont utilisés dans le gabarit, tout va bien.

### Utilisation de `QuerySet.defer()` et `only()`

Utilisez `defer()` et `only()` quand vous savez à l’avance que vous n’aurez pas besoin de certaines colonnes de base de données (ou non utiles dans la plupart des cas) afin d’éviter de les charger. Notez que si vous les utilisez quand même, l’ORM devra les charger à l’aide d’une nouvelle requête, ce qui peut potentiellement aggraver la situation quand cette technique n’est pas utilisée à bon escient.

N’utilisez pas trop agressivement les champs différés sans profilage car la base de données doit lire sur le disque la plupart des données non textuelles et non `VARCHAR` d’un enregistrement des résultats, même lorsqu’elle finit par n’exploiter que quelques colonnes. Les méthodes `defer()` et `only()` sont particulièrement adéquates lorsqu’on peut éviter de charger de grandes quantités de données textuelles ou quand la valeur de certains champs est coûteuse en terme de conversion en Python. Comme toujours, profilez d’abord, optimisez ensuite.

### Utilisation de `QuerySet.contains(obj)`

…si vous voulez uniquement savoir si `obj` est présent dans le résultat de requête, au lieu de `if obj in queryset`.

### Utilisation de `QuerySet.count()`

…si vous n’avez besoin que du nombre de lignes, et évitez `len(queryset)`.

### Utilisation de `QuerySet.exists()`

…si vous voulez uniquement savoir qu’il y a au moins un résultat, et évitez `if queryset`.

Mais :

### Ne pas abuser de `contains()`, `count()` et `exists()`

Si vous allez avoir besoin de données réelles du `QuerySet`, évaluez-le immédiatement.

Par exemple, si on dispose d’un modèle `Group` ayant une relation plusieurs-à-plusieurs vers `User`, le code suivant est optimal :

    members = group.members.all()

    if display_group_members:
        if members:
            if current_user in members:
                print("You and", len(members) - 1, "other users are members of this group.")
            else:
                print("There are", len(members), "members in this group.")

            for member in members:
                print(member.username)
        else:
            print("There are no members in this group.")

Il est optimal car :

1. Comme les objets QuerySet sont différés, aucune requête de base de données n’est exécutée si `display_group_members` vaut `False`.

2. Le stockage de `group.members.all()` dans la variable `members` permet de réutiliser son résultat mis en cache.

3. La ligne `if members:` provoque l’appel à `QuerySet.__bool__()`, ce qui provoque aussi l’exécution de la requête `group.members.all()` dans la base de données. S’il n’y a pas de résultats, elle renvoie `False`, sinon `True`.

4. La ligne `if current_user in members:` vérifie si l’utilisateur est dans le cache de résultats, donc aucune requête de base de données supplémentaire n’est effectuée.

5. L’emploi de `len(members)` appelle `QuerySet.__len__()`, réutilisant le résultat mis en cache, donc toujours sans requête supplémentaire vers la base de données.

6. La boucle `for member` utilise les données du cache.

Au total, le code effectue une ou zéro requête de base de données. La seule optimisation explicite effectuée est l’emploi de la variable `members`. Si on avait utilisé `QuerySet.exists()` pour le `if`, `QuerySet.contains()` pour le `in` ou `QuerySet.count()` pour le nombre total, cela aurait généré à chaque fois des requêtes supplémentaires.

### Utilisation de `QuerySet.update()` et `delete()`

Plutôt que de récupérer un ensemble d’objets, de définir certaines valeurs et de les enregistrer individuellement, utilisez une instruction SQL UPDATE groupée au moyen de QuerySet.update(). De la même façon, effectuez des suppressions groupées chaque fois que c’est possible.

Notez cependant que des méthodes de mise à jour groupées ne peuvent pas appeler les méthodes `save()` et `delete()` des instances individuelles, ce qui signifie que tout comportement spécialisé que vous auriez ajouté avec ces méthodes ne sera pas exécuté, y compris tout ce qui découle de l’utilisation des signaux attachés aux objets de base de données.

### Utilisation directe de valeurs de clé étrangère

Si vous n’avez besoin que de la valeur d’une clé étrangère, utilisez la valeur qui est déjà stockée dans l’objet que vous manipulez, plutôt que de récupérer tout l’objet lié pour simplement disposer de sa clé primaire. Par exemple, faites :

    entry.blog_id

au lieu de :

    entry.blog.id

### Ne pas trier les résultats sans raison

Le tri n’est pas gratuit ; chaque champ de tri est une opération que la base de données doit effectuer. Si un modèle possède un ordre de tri par défaut (`Meta.ordering`) et que vous n’en avez pas besoin, vous pouvez l’enlever sur un `QuerySet` en appelant `order_by()` sans paramètre.

L’ajout d’un index à votre base de données peut aider à améliorer les performances de tri.

## Utiliser les méthodes d’opérations groupées

Faites usage des méthodes groupées (`bulk`) pour réduire le nombre d’instructions SQL.

### Création groupée

Lors de la création d’objets, utilisez si possible la méthode `bulk_create()` pour réduire le nombre de requêtes SQL. Par exemple :

    Entry.objects.bulk_create(
        [
            Entry(headline="This is a test"),
            Entry(headline="This is only a test"),
        ]
    )

…est préférable à :

    Entry.objects.create(headline="This is a test")
    Entry.objects.create(headline="This is only a test")

Note that `bulk_create()` has several caveats, so ensure it’s appropriate for your use case.

### Mise à jour groupée

Lors de la mise à jour d’objets, utilisez si possible la méthode `bulk_update()` pour réduire le nombre de requêtes SQL. Étant donné une liste ou un résultat de requête d’objets :

    entries = Entry.objects.bulk_create(
        [
            Entry(headline="This is a test"),
            Entry(headline="This is only a test"),
        ]
    )

L’exemple suivant :

    entries[0].headline = "This is not a test"
    entries[1].headline = "This is no longer a test"
    Entry.objects.bulk_update(entries, ["headline"])

…est préférable à :

    entries[0].headline = "This is not a test"
    entries[0].save()
    entries[1].headline = "This is no longer a test"
    entries[1].save()

Note that `bulk_update()` has several caveats, so ensure it’s appropriate for your use case.

### Insertion groupée

Lors de l’insertion d’objets dans des champs `ManyToManyField`, utilisez `add()` avec plusieurs objets pour réduire le nombre de requêtes SQL. Par exemple

    my_band.members.add(me, my_friend)

…est préférable à :

    my_band.members.add(me)
    my_band.members.add(my_friend)

…où `Band` et `Artist` sont des modèles liés par une relation plusieurs-à-plusieurs.

Lors de l’insertion de plusieurs paires d’objets dans des champs `ManyToManyField` ou lorsque la table personnalisée `through` est définie, utilisez la méthode `bulk_create()` pour réduire le nombre de requêtes SQL. Par exemple

    PizzaToppingRelationship = Pizza.toppings.through
    PizzaToppingRelationship.objects.bulk_create(
        [
            PizzaToppingRelationship(pizza=my_pizza, topping=pepperoni),
            PizzaToppingRelationship(pizza=your_pizza, topping=pepperoni),
            PizzaToppingRelationship(pizza=your_pizza, topping=mushroom),
        ],
        ignore_conflicts=True,
    )

…est préférable à :

    my_pizza.toppings.add(pepperoni)
    your_pizza.toppings.add(pepperoni, mushroom)

…où `Pizza` et `Topping` ont une relation plusieurs-à-plusieurs. Notez qu’il existe un certain nombre de `restrictions à cette méthode`, il s’agit donc de s’assurer qu’elle correspond à votre cas d’utilisation.

### Suppression groupée

Lors de la suppression d’objets de champs `ManyToManyField`, utilisez `remove()` avec plusieurs objets pour réduire le nombre de requêtes SQL. Par exemple

    my_band.members.remove(me, my_friend)

…est préférable à :

    my_band.members.remove(me)
    my_band.members.remove(my_friend)

…où `Band` et `Artist` sont des modèles liés par une relation plusieurs-à-plusieurs.

When removing multiple many-to-many relationships involving several instances of the related models, use the `delete()` method on a filtered queryset of the field’s `through` model. By combining multiple conditions with Objets Q(), you can delete several relationships in a single query. For example:

    from django.db.models import Q

    PizzaToppingRelationship = Pizza.toppings.through
    PizzaToppingRelationship.objects.filter(
        Q(pizza=my_pizza, topping=pepperoni)
        | Q(pizza=your_pizza, topping=pepperoni)
        | Q(pizza=your_pizza, topping=mushroom)
    ).delete()

…est préférable à :

    my_pizza.toppings.remove(pepperoni)
    your_pizza.toppings.remove(pepperoni, mushroom)

…où `Pizza` et `Topping` sont liés par une relation plusieurs-à-plusieurs.