Postgres:SET NOT NULL如何比CHECK约束“更有效”


17

PostgreSQL文档Constraints中,它说

非空约束在功能上等同于创建检查约束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同时使用这两种变体,执行计划是什么样的?它们是相同还是不同?
马丁·史密斯

Answers:


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

值的图表显示出重要的可变性:

每插入20万行花费的时间(以秒为单位)

因此,在实践中,CHECK(列IS NOT NULL)非常慢(降低了0.5%)。但是,这种小的差异可能是由于任何随机原因造成的,前提是时序的可变性远大于此。因此,它在统计上并不重要。

从实际的角度来看,我将非常忽略“更有效的” NOT NULL,因为我并不认为它很重要。而我认为缺少an AccessExclusiveLock是有利的。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.