Postgres: Comment SET NOT NULL est-il «plus efficace» que la contrainte CHECK

17

Dans les documents PostgreSQL pour les contraintes , il est dit

Une contrainte non nulle est fonctionnellement équivalente à la création d'une contrainte de vérification CHECK (column_name IS NOT NULL), mais dans PostgreSQL, la création d'une contrainte explicite non nulle est plus efficace.

je me demande

  • Que signifie exactement «plus efficace»?
  • Quels sont les inconvénients d'utiliser CHECK (column_name IS NOT NULL)au lieu de SET NOT NULL?

Je veux pouvoir ajouter une NOT VALID CHECKcontrainte et la valider séparément (de sorte que le AccessExclusiveLockn'est maintenu que pendant une courte période de temps pour l'ajout de la contrainte, puis un ShareUpdateExclusiveLockest maintenu pendant l'étape de validation la plus longue):

ALTER TABLE table_name
  ADD CONSTRAINT column_constraint
  CHECK (column_name IS NOT NULL)
  NOT VALID;
ALTER TABLE table_name
  VALIDATE CONSTRAINT column_constraint;

Au lieu de:

ALTER TABLE table_name
  ALTER COLUMN column_name
  SET NOT NULL;
Robin Joseph
la source
1
En relation: dba.stackexchange.com/questions/66840/…
Erwin Brandstetter
À quoi ressemblent les plans d'exécution si vous effectuez un not inavec les deux variantes? Sont-ils identiques ou diffèrent-ils?
Martin Smith

Réponses:

12

Ma conjecture sauvage: "plus efficace" signifie "moins de temps est nécessaire pour effectuer la vérification" (avantage de temps). Cela peut également signifier «moins de mémoire est nécessaire pour effectuer la vérification» (avantage d'espace). Cela pourrait également signifier "a moins d'effets secondaires" (comme ne pas verrouiller quelque chose ou le verrouiller pour des périodes de temps plus courtes) ... mais je n'ai aucun moyen de connaître ou de vérifier cet "avantage supplémentaire".

Je ne peux pas penser à un moyen facile de vérifier un éventuel avantage d'espace (ce qui, je suppose, n'est pas si important lorsque la mémoire est bon marché de nos jours). D'un autre côté, ce n'est pas si difficile de vérifier le gain de temps possible: il suffit de créer deux tables qui sont les mêmes, à la seule exception de la contrainte. Insérez un nombre suffisamment important de lignes, répétez plusieurs fois et vérifiez les horaires.

Voici la configuration de la table:

CREATE TABLE t1
(
   id serial PRIMARY KEY, 
   value integer NOT NULL
) ;

CREATE TABLE t2
(
  id serial PRIMARY KEY,
  value integer
) ;

ALTER TABLE t2
  ADD CONSTRAINT explicit_check_not_null
  CHECK (value IS NOT NULL);

Il s'agit d'un tableau supplémentaire, utilisé pour stocker les horaires:

CREATE TABLE timings
(
   test_number integer, 
   table_tested integer /* 1 or 2 */, 
   start_time timestamp without time zone,
   end_time timestamp without time zone,
   PRIMARY KEY(test_number, table_tested)
) ;

Et c'est le test effectué, en utilisant pgAdmin III et la fonctionnalité pgScript .

declare @trial_number;
set @trial_number = 0;

BEGIN TRANSACTION;
while @trial_number <= 100
begin
    -- TEST FOR TABLE t1
    -- Insert start time
    INSERT INTO timings(test_number, table_tested, start_time) 
    VALUES (@trial_number, 1, clock_timestamp());

    -- Do the trial
    INSERT INTO t1(value) 
    SELECT 1.0
      FROM generate_series(1, 200000) ;

    -- Insert end time
    UPDATE timings 
       SET end_time=clock_timestamp() 
     WHERE test_number=@trial_number and table_tested = 1;

    -- TEST FOR TABLE t2
    -- Insert start time
    INSERT INTO timings(test_number, table_tested, start_time) 
    VALUES (@trial_number, 2, clock_timestamp());

        -- Do the trial
    INSERT INTO t2(value) 
    SELECT 1.0
    FROM generate_series(1, 200000) ;

    -- Insert end time
    UPDATE timings 
       SET end_time=clock_timestamp() 
     WHERE test_number=@trial_number and table_tested = 2;

    -- Increase loop counter
    set @trial_number = @trial_number + 1;
end 
COMMIT TRANSACTION;

Le résultat est résumé dans la requête suivante:

SELECT
    table_tested, 
    sum(delta_time), 
    avg(delta_time), 
    min(delta_time), 
    max(delta_time), 
    stddev_pop(delta_time) 
FROM
    (
    SELECT
        table_tested, extract(epoch from (end_time - start_time)) AS delta_time
    FROM
        timings
    ) AS delta_times
GROUP BY
    table_tested 
ORDER BY
    table_tested ;

Avec les résultats suivants:

table_tested | sum     | min   | max   | avg   | stddev_pop
-------------+---------+-------+-------+-------+-----------
           1 | 176.740 | 1.592 | 2.280 | 1.767 | 0.08913
           2 | 177.548 | 1.593 | 2.289 | 1.775 | 0.09159

Un graphique des valeurs montre une variabilité importante:

Temps passé pour chaque insertion de 200 000 lignes (en secondes)

Ainsi, en pratique, le CHECK (colonne IS NOT NULL) est très légèrement plus lent (de 0,5%). Cependant, cette petite différence peut être due à une raison aléatoire, à condition que la variabilité des timings soit bien plus importante que cela. Ce n'est donc pas statistiquement significatif.

D'un point de vue pratique, j'ignorerais beaucoup le "plus efficace" NOT NULL, car je ne vois pas vraiment qu'il est significatif; alors que je pense que l'absence d'un AccessExclusiveLockest un avantage.

joanolo
la source