postgresql - 为什么 postgres set_config 的 is_local = true 不保留整个事务的变量?

标签 postgresql variables session transactions plpgsql

我有一个函数,它使用 set_configis_local = true 来设置变量。 现在我期望在同一事务中使用带有 current_settings 的变量的 select 语句能够访问该变量,因为文档指出:

set_config(setting_name, new_value, is_local) ... set parameter and return new value

set_config sets the parameter setting_name to new_value. If is_local is true, the new value will only apply to the current transaction.

但这对我来说不起作用,我明白了

ERROR:  unrecognized configuration parameter "auth.tenant_id"
SQL state: 42704

有什么我错的地方吗?

这是函数:

CREATE OR REPLACE FUNCTION auth.authorize(IN a_user_id uuid)
    RETURNS boolean
    LANGUAGE 'plpgsql'
    SECURITY DEFINER 
AS $BODY$
declare
v_tenant_id uuid;
v_user_role auth.user_role;
begin
select tenant_id, user_role into strict v_tenant_id, v_user_role from auth.authorizations where user_id = a_user_id;
perform set_config('auth.tenant_id', v_tenant_id::text, true);
perform set_config('auth.user_role', v_user_role::text, true);
return true;
exception when no_data_found then return false;
end;
$BODY$;

这是第二个 select 语句失败的事务

begin;
select * from auth.authorize(uuid('180e1b14-21e5-4e66-a9b8-db09139d6278'));
select current_setting('auth.tenant_id') as tenant_id, current_setting('auth.user_role') as user_role;
commit;

最佳答案

set_config(is_local => true)的效果似乎仅限于您的 BEGIN … EXCEPTION 创建的隐式子事务 block 。

(跳到我对您问题的 TL;DR 解决方案的回答末尾。)

以下是当我执行类似操作但调用 set_config() 时会发生的情况BEGIN … EXCEPTION之外子事务:

create or replace function set_config_outside_of_begin_except_block(denominator int)
    returns bool
    language 'plpgsql'
as $body$
begin
    perform set_config('my.setting', 'set before subtransaction', true);

    begin
        perform 10 / denominator;
        return true;
    exception when division_by_zero then
        return false;
    end;
end;
$body$;

begin
select current_setting('my.setting') as my_setting;
select set_config_outside_of_begin_except_block(1);
rollback;
CREATE FUNCTION
BEGIN
ERROR:  unrecognized configuration parameter "my.setting"
 set_config_outside_of_begin_except_block
------------------------------------------
 t
(1 row)

        my_setting
---------------------------
 set before subtransaction
(1 row)

ROLLBACK

移动了set_config()调用BEGIN … EXCEPTION之前 block ,my.setting 的新设置已在我们的函数调用之外持续存在。

而且,正如您接下来将看到的,上面的 BEGIN … EXCEPTION 中是否发生异常也没关系。 block ,这是有道理的,因为 set_config()在输入区 block /子交易之前调用:

begin
select current_setting('my.setting') as my_setting;
rollback;
BEGIN
 set_config_outside_of_begin_except_block
------------------------------------------
 f
(1 row)
        my_setting

---------------------------
 set before subtransaction
(1 row)

ROLLBACK

现在,我将稍微修改代码以更接近您的示例,并更接近重现您描述的行为:

create or replace function set_config_in_begin_except_block(denominator int)
    returns bool
    language 'plpgsql'
as $body$
begin
    perform set_config('my.setting', 'set in subtransaction', true);
    perform 10 / denominator;
    return true;
exception when division_by_zero then
    return false;
end;
$body$;

begin;
select set_config_in_begin_except_block(0);
select current_setting('my.setting') as my_setting;
rollback;
CREATE FUNCTION
BEGIN
 set_config_in_begin_except_block
----------------------------------
 f
(1 row)

 my_setting
------------

(1 row)

ROLLBACK
begin
select set_config_in_begin_except_block(1);
select current_setting('my.setting') as my_setting;
rollback;
BEGIN
 set_config_in_begin_except_block
----------------------------------
 t
(1 row)

      my_setting
-----------------------
 set in subtransaction
(1 row)

ROLLBACK

请注意,现在,

  1. BEGIN … EXCEPTION阻止确实会导致 my.setting 的更改迷路,
  2. 但前提是确实引发了异常 ( division_by_zero ),
  3. 尽管division_by_zero由语句 after set_config() 触发.

