Postgres: как SET NOT NULL «более эффективна», чем ограничение CHECK

17

В PostgreSQL документы для ограничений говорится

Не-нулевое ограничение функционально эквивалентно созданию проверочного ограничения CHECK (column_name IS NOT NULL), но в PostgreSQL создание явного ненулевого ограничения более эффективно.

мне любопытно

  • Что именно означает «более эффективный»?
  • Каковы недостатки использования CHECK (column_name IS NOT NULL)вместо SET NOT NULL?

Я хочу иметь возможность добавлять NOT VALID CHECKограничение и проверять его отдельно (поэтому AccessExclusiveLockоно удерживается только в течение короткого периода времени для добавления ограничения, а затем ShareUpdateExclusiveLockудерживается для более длинного шага проверки):

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

Вместо того:

ALTER TABLE table_name
  ALTER COLUMN column_name
  SET NOT NULL;
Робин Джозеф
источник
1
Связанный: dba.stackexchange.com/questions/66840/…
Эрвин Брандштеттер
Как выглядят планы выполнения, если вы делаете a not inс обоими вариантами? Они одинаковые или отличаются?
Мартин Смит

Ответы:

12

Моя дикая догадка: «более эффективный» означает «требуется меньше времени для выполнения проверки» (преимущество во времени). Это также может означать, что для выполнения проверки требуется меньше памяти (преимущество пространства). Это также может означать «имеет меньше побочных эффектов» (например, не блокировать что-либо или блокировать его на более короткие промежутки времени) ... но у меня нет способа узнать или проверить это «дополнительное преимущество».

Я не могу придумать простой способ проверить возможное космическое преимущество (которое, я думаю, не так важно, когда память сегодня дешева). С другой стороны, это не так сложно проверить на возможное преимущество во времени: просто создайте две одинаковые таблицы, с единственным исключением из ограничения. Вставьте достаточно большое количество строк, повторите несколько раз и проверьте время.

Это настройка таблицы:

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

Это дополнительная таблица, используемая для хранения таймингов:

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

И это тест, выполненный с использованием pgAdmin III и функции 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;

Результат суммируется в следующем запросе:

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 ;

Со следующими результатами:

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

График значений показывает важную изменчивость:

Время, затраченное на каждую вставку 200000 строк (в секундах)

Таким образом, на практике CHECK (столбец IS NOT NULL) очень немного медленнее (на 0,5%). Однако это небольшое различие может быть вызвано любой случайной причиной, при условии, что изменчивость временных параметров намного больше, чем эта. Таким образом, это не является статистически значимым.

С практической точки зрения я бы очень проигнорировал «более эффективную» NOT NULL, потому что я не вижу в ней значимости; в то время как я думаю, что отсутствие AccessExclusiveLockявляется преимуществом.

joanolo
источник