Quelles sont les conséquences de ne pas spécifier NOT NULL dans PostgreSQL pour les champs qui ne peuvent pas être nuls?

10

J'ai une application (les données sont stockées dans PostgreSQL), où la majorité des champs dans les tables ne sont pas toujours nuls, mais le schéma de ces tables ne les applique pas. Par exemple, regardez cette fausse table:

CREATE TABLE "tbl" (
    "id" serial,
    "name" varchar(40),
    "num" int,
    "time" timestamp
    PRIMARY KEY ("id"),
    UNIQUE ("id")
);

En outre name, num, timene sont pas explicitement déclaré que NOT NULL, en réalité , ils sont, parce que l'application se produit du côté de l' application.


Mon sentiment est qu'il devrait être changé, mais le contrepoint est que le niveau d'application s'assure que les valeurs nulles ne peuvent pas apparaître ici et que personne d'autre ne modifie manuellement la table.

Ma question est : quels sont les avantages (performances, stockage, cohérence, autre chose) et inconvénients (en supposant que j'ai déjà vérifié qu'il n'y a pas de null présents pour le moment, et de la logique métier il ne devrait pas y avoir de null) en définissant un NOT NULLcontrainte explicite ?

Nous avons un bon processus de révision du code et une documentation raisonnablement bonne, donc la possibilité qu'une nouvelle personne commette quelque chose qui brise cette contrainte n'est pas vraiment suffisante pour justifier le changement.

Ce n'est pas ma décision, c'est donc exactement pourquoi je cherche d'autres justifications. À mon avis, si quelque chose ne peut pas être nul et qu'une base de données vous permet de spécifier que quelque chose n'est pas nul, alors faites-le. Surtout si le changement est super simple.

Salvador Dali
la source
1
Voir cette réponse pour les considérations Nulls et espace disque: stackoverflow.com/questions/5008753/… En bref, si votre table a plus de 8 colonnes et au moins 1 colonne nullable, la table aura besoin de plus d'octets par ligne que si toutes les colonnes sont défini non nul.
ypercubeᵀᴹ
1
@ ypercubeᵀᴹ: pour être précis, le bitmap nul n'est ajouté par ligne que s'il y a une valeur nulle réelle dans la ligne: stackoverflow.com/a/7654497/939860 . Par conséquent, les NOT NULLcontraintes n'ont aucun effet direct sur la taille du stockage. Bien sûr, toutes les colonnes étant définies NOT NULL, il ne peut pas y avoir de bitmap nul au départ. D'un autre côté: la taille de stockage est généralement beaucoup plus petite si vous utilisez NULL au lieu de valeurs "vides" ou factices pour les colonnes sans valeur réelle, car le bitmap nul est comparativement beaucoup plus petit (sauf pour les cas marginaux rares).
Erwin Brandstetter
@ErwinBrandstetter ma mauvaise alors, n'avait pas compris cette partie. Donc, pour les colonnes qui n'ont pas de valeurs nulles, il n'y a pas de réelle différence de stockage, que vous les définissiez comme NULL ou NOT NULL, correct? Est-ce la même chose pour l'espace de stockage d'index?
ypercubeᵀᴹ
5
"le niveau d'application s'assure que les valeurs nulles ne peuvent pas apparaître ici" Non, ce n'est pas le cas. Il pourrait s'assurer qu'une application n'insère pas de null. Mais j'ai psql (par exemple), et je peux insérer des valeurs nulles à la fois délibérément et accidentellement sans que votre application le sache.
Mike Sherrill 'Cat Recall'
4
La seule application qui peut garantir que personne ne modifie manuellement la table est le dbms lui-même.
Mike Sherrill 'Cat Recall'

Réponses:

9

Que se passe-t-il lorsqu'un nouveau programmeur arrive et doit écrire une application sur cette base de données? Ils ne savent pas que le champ x doit être NOT NULL.

Un autre programme pourrait supposer que tous les champs x sont NOT NULLdestinés à effectuer des comptages, mais certains le sont maintenant à NULLcause du nouveau programme, ce qui entraîne des erreurs incohérentes et difficiles à retracer.

À mon humble avis, il est toujours préférable d'appliquer les règles d'intégrité des données aussi près que possible des données, c'est-à-dire dans la base de données. De cette façon, les nouvelles applications et / ou programmeurs ne peuvent pas gâcher vos données.

Les programmeurs, les applications, les langages et les frameworks vont et viennent. Les données et les bases de données ont tendance à persister. La base de données est votre dernière ligne de défense contre les données incohérentes et potentiellement erronées.

Faire un maximum l' utilisation des mécanismes d'application de la contrainte d'intégrité de votre base de données, même au détriment de la performance. Un système lent qui produit des résultats corrects est infiniment supérieur à un système rapide qui se trompe!

Vérace
la source
1
IMHO it is always best to enforce data integrity rules as near to the data as possiblec'est en fait le même que le sentiment d'intestin sur lequel j'ai écrit. Et c'est exactement pourquoi je recherche de vraies justifications. Nous avons un examen du code en place et une bonne documentation, donc les inquiétudes concernant un nouveau développeur ne sachant pas quelque chose ne suffisent pas pour justifier le changement.
Salvador Dali
4
Les révisions de code et une bonne documentation ne vous garantissent pas contre les erreurs (de programmation ou autres).
ypercubeᵀᴹ
2
Et combien ont REAL PROGRAMMERSlu la totalité (ou même n'importe laquelle) de la documentation avant de se retrouver coincés dans un projet où ils se trouvent dans un délai serré?
Vérace
3
J'ai fait une fois une revue dans une banque qui avait la même attitude pour leur entrepôt de données. Dans leur cas - pas d'intégrité référentielle. Eh bien, il arrive que 40% des données plus anciennes étaient des ordures parce que quelqu'un n'avait pas lu la documentation et supprimé les données dans les tables de recherche. Vous ne faites pas confiance aux revues de code et à la documentation avec l'intégrité des données - vous les rendez explicites dans la base de données.
TomTom
5

Comme déjà mentionné par d'autres dans les commentaires, l'ajout NOT NULLà votre spécification de table peut améliorer de manière significative les performances de vos requêtes (en plus des très bonnes raisons méthodologiques énoncées dans une autre réponse).

La raison en est que l'optimiseur de requêtes, sachant qu'une colonne ne peut pas avoir de NULLvaleur, peut exclure des tests spéciaux pour ces valeurs, comme dans le cas NOT INvs. NOT EXISTSVous pouvez voir par exemple ce blog , où il est montré que ne pas déclarer un champ NOT NULL(lorsque la table contient toujours des valeurs non nulles) avec une certaine requête augmente le temps d'exécution de 500%. Le résultat est affiché pour SQL Server, mais un comportement similaire pourrait être présent dans d'autres SGBD relationnels, comme le vôtre (sans parler du fait que votre base de données pourrait être portée sur d'autres systèmes). Une règle générale que vous pouvez supposer est que lorsque plus d'informations sont disponibles pour l'optimiseur de requête, des plans d'accès plus efficaces peuvent être produits.

Renzo
la source
Je vous remercie. C'est le type de réponse que je cherchais.
Salvador Dali
5
Les colonnes qui ne contiennent jamais NULL doivent être définies NOT NULLpour plusieurs raisons, aucun argument à ce sujet. Mais le lien vers le blog sur SQL Server n'est pas applicable à Postgres et ne prouve aucune des implications de performances que vous mentionnez. Je ne dis pas qu'il n'y en a pas, mais j'aimerais voir des preuves réelles .
Erwin Brandstetter
@ErwinBrandstetter, j'avais de grandes attentes concernant l'optimiseur PostgreSQL :( Après plusieurs tests, je n'ai pas trouvé de différences significatives dans la requête NOT IN présentée dans le blog de PostgreSQL avec et sans contrainte NOT NULL. J'ai donc changé la réponse , et je vous demande si vous pensez que je devrais le supprimer complètement.
Renzo
Non, je ne pense pas qu'il devrait être supprimé. Il a 5 + votes et aucun downvote, pour un.
ypercubeᵀᴹ
La sémantique de not inpour les colonnes nullables est différente, donc il doit y avoir une différence dans le plan entre les deux?
Martin Smith
2

Implications spatiales

Les implications spatiales sont discutées dans cet article de @Erwin Brandstetter

En bref, vous économiserez un totalColumns - 8bit arrondi à l'octet le plus proche (ou MAXALIGN), si votre base de données a

  1. Plus de 8 colonnes
  2. TOUTES les colonnes du tableau sontNOT NULL

Répercussions sur les performances

Cependant, dans ce post sur SE par @Erwin Brandstetter , il dit

  1. "La définition de NOT NULL n'a aucun effet en soi sur les performances. Quelques cycles pour la vérification - non pertinents."
  2. "... en utilisant réellement des valeurs NULL au lieu de valeurs fictives. Selon les types de données, vous pouvez économiser beaucoup d'espace disque et de RAM, accélérant ainsi .. tout."

@Renzo a une réponse qui parle des implications en termes de performances - je suppose que rien de tout cela n'est applicable à PostgreSQL . Je ne trouve rien qui justifie tout cela comme étant pertinent pour PostgreSQL. Quels que soient les cycles enregistrés, ils ne peuvent pas être quantifiés, même dans la requête la plus rudimentaire.

CREATE TABLE foo (
  a int,
  b int NOT NULL,
  x float,
  y float NOT NULL
);

INSERT INTO foo ( a, b, x, y )
SELECT x, x, x, x
FROM generate_series(1,1E7) AS X(x);

EXPLAIN ANALYZE SELECT 1/a FROM foo;
EXPLAIN ANALYZE SELECT 1/b FROM foo;
EXPLAIN ANALYZE SELECT 1/x FROM foo;
EXPLAIN ANALYZE SELECT 1/y FROM foo;

De plus, j'ai effectué des tests pour voir si les index NULL étaient toujours plus rapides, et je n'ai pas pu le prouver. Vous pouvez trouver ce fil de discussion très utile de Scott Marlowe sur les listes de diffusion qui parle du planificateur de requêtes dans 9.1 étant capable d'utiliser un index partiel sur des clauses WHERE différentes. J'ai testé cela en exécutant ce qui suit

CREATE TABLE foo ( a int );
CREATE TABLE bar ( a int NOT NULL );
INSERT INTO foo
  SELECT null FROM generate_series(1,1e5) AS x
  UNION ALL
  SELECT 10
  UNION ALL
  SELECT null FROM generate_series(1,1e5) AS x
;
INSERT INTO bar
  SELECT 0 FROM generate_series(1,1e5) AS x
  UNION ALL
  SELECT 10
  UNION ALL
  SELECT 0 FROM generate_series(1,1e5) AS x
;

Maintenant, j'ai créé les index,

CREATE INDEX foobar ON foo(a) WHERE a IS NOT NULL;
CREATE INDEX barbar ON bar(a) WHERE a <> 0;

Dans les deux cas, le planificateur a pu utiliser l'index lors de la sélection pour = 10et utilisé un balayage seq lors de la recherche de NULL ou de 0 respectivement. Les deux indices partiels avaient la même taille. Et, les index complets (non représentés) avaient la même taille. En suivant la même méthodologie, j'ai chargé le tableau avec une séquence de 1..1e5, et une valeur null / 0, et une autre séquence de 1..1e5. Les deux méthodes ont pu trouver le null / 0 avec un index couvrant toute la table.

TLDR; Résumé

Je ne peux rien prouver d'une manière ou d'une autre sur la plupart des problèmes de performance qui, selon moi, valaient la peine d'être testés pour inclure les insuffisances du planificateur. L'avantage d'utiliser null pour enregistrer ram est réel. L'espace disque économisé en n'utilisant pas null est négligeable, et c'est une surestimation sur les tables avec une NULLABLEcolonne, ou moins de 8 colonnes. Dans ces cas, aucun espace disque n'est enregistré.

Evan Carroll
la source