Postgres: Wie ist SET NOT NULL "effizienter" als die CHECK-Einschränkung?

16

In PostgreSQL-Dokumenten für Constraints heißt es

Eine Nicht-Null-Einschränkung ist funktional äquivalent zum Erstellen einer Prüfeinschränkung CHECK (column_name IS NOT NULL), aber in PostgreSQL ist das Erstellen einer expliziten Nicht-Null-Einschränkung effizienter.

ich frage mich

  • Was genau bedeutet "effizienter"?
  • Was sind die Nachteile von CHECK (column_name IS NOT NULL)statt SET NOT NULL?

Ich möchte eine NOT VALID CHECKEinschränkung hinzufügen und separat validieren können (daher wird die Einschränkung AccessExclusiveLocknur für einen kurzen Zeitraum zum Hinzufügen und dann ShareUpdateExclusiveLockfür den längeren Validierungsschritt angehalten):

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

Anstatt von:

ALTER TABLE table_name
  ALTER COLUMN column_name
  SET NOT NULL;
Robin Joseph
quelle
1
Related: dba.stackexchange.com/questions/66840/…
Erwin Brandstetter
Wie sehen die Ausführungspläne not inbei beiden Varianten aus? Sind sie gleich oder unterscheiden sie sich?
Martin Smith

Antworten:

11

Meine wilde Vermutung: "effizienter" bedeutet "weniger Zeit wird benötigt, um die Prüfung durchzuführen" (Zeitvorteil). Dies kann auch bedeuten, dass "weniger Speicher erforderlich ist, um die Prüfung durchzuführen" (Platzvorteil). Es könnte auch bedeuten, dass "weniger Nebenwirkungen" vorliegen (z. B., dass etwas nicht gesperrt oder für kürzere Zeit gesperrt wird) ... aber ich habe keine Möglichkeit, diesen "zusätzlichen Vorteil" zu kennen oder zu überprüfen.

Ich kann mir keinen einfachen Weg vorstellen, um nach einem möglichen Platzvorteil zu suchen (was, denke ich, nicht so wichtig ist, wenn Speicher heutzutage billig ist). Auf der anderen Seite ist es nicht so schwierig, den möglichen Zeitvorteil zu überprüfen: Erstellen Sie nur zwei Tabellen, die mit Ausnahme der Einschränkung identisch sind. Fügen Sie eine ausreichend große Anzahl von Zeilen ein, wiederholen Sie diese einige Male und überprüfen Sie die Timings.

Dies ist die Tabelleneinstellung:

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

Dies ist eine zusätzliche Tabelle zum Speichern von Timings:

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

Und dies ist der Test, der mit pgAdmin III und der pgScript-Funktion durchgeführt wurde .

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;

Das Ergebnis ist in der folgenden Abfrage zusammengefasst:

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 ;

Mit folgenden Ergebnissen:

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

Ein Diagramm der Werte zeigt eine wichtige Variabilität:

Zeitaufwand für jede Einfügung von 200000 Zeilen (in Sekunden)

In der Praxis ist CHECK (Spalte IST NICHT NULL) also etwas langsamer (um 0,5%). Dieser kleine Unterschied kann jedoch auf einen beliebigen Grund zurückzuführen sein, vorausgesetzt, die Variabilität der Zeitabläufe ist weitaus größer. Es ist also statistisch nicht signifikant.

Aus praktischer Sicht würde ich das "effizientere" System sehr ignorieren NOT NULL, da ich nicht wirklich sehe, dass es von Bedeutung ist. Ich halte das Fehlen AccessExclusiveLockeines von Vorteil.

joanolo
quelle