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;


not in両方のバリアントを使用すると、実行計画はどのようになりますか?それらは同じですか、または異なりますか?
マーティンスミス

回答:


12

私のワイルドな推測:「より効率的」とは、「チェックの実行に必要な時間が短くなる」ことを意味します(時間の利点)。「チェックを実行するために必要なメモリが少ない」ことも意味します(スペースの利点)。また、「副作用が少ない」(何かをロックしない、または短時間ロックするなど)を意味する場合もありますが、その「余分な利点」を知るまたは確認する方法がありません。

スペースの利点をチェックする簡単な方法を考えることはできません(最近のメモリが安い場合、それはそれほど重要ではないでしょう)。一方、可能な時間の利点を確認するのはそれほど難しくありません。制約を除いて、同じ2つのテーブルを作成するだけです。十分な数の行を挿入し、数回繰り返して、タイミングを確認します。

これはテーブルのセットアップです:

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(column IS NOT NULL)は非常にわずかに遅くなります(0.5%ずつ)。ただし、タイミングのばらつきがそれよりもはるかに大きい場合、この小さな差は任意のランダムな理由による可能性があります。したがって、統計的に有意ではありません。

実用的な観点からは、「より効率的」をあまり無視しNOT NULLますが、それは本当に重要だとは思わないからです。一方、がないことはAccessExclusiveLock利点だと思います。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.