Bases de données

Django prend officiellement en charge les bases de données suivantes :

Il existe également un certain nombre de moteurs de base de données fournis par des sources tierces.

Django tente d’activer autant de fonctionnalités que possible sur tous les types de base de données. Cependant, tous les types de base de données ne sont pas semblables, et nous avons dû prendre des décisions de conception sur les fonctionnalités à activer et les hypothèses sur lesquelles nous pouvions nous baser en toute sécurité.

Ce fichier décrit quelques-unes des caractéristiques qui pourraient être pertinentes pour l’utilisation de Django. Bien sûr, il ne remplace pas la documentation ou les manuels de référence spécifiques aux différents serveurs de base de données.

Remarques générales

Connexions persistantes

Les connexions persistantes évitent de devoir rétablir une connexion à la base de données pour chaque requête. Elles sont contrôlées par le réglage CONN_MAX_AGE qui définit la durée de vie maximale d’une connexion. Ce réglage peut être défini indépendamment pour chaque base de données.

La valeur par défaut est 0, pour préserver le comportement historique de la fermeture de la connexion de base de données à la fin de chaque requête. Pour activer les connexions persistantes, définissez CONN_MAX_AGE à un nombre positif de secondes. Pour que les connexions persistantes soient illimitées dans le temps, définissez-le à None.

Gestion des connexions

Django ouvre une connexion à la base de données lors de la première requête de base de données. Il garde cette connexion ouverte et la réutilise pour les requêtes suivantes. Django ferme la connexion dès que la limite de durée définie par CONN_MAX_AGE est dépassée ou que la connexion n’est plus utilisable.

Dans le détail, Django ouvre automatiquement une connexion à la base de données chaque fois qu’il en a besoin et qu’il n’en a pas déjà une – soit parce que c’est la première connexion, soit parce que la connexion précédente a été fermée.

Avant chaque requête à la base de données, Django clôture la connexion si celle-ci a atteint la limite de durée. Si votre base de données termine les connexions inactives après une certaine durée, vous devez définir CONN_MAX_AGE à une valeur inférieure, de sorte que Django ne cherche pas à utiliser une connexion qui a été interrompue par le serveur de base de données (ce problème ne peut affecter que les sites à très faible trafic).

À la fin de chaque requête à la base de données, Django clôture la connexion si celle-ci a atteint la limite de durée ou si elle est dans un état d’erreur irrécupérable. Si des erreurs de base de données ont eu lieu lors du traitement des requêtes, Django vérifie si la connexion fonctionne encore, et la ferme si ce n’est pas le cas. De cette manière, les erreurs de base de données affectent au plus une requête ; si la connexion devient inutilisable, la prochaine requête obtient une nouvelle connexion.

Mises en garde

Comme chaque fil d’exécution gère sa propre connexion, la base de données doit prendre en charge au moins autant de connexions simultanées qu’il n’y a de fils d’exécution (threads) de travail pour Django.

Parfois, la majorité des vues n’accèdent pas à une certaine base de données, par exemple parce que c’est la base de données d’un système externe, ou grâce à la mise en cache. Dans ce cas, vous devez définir CONN_MAX_AGE à une valeur faible, voire à 0, car cela n’a aucun sens de maintenir une connexion qui est peu susceptible d’être réutilisée. Cela contribuera à garder un petit nombre de connexions simultanées à cette base de données.

Le serveur de développement crée un nouveau fil d’exécution pour chaque requête qu’il gère, annulant l’effet des connexions persistantes. Ne les activez pas pendant le développement.

Lorsque Django établit une connexion à la base de données, il met en place les paramètres appropriés en fonction du type de base de données utilisé. Si vous activez les connexions persistantes, cette phase de configuration n’est pas répétée pour chaque requête. Si vous modifiez des paramètres tels que le niveau d’isolement de la connexion ou le fuseau horaire, vous devez soit restaurer les paramètres par défaut de Django à la fin de chaque requête, soit forcer une valeur appropriée avant chaque requête, soit désactiver les connexions persistantes.

Codage de caractères

Django suppose que toutes les bases de données utilisent le codage UTF-8. L’utilisation d’autres codages peut entraîner un comportement inattendu comme des erreurs de base de données « valeur trop longue » pour des données qui sont valides dans Django. Consultez les notes ci-dessous spécifiques à chaque base de données pour obtenir des informations sur la façon de configurer correctement votre base de données.

Notes sur PostgreSQL

Django prend en charge PostgreSQL 9.4 et plus récent. psycopg2 2.5.4 ou plus récent est requis, même s’il est recommandé d’utiliser la version la plus récente.

Paramètres de connexion PostgreSQL

Voir HOST pour plus de détails.

