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)
. 有一个 UNIQUE
或 PRIMARY 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?
我正在使用一个函数将行插入 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)
. 有一个 最重要的是,最后的
SELECT
语句注定会失败。由于 function isVOLATILE
(必须如此),它会针对 table 中的 每个 现有行计算一次。即使这有效,这也将是一场性能噩梦。但事实并非如此。就像@user2864740 评论的那样,存在 也 可见性问题。 Postgres 根据函数的结果检查每个 existing 行,然后添加 1 行或更多行,而这些行还不在SELECT
正在运行的快照中。SELECT * FROM test_table WHERE id = test_function(4);
UNIQUE
或 PRIMARY KEY
约束
这可行(但见下文!):
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?