web-dev-qa-db-fra.com

Performances "par défaut" de SQLAlchemy vs "défaut_serveur"

Existe-t-il un avantage (ou un inconvénient) en termes de performances lors de l'utilisation de default au lieu de server_default pour mapper les valeurs par défaut des colonnes de table lors de l'utilisation de SQLAlchemy avec PostgreSQL?

Ma compréhension est que default rend l'expression dans le INSERT (généralement) et que server_default place l'expression dans le CREATE TABLE déclaration. Semble être server_default est analogue à la gestion typique des valeurs par défaut directement dans la base de données, comme:

CREATE TABLE example (
    id serial PRIMARY KEY,
    updated timestamptz DEFAULT now()
);

... mais il n'est pas clair pour moi s'il est plus efficace de gérer les valeurs par défaut sur INSERT ou via la création de table.

Y aurait-il une amélioration ou une dégradation des performances pour les insertions de lignes si chacun des paramètres default dans l'exemple ci-dessous était modifié en server_default?

from uuid import uuid4
from sqlalchemy import Column, Boolean, DateTime, Integer
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.sql import func

Base = declarative_base()

class Item(Base):
    __tablename__ = 'item'

    id = Column(UUID(as_uuid=True), primary_key=True, default=uuid4)
    count = Column(Integer, nullable=False, default=0)
    flag = Column(Boolean, nullable=False, default=False)
    updated = Column(DateTime(timezone=True), nullable=False, default=func.now())

REMARQUE: la meilleure explication que j'ai trouvée jusqu'à présent pour savoir quand utiliser default au lieu de server_default ne traite pas des performances ( voir la réponse de Mike Bayer SO sur le sujet ). Mon résumé simplifié de cette explication est que default est préférable à server_default quand...

  • La base de données ne peut pas gérer l'expression dont vous avez besoin ou que vous souhaitez utiliser pour la valeur par défaut.
  • Vous ne pouvez pas ou ne souhaitez pas modifier directement le schéma.

... la question reste donc de savoir si les performances doivent être prises en compte lors du choix entre default et server_default?

14
benvc

Il est impossible de vous donner une réponse "c'est plus rapide", car les performances par expression de valeur par défaut peuvent varier considérablement, à la fois sur le serveur et en Python. Une fonction permettant de récupérer l'heure actuelle se comporte différemment d'une valeur scalaire par défaut.