Optimisation de la configuration de PostgreSQL

Django a besoin des paramètres suivants pour se connecter aux bases de données :

  • client_encoding: 'UTF8',
  • default_transaction_isolation: 'read committed' par défaut, ou la valeur définie dans les options de connexion (voir ci-dessous),
  • timezone: 'UTC' quand USE_TZ vaut True, sinon indiquez la valeur de TIME_ZONE.

Si ces paramètres ont déjà les valeurs correctes, Django n’aura pas à les indiquer pour chaque nouvelle connexion, ce qui améliore légèrement les performances. Vous pouvez les configurer directement dans postgresql.conf ou plus commodément pour chaque utilisateur de base de données avec ALTER ROLE.

Django fonctionne très bien sans cette optimisation, mais chaque nouvelle connexion va faire quelques requêtes supplémentaires pour définir ces paramètres.

Niveau d’isolement

Comme pour PostgreSQL lui-même, le `niveau d'isolement`_ de Django vaut par défaut READ COMMITTED. Si vous avez besoin d’un plus haut niveau d’isolement tel que REPEATABLE READ ou SERIALIZABLE, définissez-le dans la partie OPTIONS de la configuration de base de données DATABASES:

import psycopg2.extensions

DATABASES = {
    # ...
    'OPTIONS': {
        'isolation_level': psycopg2.extensions.ISOLATION_LEVEL_SERIALIZABLE,
    },
}

Note

Sous des niveaux d’isolement plus élevés, votre application doit être préparée à gérer des exceptions générées lors d’échecs de sérialisation. Cette option est prévue pour des utilisateurs avancés.

Index pour les colonnes varchar et text

Lorsque vous indiquez db_index=True sur vos champs de modèle, Django génère généralement un seul CREATE INDEX. Toutefois, si le type de champ de base de données est varchar ou text (par exemple, pour les champs de type CharField, FileField ou TextField), Django crée alors un index supplémentaire qui utilise une classe opérateur de PostgreSQL appropriée pour la colonne. Cet index supplémentaire est nécessaire pour le bon fonctionnement des recherches qui utilisent l’opérateur SQL LIKE, comme le font les recherches avec contains et startswith.

Opération de migration pour ajouter des extensions

Si vous avez besoin d’ajouter une extension PostgreSQL (comme hstore, postgis, etc.) en utilisant une migration, utilisez l’opération CreateExtension.

Curseurs côté serveur

Lorsqu’on utilise QuerySet.iterator(), Django ouvre un curseur côté serveur. Par défaut, PostgreSQL suppose que seuls les premiers 10% des résultats des requêtes de curseur seront récupérées. Le planificateur de requête passe moins de temps à planifier la requête et commence à renvoyer des résultats plus rapidement, mais cela peut aussi diminuer les performances si plus de 10% des résultats sont récupérés. Les suppositions de PostgreSQL sur le nombre de lignes récupérées d’une requête de curseur sont contrôlées par l’option cursor_tuple_fraction.

Transactions groupées et curseurs côté serveur

L’utilisation d’un concentrateur (pooler) de connexion en mode groupement de transactions (par ex. pgBouncer) nécessite de désactiver les curseurs côté serveur pour cette connexion.

Les curseurs côté serveur sont propres à une connexion et restent ouverts à la fin d’une transaction lorsque AUTOCOMMIT vaut True. Une prochaine transaction peut demander l’obtention de davantage de résultats de la part du curseur côté serveur. En mode groupement de transactions, il n’y a aucune garantie que les transactions suivantes réutilisent la même connexion. Si une autre connexion est utilisée, une erreur survient lorsque la transaction fait référence au curseur côté serveur, car ces types de curseurs ne sont accessibles que pour la connexion dans laquelle ils ont été créés.

Une solution est de désactiver les curseurs côté serveur pour une connexion dans DATABASES en définissant DISABLE_SERVER_SIDE_CURSORS à True.

Pour bénéficier des curseurs côté serveur en mode de groupement des transactions, il est possible de définir une autre connexion à la base de données afin d’effectuer des requêtes utilisant les curseurs côté serveur. Cette connexion doit se faire soit directement à la base de données soit vers un concentrateur de connexions en mode de groupement par session.

Une autre option est d’envelopper chaque QuerySet utilisant des curseurs côté serveur dans un bloc atomic(), car ceci désactive le mode autocommit pour la durée de la transaction. De cette façon, le curseur côté serveur ne sera actif que pour la durée de la transaction.

Indication manuelle des valeurs de clé primaire avec autoincrémentation

