3

我想为 PG SQL 数据库中的模式创建一个更新函数。测试功能如下。它不起作用,因为它永远不应该发出通知,但会在运行时发出通知test_schema_update('second')

CREATE OR REPLACE FUNCTION test_schema_update(my_schema_name VARCHAR(200)) 
RETURNS void AS
$__marker__$
DECLARE
    actualValue varchar(1000);
    testValue varchar(1000);
BEGIN
    EXECUTE 'SET search_path TO ' || quote_ident(my_schema_name);

    testValue := (SELECT max(value) FROM setting WHERE settingkey = 'libraryname');
    EXECUTE ('SELECT max(value) FROM setting WHERE settingkey = ''libraryname''')
        INTO actualValue;

    IF (actualValue != testValue)
    THEN
        RAISE NOTICE '% != %', actualValue, testValue;
        RAISE INFO 'Schema was: %', current_schema();
    END IF;

    RESET search_path;
END;
$__marker__$ LANGUAGE plpgsql;

test_schema_update('first');
test_schema_update('second');

问题是 PG SQL 似乎SELECT每个会话只分析一次语句,然后将表固定到特定模式。有趣的是你会得到Schema was: second.

那么有没有办法重置SELECT语句分析或其他方法来解决这个问题?

旁注:所有模式创建功能(ALTER TABLECREATE TABLE...)都可以正常工作。似乎只有数据操作函数受到影响(SELECT, INSERT, UPDATE)。

解决方法

前:

IF (
    SELECT max(id) FROM dimtime
)
THEN
    INSERT INTO dimtime SELECT * FROM public.src_dimtime;
END IF;

后:

EXECUTE ('
    SELECT max(id) FROM dimtime
')
INTO testInt;
IF (testInt IS NULL)
THEN
    EXECUTE 'INSERT INTO dimtime SELECT * FROM public.src_dimtime';
END IF;

编辑:问题出现在 PostgreSQL 9.2 中,但似乎没有出现在 9.3 中。也许它是固定的?

4

1 回答 1

5

这种行为是意料之中的。原因是 PL/pgSQL 对 SQL 语句使用计划缓存,内部使用标准准备语句

根据文档:

由于每个表达式和 SQL 命令首先在函数中执行,PL/pgSQL 解释器创建一个准备好的执行计划(使用 SPI 管理器SPI_prepareSPI_saveplan函数)。对该表达式或命令的后续访问会重用准备好的计划。

这也是为什么 plpgsql 函数对于复杂操作通常比普通 SQL 函数更快的原因:

准备好的语句在session的生命周期内保存,而不仅仅是事务(但在底层对象更改时会失效,这对于并发访问是安全的)。文档再次:

一旦 PL/pgSQL 为函数中的特定命令制定了执行计划,它将在数据库连接的生命周期内重用该计划。这通常会提高性能,但如果您动态更改数据库模式,它可能会导致一些问题。

大胆强调我的。

如果你想“改变”一个表名的模式,你真的要引用一个完全不同的表,并且需要使用动态 SQL EXECUTE,它每次都会生成一个新的计划(有所有优点和缺点):

因为 PL/pgSQL 以这种方式保存执行计划,所以直接出现在 PL/pgSQL 函数中的 SQL 命令在每次执行时必须引用相同的表和列;也就是说,您不能将参数用作 SQL 命令中的表或列的名称。为了绕过这个限制,你可以使用 PL/pgSQL EXECUTE语句构造动态命令——代价是在每次执行时构造一个新的执行计划。

阅读手册中的参考章节。它相当全面。

代码示例

您添加的代码示例不需要动态 SQL,并且单个语句会更快:

INSERT INTO dimtime  -- you may want list columns
SELECT *             -- here as well
FROM   public.src_dimtime
WHERE  NOT EXISTS (SELECT 1 FROM dimtime);
于 2014-09-04T20:00:44.147 回答