Postgres: in che modo SET NOT NULL è “più efficiente” del vincolo CHECK


17

Nei documenti PostgreSQL per Vincoli , dice

Un vincolo non nullo è funzionalmente equivalente alla creazione di un vincolo check CHECK (column_name IS NOT NULL), ma in PostgreSQL la creazione di un vincolo esplicito non null è più efficiente.

mi sto chiedendo

  • Cosa significa esattamente "più efficiente"?
  • Quali sono gli svantaggi dell'utilizzo CHECK (column_name IS NOT NULL)anziché SET NOT NULL?

Voglio essere in grado di aggiungere un NOT VALID CHECKvincolo e convalidarlo separatamente (quindi AccessExclusiveLockviene trattenuto solo per un breve periodo di tempo per l'aggiunta del vincolo e quindi ShareUpdateExclusiveLockviene trattenuto per il passaggio di convalida più lungo):

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

Invece di:

ALTER TABLE table_name
  ALTER COLUMN column_name
  SET NOT NULL;


Che aspetto hanno i piani di esecuzione se si esegue un not incon entrambe le varianti? Sono uguali o differiscono?
Martin Smith,

Risposte:


12

La mia ipotesi selvaggia: "più efficiente" significa "è necessario meno tempo per eseguire il controllo" (vantaggio di tempo). Può anche significare "è necessaria meno memoria per eseguire il controllo" (vantaggio di spazio). Potrebbe anche significare "ha meno effetti collaterali" (come non bloccare qualcosa o bloccarlo per periodi di tempo più brevi) ... ma non ho modo di sapere o verificare quel "vantaggio in più".

Non riesco a pensare a un modo semplice per verificare un possibile vantaggio di spazio (che, immagino, non è così importante quando la memoria al giorno d'oggi è economica). D'altra parte, non è così difficile verificare l'eventuale vantaggio temporale: basta creare due tabelle uguali, con la sola eccezione del vincolo. Inserisci un numero sufficientemente elevato di righe, ripeti alcune volte e controlla i tempi.

Questa è l'impostazione della tabella:

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);

Questa è una tabella aggiuntiva, utilizzata per memorizzare i tempi:

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)
) ;

E questo è il test eseguito, usando pgAdmin III, e la funzione 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;

Il risultato è riassunto nella seguente query:

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 ;

Con i seguenti risultati:

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 grafico dei valori mostra un'importante variabilità:

Tempo impiegato per ogni inserimento di 200000 righe (in secondi)

Quindi, in pratica, il CONTROLLO (colonna NON È NULL) è leggermente più lento (dello 0,5%). Tuttavia, questa piccola differenza può essere dovuta a qualsiasi motivo casuale, a condizione che la variabilità dei tempi sia molto più grande di quella. Quindi, non è statisticamente significativo.

Da un punto di vista pratico, ignorerei molto il "più efficiente" NOT NULL, perché non vedo davvero che sia significativo; mentre penso che l'assenza di un AccessExclusiveLocksia un vantaggio.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.