Postgres: Como SET NOT NULL é “mais eficiente” que a restrição CHECK

17

Nos documentos do PostgreSQL para restrições , ele diz

Uma restrição não nula é funcionalmente equivalente à criação de uma restrição de verificação CHECK (column_name IS NOT NULL), mas no PostgreSQL a criação de uma restrição explícita não nula é mais eficiente.

Eu estou pensando

  • O que exatamente significa "mais eficiente"?
  • Quais são os inconvenientes de usar em CHECK (column_name IS NOT NULL)vez de SET NOT NULL?

Quero poder adicionar uma NOT VALID CHECKrestrição e validá-la separadamente (portanto, ela AccessExclusiveLocké mantida apenas por um curto período de tempo para adicionar a restrição e, em seguida, a ShareUpdateExclusiveLocké mantida para a etapa de validação mais longa):

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

Ao invés de:

ALTER TABLE table_name
  ALTER COLUMN column_name
  SET NOT NULL;
Robin Joseph
fonte
11
Relacionados: dba.stackexchange.com/questions/66840/...
Erwin Brandstetter
Como são os planos de execução se você fizer um not incom as duas variantes? Eles são iguais ou diferem?
Martin Smith

Respostas:

12

Meu palpite: "mais eficiente" significa "menos tempo para executar a verificação" (vantagem de tempo). Também pode significar "menos memória é necessária para executar a verificação" (vantagem de espaço). Também pode significar "tem menos efeitos colaterais" (como não travar algo ou travá-lo por períodos mais curtos) ... mas não tenho como saber ou verificar essa "vantagem extra".

Não consigo pensar em uma maneira fácil de verificar uma possível vantagem de espaço (o que, eu acho, não é tão importante quando a memória hoje em dia é barata). Por outro lado, não é tão difícil verificar a possível vantagem de tempo: basta criar duas tabelas iguais, com a única exceção da restrição. Insira um número suficientemente grande de linhas, repita algumas vezes e verifique os tempos.

Esta é a configuração da tabela:

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

Esta é uma tabela extra, usada para armazenar horários:

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 este é o teste realizado, usando o pgAdmin III e o recurso 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;

O resultado é resumido na seguinte consulta:

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 ;

Com os seguintes resultados:

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

Um gráfico dos valores mostra uma variabilidade importante:

Tempo gasto para cada inserção de 200000 linhas (em segundos)

Portanto, na prática, o CHECK (a coluna NÃO É NULL) é um pouco mais lento (0,5%). No entanto, essa pequena diferença pode ser devido a qualquer motivo aleatório, desde que a variabilidade dos tempos seja muito maior que isso. Portanto, não é estatisticamente significativo.

Do ponto de vista prático, eu ignoraria muito o "mais eficiente" NOT NULL, porque realmente não vejo isso significativo; considerando que a ausência de um AccessExclusiveLocké uma vantagem.

joanolo
fonte