Django utilise le type de données SERIAL de PostgreSQL pour stocker les clés primaires autoincrémentées. Une colonne SERIAL est remplie par des valeurs d’une séquence qui garde la trace de la prochaine valeur disponible. L’attribution manuelle d’une valeur à un champ autoincrémenté ne met pas à jour la séquence du champ, ce qui peut causer un conflit plus tard. Par exemple

>>> from django.contrib.auth.models import User
>>> User.objects.create(username='alice', pk=1)
<User: alice>
>>> # The sequence hasn't been updated; its next value is 1.
>>> User.objects.create(username='bob')
...
IntegrityError: duplicate key value violates unique constraint
"auth_user_pkey" DETAIL:  Key (id)=(1) already exists.

Si vous devez indiquer de telles valeurs, réinitialisez ensuite la séquence pour éviter de réutiliser une valeur qui se trouve déjà dans la table. La commande d’administration sqlsequencereset génère les commandes SQL qui font cela.

Modèles de base de données de test

On peut utiliser le réglage TEST['TEMPLATE'] pour indiquer un modèle (par ex. 'template0') à partir duquel la base de données de test est créée.

Accélération de l’exécution des tests par des réglages temporaires

Vous pouvez accélérer l’exécution des tests en configurant PostgreSQL avec perte acceptée.

Avertissement

C’est dangereux : votre base de données sera plus sujette aux pertes de données ou aux corruptions dans le case de plantage de serveur ou de coupure de courant. N’utilisez cela que sur des machines de développement où vous pouvez facilement restaurer l’ensemble de toutes les bases de données dans la grappe (cluster).

Notes sur MySQL

Versions prises en charge

Django prend en charge les versions 5.6 et plus récentes de MySQL.

La fonctionnalité inspectdb de Django utilise la base de données information_schema, qui contient des données détaillées sur tous les schémas de la base de données.

Django s’attend à ce que la base de données accepte l’Unicode (codage UTF-8) et lui délègue la charge de faire respecter les transactions et l’intégrité référentielle. Il est important d’être conscient du fait que ces deux derniers aspects ne sont pas réellement appliqués par MySQL lorsque le moteur de stockage MyISAM est utilisé, voir la section suivante.

Les moteurs de stockage

MySQL possède plusieurs moteurs de stockage. Vous pouvez changer le moteur de stockage par défaut dans la configuration du serveur.

Le moteur de stockage par défaut de MySQL est InnoDB. Ce moteur est pleinement transactionnel et prend en charge les références de clé étrangère. Il s’agit du choix recommandé. Cependant, le compteur d’incrémentation automatique de InnoDB est perdu au redémarrage de MySQL car il ne conserve pas la valeur AUTO_INCREMENT mais la recalcule comme « max(id)+1 ». Cela peut aboutir à une réutilisation inappropriée de valeurs AutoField.

Les désavantages principaux de MyISAM sont qu’il ne prend pas en charge les transactions et ne vérifie pas les contraintes de clé étrangère.

Pilotes DB API MySQL

MySQL propose plusieurs pilotes qui implémentent l’API Python de base de données documentée dans PEP 249:

  • mysqlclient est un pilote natif. Il s’agit du choix recommandé.
  • MySQL Connector/Python est un pilote en Python pur écrit par Oracle qui n’a pas besoin de la bibliothèque client MySQL ni d’autres modules Python en dehors de la bibliothèque standard.

Ces pilotes respectent la concurrence entre fils d’exécution (thread-safe) et gèrent le regroupement de connexions (pooling).

En plus d’un pilote DB API, Django a besoin d’un adaptateur pour accéder aux pilotes de bases de données à partir de son ORM. Django fournit un adaptateur pour mysqlclient alors que MySQL Connector/Python inclut le sien.

mysqlclient

Django nécessite la version 1.3.13 ou ultérieure de mysqlclient.

MySQL Connector/Python

MySQL Connector/Python est disponible sur cette page de téléchargement. L’adaptateur Django est disponible dans les versions 1.1.X et ultérieures. Il est possible qu’il ne prenne pas en charge la toute dernière version de Django.

Définitions de fuseaux horaires

Si vous prévoyez d’utiliser la prise en charge des fuseaux horaires de Django, utilisez mysql_tzinfo_to_sql pour charger les tableaux de fuseaux horaires dans la base de données MySQL. Cela doit être fait une seule fois par serveur MySQL, pas pour chaque base de données.

Création d’une base de données

Vous pouvez créer une base de données à l’aide des outils de ligne de commande et de cette commande SQL :

CREATE DATABASE <dbname> CHARACTER SET utf8;

Cela garantit que toutes les tables et colonnes utilisent le codage UTF-8 par défaut.

Paramètres de tri