所以,它有点接近您描述的行为,但又不完全一样。您的设置没有持续存在于 BEGIN … EXCEPTION 之外阻止无论是否 no_data_found异常是否命中。

作为一个小绕道:我偶然发现了你的问题,因为我感兴趣的是我是否可以将这些设置视为与(子)事务堆栈一起展开的堆栈。事实证明我可以:

create or replace function set_config_stacked(denominator int)
    returns bool
    language 'plpgsql'
as $body$
begin
    perform set_config('my.setting', 'set before substraction', true);

    begin
        perform set_config('my.setting', 'set within subtransaction before division', true);
        perform 10 / denominator;
        return true;
    exception when division_by_zero then
        return false;
    end;
end;
$body$;

begin;
select set_config_stacked(1);
select current_setting('my.setting') as my_setting;
rollback;
CREATE FUNCTION
BEGIN
 set_config_stacked
--------------------
 t
(1 row)

                my_setting
-------------------------------------------
 set within subtransaction before division
(1 row)

ROLLBACK
begin;
select set_config_stacked(0);
select current_setting('my.setting') as my_setting;
rollback;
BEGIN
 set_config_stacked
--------------------
 f
(1 row)

       my_setting
-------------------------
 set before substraction
(1 row)

ROLLBACK

所以my.settingBEGIN … EXCEPTION 的子事务时,实际上会恢复到之前的设置。区 block 关闭。

我需要最后一项更改来重现您的函数的行为:

create table tab (id uuid primary key, stuff text);
insert into tab (id, stuff) values (uuid('180e1b14-21e5-4e66-a9b8-db09139d6278'), 'some stuff');

create or replace function set_config_stacked(id$ uuid)
    returns bool
    language 'plpgsql'
as $body$
declare
    rec tab%rowtype;
begin
    perform set_config('my.setting', 'set before substraction', true);

    begin
        perform set_config('my.setting', 'set within subtransaction before SELECT', true);
        select * into strict rec from tab where id = id$;
        perform set_config('my.setting', 'set within subtransaction after SELECT', true);
        return true;
    exception when no_data_found then
        return false;
    end;
end;
$body$;

begin;
select set_config_stacked(uuid('180e1b14-21e5-4e66-a9b8-db09139d6278'));  -- exist
select current_setting('my.setting') as my_setting;
rollback;
CREATE TABLE
INSERT 1 0
CREATE FUNCTION
 set_config_stacked
--------------------
 t
(1 row)

               my_setting
----------------------------------------
 set within subtransaction after SELECT
(1 row)

ROLLBACK
begin;
select set_config_stacked(uuid('cc7ad0c3-7e3a-49a0-b7d8-7b4093ae0028'));  -- doesn't exist
select current_setting('my.setting') as my_setting;
rollback;
BEGIN
 set_config_stacked
--------------------
 f
(1 row)

       my_setting
-------------------------
 set before substraction
(1 row)

ROLLBACK

正如您所看到的,您所描述的行为的一个方面我仍然无法重现。此版本set_config_stacked()围绕 no_data_found 运行异常的行为与基于 division_by_zero 的先前版本相同。异常。

但是,您的示例表明您的设置也不会在 auth.authorize() 之外持续存在。当没有遇到异常时。这让我很困惑,而且我无法重现,至少在 Postgres 14 上是这样。

无论如何,您的问题可以通过将调用移至 set_config() 来解决。低于BEGIN … EXCEPTION … END堵塞。您仍然希望在该 block 内设置变量,但是执行这些调用和 return true从新创建的外部BEGIN … END block (没有 EXCEPTION )。

关于postgresql - 为什么 postgres set_config 的 is_local = true 不保留整个事务的变量?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71242103/

相关文章:

database - 如何在 Windows 中将用户添加到 PostgreSQL?

sql - 结合两个查询 - 如何?

javascript - 一直在扫描 NaN 并迷路

bash - 如何从 Bash 中的字符串中删除换行符

function - PostgreSQL UPSERT 触发器返回 id

sql - Postgresql 从 JSON 列中选择

javascript - 为什么每个人都将端口存储为快速变量?

session - 如何在多页面 Angular 应用程序中保持 session ?

session - 如何关闭 tomcat5 'session expire' 日志消息?

laravel - 使用 vue 和 laravel API 管理用户 session 数据的良好实践