SELECT "faster" 是嵌套 INSERT 函数吗?

Is SELECT "faster" than function with nested INSERT?

我正在使用一个函数将行插入 table 如果它不存在,那么 return 就是该行的 ID。

每当我将函数放在 SELECT 语句中时,其值在 table 中尚不存在,例如:

SELECT * FROM table WHERE id = function(123);

...它 return 是一个空行。但是,运行再次使用相同的值将其 return 包含我想要查看的值的行。

为什么会这样? INSERT 运行ning 是否落后于 SELECT 速度?还是 PostgreSQL 在 table 不存在时缓存它,然后在下一个 运行 显示结果?

这里有一个现成的例子来说明这个问题是如何发生的:

CREATE TABLE IF NOT EXISTS test_table(
id INTEGER,
tvalue boolean
);

CREATE OR REPLACE FUNCTION test_function(user_id INTEGER)
    RETURNS integer
    LANGUAGE 'plpgsql'
AS $$
DECLARE
    __user_id INTEGER;

BEGIN
    EXECUTE format('SELECT * FROM test_table WHERE id = ')
    USING user_id
    INTO __user_id;

    IF __user_id IS NOT NULL THEN
        RETURN __user_id;

    ELSE
        INSERT INTO test_table(id, tvalue)
        VALUES (user_id, TRUE) 
        RETURNING id
        INTO __user_id;
        RETURN __user_id;
    END IF;
END;
$$;

通话:

SELECT * FROM test_table WHERE id = test_function(4);

要重现该问题,请传递 table 中不存在的任何整数。

示例在多个地方损坏。

  • 不需要动态 SQL 和 EXECUTE
  • SELECT *函数中的错误
  • 您的 table 定义应该对 (id).
  • 有一个 UNIQUEPRIMARY KEY 约束
  • 最重要的是,最后的 SELECT 语句注定会失败。由于 function is VOLATILE(必须如此),它会针对 table 中的 每个 现有行计算一次。即使这有效,这也将是一场性能噩梦。但事实并非如此。就像@user2864740 评论的那样,存在 可见性问题。 Postgres 根据函数的结果检查每个 existing 行,然后添加 1 行或更多行,而这些行还不在 SELECT 正在运行的快照中。

    SELECT * FROM test_table WHERE id = test_function(4);

这可行(但见下文!):

CREATE TABLE test_table (
  id     int PRIMARY KEY  --!
, tvalue bool
);

CREATE OR REPLACE FUNCTION test_function(_user_id int)
  RETURNS test_table LANGUAGE sql AS
$func$
   WITH ins AS (
      INSERT INTO test_table(id, tvalue)
      VALUES (_user_id, TRUE) 
      ON CONFLICT DO NOTHING
      RETURNING *
      )
   TABLE ins
   UNION ALL
   SELECT * FROM test_table WHERE id = _user_id
   LIMIT 1
$func$;

并将您的 SELECT 替换为:

SELECT * FROM test_function(1);

db<>fiddle here

相关:

  • Return a value if no record is found

并发调用仍然存在竞争条件。如果可以的话,请考虑:

  • Is SELECT or INSERT in a function prone to race conditions?