La méthode de tri (« collation ») d’une colonne détermine l’ordre dans lequel les données sont triées ainsi que les comparaisons d’égalité entre les chaînes. Ce paramètre peut être défini à l’échelle de la base de données mais aussi par table et par colonne. Ceci est documenté complètement dans la documentation MySQL. Dans tous les cas, la méthode de tri est définie en modifiant directement les tables de la base de données ; Django ne fournit aucun moyen de faire cela au niveau de la définition du modèle.

Par défaut, avec une base de données UTF-8, MySQL utilise la collation utf8_general_ci. En conséquence, toutes les comparaisons d’égalité de chaînes se fait de manière insensible à la casse. C’est-à-dire que « Fred » et « freD » sont jugés équivalents pour la base de données. Si vous avez placé une contrainte unique sur un champ, il ne serait pas permis d’insérer à la fois « aa » et « AA » dans cette même colonne, dans la mesure où leur comparaison dit qu’elles sont identiques (et donc pas uniques) avec la collation par défaut. Si vous souhaitez effectuer des comparaisons sensibles à la casse sur une colonne ou table particulière, modifiez la collation de la colonne ou table en utf8_bin.

Notez que selon les Jeux de caractères Unicode MySQL, les comparaisons avec la collation utf8_general_ci sont plus rapides, mais légèrement moins correctes que les comparaisons avec utf8_unicode_ci. Si cela est acceptable pour votre application, il est préférable d’utiliser utf8_general_ci car elle est plus rapide. Dans le cas contraire (par exemple si vous avez besoin de l’ordre du dictionnaire allemand), utilisez utf8_unicode_ci car cette collation est plus juste.

Avertissement

Les sous-formulaires de modèles valident les champs uniques de manière sensible à la casse. Ainsi, lors de l’utilisation d’une collation de base de données insensible à la casse, des sous-formulaires contenant des valeurs de champs uniques qui ne diffèrent que par leur casse vont passer la validation avec succès, mais au moment de l’enregistrement par save(), une exception IntegrityError va se produire.

Connexion à la base de données

Reportez-vous à la documentation des réglages.

Les paramètres de connexion sont utilisés dans cet ordre :

  1. OPTIONS.
  2. NAME, USER, PASSWORD, HOST, PORT
  3. Fichiers d’options de MySQL.

En d’autres termes, si vous définissez le nom de la base de données dans OPTIONS, cette définition a la priorité sur NAME, qui aurait lui-même la priorité sur n’importe quelle valeur d’un fichier d’options MySQL.

Voici un exemple de configuration qui utilise un fichier d’options MySQL :

# settings.py
DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',
        'OPTIONS': {
            'read_default_file': '/path/to/my.cnf',
        },
    }
}


# my.cnf
[client]
database = NAME
user = USER
password = PASSWORD
default-character-set = utf8

Quelques autres options de connexion MySQLdb peuvent se révéler utiles, telles que ssl, init_command ou sql_mode.

Définition de sql_mode

À partir de MySQL 5.7 et pour les nouvelles installations de MySQL 5.6, la valeur par défaut de l’option sql_mode contient STRICT_TRANS_TABLES. Cette option transforme les avertissements en erreurs lorsque des données sont tronquées lors de leur insertion. Django recommande fortement d’activer un mode strict pour MySQL afin d’éviter des pertes de données (STRICT_TRANS_TABLES ou STRICT_ALL_TABLES).

SI vous avez besoin de personnaliser le mode SQL, vuos pouvez définir la variable sql_mode comme toute autre option MySQL : soit dans un fichier de configuration, soit par la ligne 'init_command': "SET sql_mode='STRICT_TRANS_TABLES'" dans la partie OPTIONS de la configuration de base de données dans DATABASES.

Niveau d’isolement

Lors d’un fonctionnement avec charge concurrentielle, les transactions de base de données de différentes sessions (par exemple des fils d’exécution séparés traitant différentes requêtes) peuvent interagir entre elles. Ces interactions sont affectées par le niveau d’isolation de transaction de chaque session. Il est possible de définir le niveau d’isolation de transaction d’une connexion avec la clé 'isolation_level' de la partie OPTIONS de la configuration de base de données dans DATABASES. Les valeurs autorisées pour cette clé sont les quatre niveaux d’isolation standards :

  • 'read uncommitted'
  • 'read committed'
  • 'repeatable read'
  • 'serializable'

ou None pour utiliser le niveau d’isolation configuré sur le serveur. Cependant, Django fonctionne mieux avec read committed (choix Django par défaut) plutôt que la valeur par défaut de MySQL qui est repeatable read. Des pertes de données sont possibles avec le niveau repeatable read. En particulier, vous pouvez rencontrer des cas où get_or_create() génère une exception IntegrityError mais l’objet n’apparaît pas dans de futurs appels à get().