Ensuite, vous devez comprendre que les valeurs par défaut peuvent être fournies de cinq manières différentes:

  • Valeurs par défaut scalaires côté client . Une valeur fixe, comme 0 Ou True. La valeur est utilisée dans une instruction INSERT.
  • fonction côté client Python . Appelé chaque fois qu'un défaut est nécessaire, produit la valeur à insérer, utilisé de la même manière qu'un défaut scalaire à partir de là. Ceux-ci peuvent être sensibles au contexte (avoir accès au contexte d'exécution actuel avec des valeurs à insérer).
  • expression SQL côté client ; cela génère un morceau supplémentaire de expression SQL qui est ensuite utilisé dans la requête et exécuté sur le serveur pour produire une valeur.
  • expression DLL côté serveur sont des expressions SQL qui sont ensuite stockées dans la définition de la table, elles font donc partie du schéma. Le serveur les utilise pour remplir une valeur pour toutes les colonnes omises des instructions INSERT, ou lorsqu'une valeur de colonne est définie sur DEFAULT dans un INSERT ou UPDATE déclaration.
  • Valeurs implicites ou déclencheurs côté serveur , où d'autres DLL tels que les déclencheurs ou les fonctionnalités de base de données spécifiques fournissent une valeur par défaut pour les colonnes.

Notez que lorsqu'il s'agit d'une expression SQL déterminant la valeur par défaut, qu'il s'agisse d'une expression SQL côté client, d'une expression DLL côté serveur ou d'un déclencheur, cela fait très peu de différence pour une base de données où la valeur par défaut l'expression vient. L'exécuteur de requête devra savoir comment produire des valeurs pour une colonne donnée, une fois analysée dans l'instruction DML ou la définition de schéma, le serveur doit toujours exécuter l'expression pour chaque ligne.

Le choix entre ces options va rarement être basé uniquement sur les performances, les performances devraient tout au plus être l'un des multiples aspects que vous considérez. De nombreux facteurs interviennent ici:

  • default avec une fonction scalaire ou Python produit directement une valeur par défaut Python, puis envoie la nouvelle valeur au serveur lors de l'insertion. Le code Python peut accéder à la valeur par défaut avant l'insertion des données dans la base de données.
  • Une expression SQL côté client, une valeur server_default Et des valeurs implicites et déclencheurs implicites côté serveur ont tous pour que le serveur génère la valeur par défaut, qui doit ensuite être récupérée par le client si vous voulez pouvoir y accéder dans la même session SQLAlchemy. Vous ne pouvez pas accéder à la valeur tant que l'objet n'a pas été inséré dans la base de données.

    Selon la requête exacte et la prise en charge de la base de données, SQLAlchemy peut être amené à effectuer des requêtes SQL extra pour générer une valeur par défaut avant l'instruction INSERT ou exécuter une SELECT distincte ensuite pour récupérer les valeurs par défaut qui ont été insérées. Vous pouvez contrôler quand cela se produit (directement lors de l'insertion ou lors du premier accès après le rinçage, avec la configuration du mappeur eager_defaults ).

  • Si plusieurs clients sur différentes plates-formes accèdent à la même base de données, un server_default Ou autre valeur par défaut attachée au schéma (tel qu'un déclencheur) garantit que tous les clients utiliseront les mêmes valeurs par défaut, malgré tout, tandis que les valeurs par défaut implémentées dans Python n'est pas accessible par d'autres plates-formes.

Lors de l'utilisation de PostgreSQL, SQLAlchemy peut utiliser la clause RETURNING pour les instructions DML , qui permet à un client d'accéder aux valeurs par défaut générées côté serveur en une seule étape.

Ainsi, lorsque vous utilisez une colonne server_default Par défaut qui calcule une nouvelle valeur pour chaque ligne (pas une valeur scalaire), vous économisez une petite quantité de temps côté Python et économisez une petite quantité de bande passante réseau comme vous ne l'êtes pas envoyer les données de cette colonne à la base de données. La base de données pourrait être plus rapide à créer cette même valeur, ou elle pourrait être plus lente; cela dépend en grande partie du type d'opération. Si vous devez avoir accès à la valeur par défaut générée à partir de Python, dans la même transaction, vous devez alors attendre un flux de retour de données, analysé par SQLAlchemy. Tous ces détails peuvent deviennent cependant insignifiants par rapport à tout ce qui se passe autour de l'insertion ou de la mise à jour des lignes.

Comprenez bien qu'un ORM est ne convient pas pour être utilisé pour des insertions ou des mises à jour de lignes en vrac hautes performances; citation de l'entrée SQAlchemy Performance FAQ :

L'ORM SQLAlchemy utilise l'unité de travail lors de la synchronisation des modifications dans la base de données. Ce modèle va bien au-delà de simples "insertions" de données. Il comprend que les attributs attribués aux objets sont reçus à l'aide d'un système d'instrumentation d'attributs qui suit les modifications sur les objets au fur et à mesure qu'ils sont effectués, comprend que toutes les lignes insérées sont suivies dans une carte d'identité qui a pour effet que pour chaque ligne SQLAlchemy doit récupérer son " le dernier identifiant inséré "s'il n'est pas déjà indiqué, et implique également que les lignes à insérer sont analysées et triées pour les dépendances selon les besoins. Les objets sont également soumis à un bon niveau de tenue de livres afin de maintenir tout cela en marche, ce qui pour un très grand nombre de lignes à la fois peut créer une quantité excessive de temps passé avec de grandes structures de données, il est donc préférable de les segmenter.

Fondamentalement, l'unité de travail est un degré élevé d'automatisation afin d'automatiser la tâche de persister un graphe d'objet complexe dans une base de données relationnelle sans code de persistance explicite, et cette automatisation a un prix.

Les ORM ne sont fondamentalement pas destinés aux insertions en vrac hautes performances - c'est la raison pour laquelle SQLAlchemy propose le Core en plus de l'ORM en tant que composant de première classe.

Parce qu'un ORM comme SQLAlchemy est livré avec un prix élevé, toute différence de performances entre un défaut côté serveur ou Python disparaît rapidement dans le bruit des opérations ORM.

Donc, si vous êtes préoccupé par les performances des opérations d'insertion ou de mise à jour en grande quantité, vous voudrez utiliser opérations en bloc pour celles-ci et activer les psycopg2 Aides à l'exécution par lots pour vraiment gagner en vitesse. Lors de l'utilisation de ces opérations en bloc, je m'attends à ce que les valeurs par défaut côté serveur améliorent les performances simplement en enregistrant la bande passante en déplaçant les données de ligne de Python vers le serveur, mais combien dépend de la nature exacte des valeurs par défaut.

Si les performances d'insertion et de mise à jour d'ORM en dehors des opérations en bloc sont un gros problème pour vous, vous devez tester vos options spécifiques. Je commencerais par le package SQLAlchemy examples.performance et ajoutez votre propre suite de tests en utilisant deux modèles qui ne diffèrent que par un seul server_default Et default configuration.

20
Martijn Pieters

Il y a autre chose d'important plutôt que de simplement comparer les performances des deux

Si vous aviez besoin d'ajouter une nouvelle colonne create_at (Not Null) à une table existante User contenant des données, default ne fonctionnera pas.

Si utilisé default, lors de la mise à niveau de la base de données, l'erreur se produira en disant qu'il ne peut pas insérer de valeur Null dans les données existantes dans la table. Et cela causera des problèmes importants si vous souhaitez conserver vos données, même uniquement pour les tests.

Et lorsqu'il est utilisé server_default, lors de la mise à niveau de la base de données, la base de données insérera la valeur DateTime actuelle dans toutes les données de test existantes précédentes.

Donc dans ce cas, seulement server_default marchera.

4