Postgresql 的性能存储在 procedures/functions 多租户环境中,该环境有一个数据库和许多模式(每个租户一个)

Performance of Postgresql stored procedures/functions in a multi-tenant environment that has one db with many schemata (one for each tenant)

我是 Postgresql 的新手,我想弄清楚在多模式环境中使用时有关存储过程(我认为实际上在 pgsql 中称为函数)的一些细节。

我想到的应用程序涉及多租户数据库设计,其中每个租户使用一个模式,并且具有相同 table 结构和名称的所有模式都是同一数据库的一部分。据我所知,一般来说,存储的 procedures/functions 是预编译的,因此速度更快,所以我想通过从应用程序发送所需的参数来使用它们对每个模式的 table 执行操作服务器而不是发送 SQL 命令列表。此外,我希望有一组函数在每个模式的 table 上实现所有 SELECT(包括 JOIN 类型)、INSERT、UPDATE 等操作。这将允许轻松地在每个函数中执行更改并避免 SQL 代码复制和冗余。正如我发现的那样,可以在模式 s0 中创建一组函数,然后创建使用这些函数的 s1, s2, ... 模式(具有相同的 tables)。

例如,我可以创建一个名为 s0 的模板模式(与所有其他模式相同)并创建属于该模式并包含对的操作的 SQL 或 pl/pgSQL 函数模式的 tables。在此函数中,table 名称不带架构前缀,即 first_table 而不是 s0.first_table

示例函数可以是:

CREATE FUNCTION sel() RETURNS BIGINT 
AS 'SELECT count(a) from first_table;' 
LANGUAGE SQL;

经我测试,这个功能很好用。如果我通过输入移动到架构 s1

set search_path to s1;

然后再次调用该函数,该函数作用于s1 schema 的同名table first_table。 该函数还可以包含参数 path 以便使用架构名称调用它,以及更改 search_ path 的命令,类似于:

CREATE OR REPLACE FUNCTION doboth(path TEXT, is_local BOOLEAN DEFAULT false) RETURNS BIGINT AS $$
    SELECT set_config('search_path', regexp_replace(path, '[^\w ,]', '', 'g'), is_local);
    SELECT count(a) from first_table;
 $$ LANGUAGE sql;

PostgreSQL: how do I set the search_path from inside a function?

中建议的解决方案所示

然而,当我尝试这个并为模式调用函数时,我注意到函数的第二个 SELECT 在第一个之前执行,这导致执行第二个 SELECT在错误的架构上!这真是出乎意料。有人知道对这种行为的解释吗?

为了绕过这个问题,我创建了一个 plpgsql 函数来做同样的事情并且它没有任何执行顺序问题:

CREATE OR REPLACE FUNCTION doboth(path TEXT, is_local BOOLEAN DEFAULT false) RETURNS BIGINT AS $$ 
DECLARE result BIGINT;
BEGIN
PERFORM set_config('search_path', regexp_replace(path, '[^\w ,]', '', 'g'), is_local);
SELECT count(a) from first_table INTO result;
RETURN result;
END
 $$ LANGUAGE plpgsql;

所以,现在有一些关于性能的问题:

1) 除了 a) 选择要操作的模式以及在一个事务中对模式进行指定操作,这是我的多租户实施所必需的,以及 b) 将 SQL 命令和避免在应用程序服务器和数据库服务器之间进行一些额外的数据交换是有益的,Postgresql 函数是否比在单独的 SQL 命令中执行相同的代码有任何性能优势?

2) 在所描述的具有多个模式和一个数据库的多租户场景中, 定义一次并调用与其定义的模式相同的模式的函数是否会失去任何性能优势(如果有)?

3) SQL 函数和包含相同操作的 PL/pgSQL 函数在性能上有什么不同吗?

首先,我真的不相信函数中的行执行顺序会有任何问题。如果您有任何问题,那是您的代码无法正常工作,而不是 Postgres。

其次,set search_path to s1, s0; 很好地实现了多租户行为。通常不需要在程序内部切换任何东西。

第三,除了最小化数据库和应用程序之间的数据流之外,使用存储过程没有任何性能优势。如果您考虑像 SELECT count(*) FROM mytable WHERE somecolumn = 这样的查询,那么在您知道 .

的值之前绝对没有什么可以优化的

最后,不,SQL 和 PL/pgSQL 中的函数之间没有显着差异。大部分时间还是花在通读表格上,所以要集中精力完善它。

希望澄清情况。此外,您可能还需要考虑存储过程的安全优势。只是一个提示。

在我回答你的问题之前,先对你的 SQL 函数做个评论。

它不会因为语句执行顺序错误而失败,而是因为两个查询都在第一个查询执行之前解析。您收到的错误消息有点像

ERROR:  relation "first_table" does not exist
[...]
CONTEXT:  SQL function "doboth" during startup

注意“启动时”。

回答

  1. 您可能会体验到轻微的性能提升,特别是如果 SQL 语句很复杂,因为 PL/pgSQL 函数中 SQL 语句的计划被缓存在数据库会话期间或直到它们失效。

    如果查询的计划被PL/pgSQL函数缓存,但是调用该函数的SQL语句每次都必须计划,从性能角度来看你实际上可能更差因为执行函数的开销。

  2. 每当您使用不同的模式名称调用该函数时,查询计划将失效并且必须重新创建。因此,如果您为每次调用更改架构名称,您将一无所获。

  3. SQL 函数不缓存查询计划,因此它们的性能并不比普通 SQL 查询好。

但是请注意,在函数中缓存简单的 SQL 语句的好处并不大。

仅当它使您的生活更简单时,才使用仅充当 SQL 语句容器的函数,否则使用普通的 SQL.

设计时不要只注重性能,还要注重好的架构和简单的设计。

如果相同的语句不断重复,使用准备好的语句可能会比使用函数获得更高的性能。