Création des tables

Lorsque Django génère le schéma, il ne précise pas de moteur de stockage, donc les tables seront créées avec le moteur de stockage par défaut de votre serveur de base de données. La solution la plus simple est de définir le moteur souhaité comme moteur de stockage par défaut pour votre serveur de base de données.

Si vous utilisez un service d’hébergement et que vous ne pouvez pas modifier le moteur de stockage par défaut sur votre serveur, vous avez plusieurs possibilités.

  • Après la création des tables, exécutez une commande SQL ALTER TABLE pour convertir une table vers un nouveau moteur de stockage (comme InnoDB) :

    ALTER TABLE <tablename> ENGINE=INNODB;
    

    Cela peut être fastidieux si vous avez beaucoup de tables.

  • Une autre possibilité est d’utiliser l’option init_command de MySQLdb avant de créer les tables :

    'OPTIONS': {
       'init_command': 'SET default_storage_engine=INNODB',
    }
    

    Ceci définit le moteur de stockage par défaut lors de la connexion à la base de données. Une fois que les tables ont été créées, vous devriez supprimer cette option car elle ajoute une requête lors de chaque connexion à la base de données, alors que c’est uniquement nécessaire lors de la création d’une table.

Noms de tables

Certains problèmes connus, même dans les dernières versions de MySQL peuvent engendrer une modification d’un nom de table lorsque certaines instructions SQL sont exécutées sous certaines conditions. Il est recommandé d’utiliser des noms de table en minuscules, si possible, pour éviter les problèmes qui pourraient résulter de ce comportement. Django utilise des noms de tables en minuscules quand il génère automatiquement les noms de table à partir des modèles, c’est donc un élément à prendre en compte surtout si vous surchargez le nom de la table en utilisant le paramètre db_table.

Points de sauvegarde (« savepoints »)

L’ORM de Django tout comme MySQL (lorsque le moteur de stockage InnoDB est utilisé) prennent en charge les points de sauvegarde des bases de données.

Si vous utilisez le moteur de stockage MyISAM, vous recevrez des erreurs générées par la base de données si vous essayez d’utiliser les méthodes de l’API des transactions liées aux points de sauvegarde. En effet, comme la détection du moteur de stockage d’une table ou d’une base de données MySQL est une opération très coûteuse en ressources, il a été décidé de ne pas convertir dynamiquement ces méthodes en méthodes neutres en se basant sur ce genre de détection.

Notes sur des champs particuliers

Champs de type caractère

Tous les champs qui sont stockés dans des types de colonnes VARCHAR ont leur taille maximale (max_length) limitée à 255 caractères si vous utilisez unique=True pour ce champ. Cela concerne les champs CharField et SlugField.

Limites des champs

MySQL ne peut indexer que les N premiers caractères d’une colonne BLOB ou TEXT. Comme TextField n’a pas de longueur définie, il n’est pas possible de marquer un tel champ avec unique=True. MySQL produirait l’erreur : « BLOB/TEXT column “<db_column>” used in key specification without a key length ».

Prise en charge des fractions de secondes pour les champs heure et date/heure

À partir de la version 5.6.4, MySQL est capable de stocker des fractions de secondes, pour autant que les définitions de colonnes contiennent une indication adéquate (par ex. DATETIME(6)). Dans les versions précédentes, ce n’était pas pris en charge du tout.

Django ne va pas mettre à jour lui-même les colonnes existantes pour que les fractions de secondes soient incluses si le serveur de base de données le prend en charge. Si vous souhaitez les activer dans une base de données existante, c’est à vous de mettre à jour la colonne manuellement dans la base de données en exécutant une commande du style :

ALTER TABLE `your_table` MODIFY `your_datetime_column` DATETIME(6)

ou en utilisant une opération RunSQL dans une migration de données.

Colonnes TIMESTAMP

Si vous utilisez une base de données existante qui contient des colonnes TIMESTAMP, vous devez définir USE_TZ = False pour éviter de corrompre des données. inspectdb fait correspondre ces colonnes à des champs DateTimeField et si vous activez la prise en charge des fuseaux horaires, aussi bien MySQL que Django vont tenter de convertir les valeurs depuis le fuseau UTC vers le temps local.

Verrouillage de ligne avec QuerySet.select_for_update()

MySQL ne gère pas certaines options de l’instruction SELECT ... FOR UPDATE. Si select_for_update() est utilisé avec une option non prise en charge, une exception NotSupportedError est générée.

Option MySQL
SKIP LOCKED X (≥8.0.1)
NOWAIT X (≥8.0.1)
OF  

Lors de l’utilisation de select_for_update() avec MySQL, assurez-vous de filtrer la requête avec au moins un champ contenu dans les contraintes d’unicité ou uniquement avec des champs dotés d’index. Sinon, un verrou exclusif en écriture sera acquis pour toute la table durant toute la transaction.

Le retypage automatique peut produire des résultats inattendus

Lors de l’exécution d’une requête sur un type chaîne mais avec une valeur nombre entier, MySQL force les types de toutes les valeurs de la table à des nombres entiers avant d’effectuer la comparaison. Si la table contient les valeurs 'abc', 'def' et que la requête cherche WHERE macolonne=0, les deux lignes vont correspondre. De la même manière, WHERE macolonne=1 correspond à la valeur 'abc1'. C’est pourquoi les champs de type chaîne inclus dans Django forcent toujours la valeur à une chaîne avant de l’utiliser dans une requête.

Si vous implémentez des champs de modèle personnalisés héritant directement de Field, que vous surchargez get_prep_value() ou que vous utilisez RawSQL, extra() ou raw(), vous devez vous assurer d’effectuer les forçages de type appropriés.

Notes sur SQLite

Django prend en charge les versions 3.8.3 et plus récentes de SQLite.

SQLite fournit une excellente alternative pour le développement d’applications qui sont essentiellement en lecture seule ou qui nécessitent une installation de plus petite taille. Comme avec tous les serveurs de base de données, cependant, il y a quelques différences spécifiques à SQLite à prendre en compte.

Recherche de portions de chaînes de caractères et sensibilité à la casse

Pour toutes les versions de SQLite, il y a un comportement peu intuitif lorsque l’on essaie de faire correspondre certains types de chaînes. Ce comportement est déclenché lors de l’utilisation des filtres iexact ou contains dans des QuerySets. Le comportement se divise en deux cas :

1. For substring matching, all matches are done case-insensitively. That is a filter such as filter(name__contains="aa") will match a name of "Aabb".

2. For strings containing characters outside the ASCII range, all exact string matches are performed case-sensitively, even when the case-insensitive options are passed into the query. So the iexact filter will behave exactly the same as the exact filter in these cases.

Il existe quelques solutions de contournement documentées sur sqlite.org, mais elles ne sont pas exploitées par le moteur SQLite par défaut de Django, car cela impliquerait des difficultés certaines pour le faire de manière robuste. Django présente donc le comportement SQLite par défaut et il faut en être bien conscient lors de filtrage de sous-chaînes ou de chaînes insensibles à la casse.

Gestion des nombres décimaux

SQLite n’a pas vraiment de type nombre décimal en interne. Les valeurs décimales sont converties dans le type de données REAL (nombre à virgule flottante 8-octets IEEE), comme présenté dans la documentation des types de données SQLite, ce qui explique pourquoi l’arithmétique sur des nombres décimaux à virgule flottante n’est pas géré correctement (problèmes d’arrondis).

Erreurs « Database is locked » (la base de données est verrouillée)

SQLite est supposé être une base de données légère et ne peut donc pas gérer un niveau élevé de concurrence. Les erreurs OperationalError: database is locked indiquent que l’application subit une concurrence plus élevée que ce que sqlite ne peut gérer dans sa configuration par défaut. Cette erreur signifie qu’un processus ou un fil d’exécution possède un verrou exclusif sur la connexion de base de données et qu’un autre fil d’exécution a dû attendre trop longtemps que le verrou se libère.

L’adaptateur SQLite de Python comporte une valeur d’expiration par défaut qui détermine le temps maximal d’attente de déverrouillage d’un autre fil d’exécution avant qu’il n’expire en générant une erreur OperationalError: database is locked.

Si vous obtenez cette erreur, vous pouvez résoudre le problème en :

  • Passant à un autre moteur de base de données. À un certain stade, SQLite devient vraiment trop léger pour des applications du monde réel, et ce type d’erreur de concurrence indique que ce point a été atteint.

  • Réécrivant le code pour réduire la concurrence et s’assurer que les transactions de base de données restent aussi brèves que possible.

  • Augmentant la valeur d’expiration par défaut en définissant l’option de base de données timeout:

    'OPTIONS': {
        # ...
        'timeout': 20,
        # ...
    }
    

    Cela ne fera que prolonger un peu le temps d’attente de SQLite avant de produire des erreurs « database is locked » ; le problème de base n’en est pas résolu pour autant.

QuerySet.select_for_update() non pris en charge

SQLite ne prend pas en charge la syntaxe SELECT ... FOR UPDATE. L’appel de cette méthode n’a aucun effet.

Le style de paramètre « pyformat » dans les requêtes brutes n’est pas pris en charge

Pour la plupart des moteurs, les requêtes brutes (Manager.raw() ou cursor.execute()) peuvent exploiter le style de paramètre « pyformat » où les substituants dans la requête sont écrits sous la forme '%(nom)s' et les paramètres sont transmis sous forme de dictionnaire au lieu de liste. SQLite ne prend pas cette syntaxe en charge.

Isolation lors de l’utilisation de QuerySet.iterator()

Il existe des situations spéciales décrites dans Isolation dans SQLite lorsqu’on modifie une table tout en la parcourant avec QuerySet.iterator(). Si une ligne est ajoutée, modifiée ou détruite dans la boucle, cette ligne peut apparaître ou non, ou même apparaître deux fois dans les résultats suivants en provenance de l’itérateur. Votre code doit gérer cela.

Notes sur Oracle

Django prend en charge les versions 12.1 et plus récentes du serveur de base de données Oracle. Il est nécessaire de posséder au minimum la version 6.0 du pilote Python cx_Oracle.

Pour que la commande python manage.py migrate fonctionne, l’utilisateur de base de données Oracle doit posséder les permissions d’exécuter les commandes suivantes :

  • CREATE TABLE
  • CREATE SEQUENCE
  • CREATE PROCEDURE
  • CREATE TRIGGER

Pour exécuter la suite de tests d’un projet, l’utilisateur doit généralement posséder les privilèges supplémentaires suivants :

  • CREATE USER
  • ALTER USER
  • DROP USER
  • CREATE TABLESPACE
  • DROP TABLESPACE
  • CREATE SESSION WITH ADMIN OPTION
  • CREATE TABLE WITH ADMIN OPTION
  • CREATE SEQUENCE WITH ADMIN OPTION
  • CREATE PROCEDURE WITH ADMIN OPTION
  • CREATE TRIGGER WITH ADMIN OPTION

Même si le rôle RESOURCE possède les privilèges requis CREATE TABLE, CREATE SEQUENCE, CREATE PROCEDURE et CREATE TRIGGER, et qu’un utilisateur disposant de RESOURCE WITH ADMIN OPTION peut accorder le privilège RESOURCE, cet utilisateur ne peut pas accorder de privilèges individuels (par ex. CREATE TABLE), ce qui fait que RESOURCE WITH ADMIN OPTION n’est généralement pas suffisant pour exécuter les tests.

Certaines suites de tests créent aussi des vues ou des vues matérialisées ; pour exécuter celles-ci, l’utilisateur a aussi besoin des privilèges CREATE VIEW WITH ADMIN OPTION et CREATE MATERIALIZED VIEW WITH ADMIN OPTION. C’est nécessaire en particulier pour la propre suite de tests de Django.

Tous ces privilèges sont inclus dans le rôle DBA, qui est adéquat dans le cadre d’une base de données de développement privée.

Le moteur de base de données Oracle utilise les paquets SYS.DBMS_LOB et SYS.DBMS_RANDOM, il faut donc que l’utilisateur possède les droits d’exécution pour lui. Il est normalement accessible par défaut à tous les utilisateurs, mais si ce n’est pas le cas, il faut attribuer les permissions comme ceci :

GRANT EXECUTE ON SYS.DBMS_LOB TO user;
GRANT EXECUTE ON SYS.DBMS_RANDOM TO user;

Connexion à la base de données

Pour se connecter en utilisant le nom de service de la base de données Oracle, le fichier settings.py doit ressembler à quelque chose comme ceci :

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.oracle',
        'NAME': 'xe',
        'USER': 'a_user',
        'PASSWORD': 'a_password',
        'HOST': '',
        'PORT': '',
    }
}

Dans ce cas, il faut laisser vide les deux clés HOST et PORT. Cependant, si vous n’utilisez pas de fichier tnsnames.ora ou une méthode de nommage similaire et que vous vouliez vous connecter en utilisant le SID (« xe » dans cet exemple), remplissez alors à la fois HOST et PORT, comme ceci :

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.oracle',
        'NAME': 'xe',
        'USER': 'a_user',
        'PASSWORD': 'a_password',
        'HOST': 'dbprod01ned.mycompany.com',
        'PORT': '1540',
    }
}

Il faut soit remplir les deux clés HOST et PORT, soit les laisser toutes deux vides. Django utilise un descripteur de connexion différent en fonction de ce choix.

DSN complet et Easy Connect

Un chaîn DSN complète ou Easy Connect peut être utilisée dans NAME si à la fois HOST et PORT sont vides. Ce format est requis par exemple lors de l’utilisation de RAC ou de bases de données enfichables sans tnsnames.ora.

Exemple d’une chaîne Easy Connect

'NAME': 'localhost:1521/orclpdb1',

Exemple d’une chaîne DSN complète

'NAME': (
    '(DESCRIPTION=(ADDRESS=(PROTOCOL=TCP)(HOST=localhost)(PORT=1521))'
    '(CONNECT_DATA=(SERVICE_NAME=orclpdb1)))'
),

Option threaded

Si vous prévoyez de faire fonctionner Django dans un environnement à fils d’exécution multiples (multithread), par exemple avec Apache et le module MPM par défaut sur tout système d’exploitation moderne), vous devez définir à True l’option threaded de votre configuration de base de données Oracle :

'OPTIONS': {
    'threaded': True,
},

Si vous ne le faites pas, vous risquez d’obtenir des plantées et d’autres comportements bizarres.

INSERT … RETURNING INTO

Par défaut, le moteur Oracle utilise une clause RETURNING INTO pour obtenir efficacement la valeur d’un champ AutoField lors de l’insertion de nouvelles lignes. Ce comportement peut aboutir à des erreurs DatabaseError dans certaines configurations particulières, comme lors de l’insertion dans une table distante ou dans une vue avec le déclencheur INSTEAD OF. La clause RETURNING INTO peut être désactivée en définissant l’option use_returning_into de la configuration de base de données à False:

'OPTIONS': {
    'use_returning_into': False,
},

Dans ce cas, le moteur Oracle utilise une requête SELECT séparée pour récupérer les valeurs AutoField.

Questions de nommage

Oracle impose une longueur limite de 30 caractères pour les noms. Pour respecter cela, le moteur tronque les identifiants de base de données si nécessaire, remplaçant les quatre caractères finaux du nom tronqué par une valeur de hachage MD5 reproductible. De plus, le moteur transforme les identifiants de base de données tout en majuscules.

Pour empêcher ces transformations (ce qui n’est généralement nécessaire que lorsqu’on a affaire à des bases de données existantes ou quand il faut accéder à des tables appartenant à d’autres utilisateurs), entourez la valeur de db_table par des guillemets :

class LegacyModel(models.Model):
    class Meta:
        db_table = '"name_left_in_lowercase"'

class ForeignModel(models.Model):
    class Meta:
        db_table = '"OTHER_USER"."NAME_ONLY_SEEMS_OVER_30"'

Les noms entre guillemets peuvent également être utilisés avec les autres moteurs de base de données pris en charge par Django ; mais ces guillemets n’ont un effet qu’avec Oracle.

Lors de l’exécution de migrate, une erreur ORA-06552 peut se produire si certains mots-clés Oracle sont employés comme noms de champs de modèle ou comme valeur de l’option db_column. Django place entre guillemets tous les identifiants utilisés dans les requêtes pour empêcher la plupart de ce genre de problèmes, mais cette erreur peut quand même se produire lorsqu’un type de données Oracle est utilisé comme nom de colonne. Plus particulièrement, essayez d’éviter d’utiliser les noms date, timestamp, number ou float comme noms de champs.

NULL et les chaînes vides

Django préfère généralement utiliser la chaîne vide ('') plutôt que NULL, mais Oracle considère ces deux valeurs comme identiques. Pour contourner cela, le moteur Oracle ignore l’option explicite null pour les champs où la chaîne vide est une valeur possible et génère les instructions SQL comme si null=True. Lors de la lecture à partir de la base de données, Django part du principe qu’une valeur NULL dans l’un de ces champs équivaut en réalité à la chaîne vide et les données sont silencieusement converties pour respecter ce principe.

Limites des champs

Le moteur Oracle stocke les champs TextFields dans des colonnes NCLOB. Oracle impose certaines limites à l’utilisation de telles colonnes LOB en général :

  • Les colonnes LOB ne peuvent pas être utilisées comme clés primaires.
  • Les colonnes LOB ne peuvent pas être utilisées dans les index.
  • Les colonnes LOB ne peuvent pas être utilisées dans une liste SELECT DISTINCT. Cela signifie qu’une tentative d’utiliser la méthode QuerySet.distinct pour un modèle qui contient des colonnes TextField produira une erreur ORA-00932``avec Oracle. Pour contourner ce problème, utilisez la méthode ``QuerySet.defer de concert avec distinct() afin d’éviter que des colonnes TextField ne se retrouvent incluses dans la liste SELECT DISTINCT.

Utilisation d’un moteur de base de données externe

En plus des bases de données prises en charge officiellement, il existe des moteurs externes à Django qui permettent d’utiliser d’autres bases de données avec Django :

Les versions de Django et les fonctionnalités ORM prises en charges par ces moteurs inofficiels varient considérablement. Si vous avez des questions concernant les capacités spécifiques de ces moteurs inofficiels ou des questions de support, vous devrez vous adresser aux canaux d’aide offerts par chacun de ces projets externes.

Back to Top