sql - 子事务处于事件状态时无法回滚 - 错误 2D000

标签 sql postgresql plpgsql

我编写了一个存储过程,它基本上循环遍历字段数组,并为每次迭代在数据库中执行一些操作。我想要实现的是,要么循环的所有迭代都应该发生,要么都不应该发生。

假设 fields 数组中有 5 个元素,并且循环迭代到第三个元素,然后注意到某些条件为 true 并抛出错误,我想回滚前 2 次迭代期间发生的所有更改。我使用 ROLLBACK 语句来实现相同的目的,但每次到达 ROLLBACK 语句时都会抛出以下错误:

Cannot rollback while a subtransaction is active : 2D000

令人惊讶的是,如果我注释掉 EXCEPTION WHEN OTHERS THEN block 中的 outobj := json_build_object('code',0); 语句,或者 if我完全删除了整个 block 。

我检查了PostgreSQL documentation for error codes ,但这并没有真正帮助。我的存储过程如下:


CREATE OR REPLACE PROCEDURE public.usp_add_fields(
    field_data json,
    INOUT outobj json DEFAULT NULL::json)
LANGUAGE 'plpgsql'
AS $BODY$
DECLARE 
v_user_id bigint;
farm_and_bussiness json;
_field_obj json;
_are_wells_inserted boolean;
BEGIN

-- get user id
 v_user_id = ___uf_get_user_id(json_extract_path_text(field_data,'user_email'));

IF(v_user_id IS NULL) THEN
    outobj := json_build_object('code',17);
    RETURN;
END IF;

-- Loop over entities to create farms & businesses
FOR _field_obj IN SELECT * FROM json_array_elements(json_extract_path(field_data,'fields'))
LOOP
    -- check if irrigation unit id is already linked to some other field
    IF(SELECT EXISTS(
        SELECT field_id FROM user_fields WHERE irrig_unit_id LIKE json_extract_path_text(_field_obj,'irrig_unit_id') AND deleted=FALSE
    )) THEN
        outobj := json_build_object('code',26);
        -- Rollback any changes made by previous iterations of loop
        ROLLBACK;
        RETURN;
    END IF;
    
    -- check if this field name already exists
    IF( SELECT EXISTS(
            SELECT uf.field_id FROM user_fields uf
            INNER JOIN user_farms ufa ON (ufa.farm_id=uf.user_farm_id AND ufa.deleted=FALSE)
            INNER JOIN user_businesses ub ON (ub.business_id=ufa.user_business_id AND ub.deleted=FALSE)
            INNER JOIN users u ON (ub.user_id = u.user_id AND u.deleted=FALSE)
            WHERE u.user_id = v_user_id
            AND uf.field_name LIKE json_extract_path_text(_field_obj,'field_name')
            AND uf.deleted=FALSE
        )) THEN 
        outobj := json_build_object('code', 22);
        -- Rollback any changes made by previous iterations of loop
        ROLLBACK;
        RETURN;
    END IF;

    --create/update user business and farm and return farm_id 
    CALL usp_add_user_bussiness_and_farm(
        json_build_object('user_email', json_extract_path_text(field_data,'user_email'),
                          'business_name', json_extract_path_text(_field_obj,'business_name'),
                          'farm_name', json_extract_path_text(_field_obj,'farm_name')
        ), farm_and_bussiness);

    IF(json_extract_path_text(farm_and_bussiness, 'code')::int != 1) THEN
        outobj := farm_and_bussiness;
        -- Rollback any changes made by previous iterations of loop
        ROLLBACK;
        RETURN;
    END IF;

    -- insert into users fields
    INSERT INTO user_fields (user_farm_id, irrig_unit_id, field_name, ground_water_percent, surface_water_percent)
    SELECT json_extract_path_text(farm_and_bussiness,'farm_id')::bigint,
    json_extract_path_text(_field_obj,'irrig_unit_id'),
    json_extract_path_text(_field_obj,'field_name'),
    json_extract_path_text(_field_obj,'groundWaterPercentage'):: int,
    json_extract_path_text(_field_obj,'surfaceWaterPercentage'):: int;

    -- add to user wells
    CALL usp_insert_user_wells(json_extract_path(_field_obj,'well_data'), v_user_id, _are_wells_inserted);
END LOOP;

outobj := json_build_object('code',1);
RETURN;

EXCEPTION WHEN OTHERS THEN 
    raise notice '% : %', SQLERRM, SQLSTATE;
    outobj := json_build_object('code',0);
RETURN;

END;
$BODY$;

最佳答案

如果 PL/pgSQL block 中有一个 EXCEPTION 子句,则整个 block 将在发生异常时回滚的子事务中执行。因此,您不能在这样的 block 中使用 COMMITROLLBACK

如果您确实需要ROLLBACK,请像这样重写您的代码:

DECLARE
   should_rollback boolean := FALSE;
BEGIN
   FOR ... LOOP
      BEGIN  -- inner block for exception handling
         /* do stuff */
         IF (/* condition that should cause a rollback */) THEN
            should_rollback := TRUE;
            EXIT;  -- from LOOP
         END IF;
      EXCEPTION
         WHEN OTHERS THEN
            /* handle the error */
      END;
   END LOOP;

   IF should_rollback THEN
      ROLLBACK;
      /* do whatever else is needed */
   END IF;
END;

现在回滚不会发生在带有异常处理程序的 block 中,它应该按照您想要的方式工作。

关于sql - 子事务处于事件状态时无法回滚 - 错误 2D000,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72173443/

相关文章:

mysql - 在查询中使用别名的查询构建器

postgresql - 如何小写postgresql数组?

postgresql - 游标检索在 plpgsql 函数中创建的表中已删除的行

function - 在 postgresql 中使用变量作为字段名

postgresql - 从函数调用中分配带有 UUID 字段的复杂类型

c# - Asp.Net 将 SQL 数据绑定(bind)到转发器?

mysql - JOIN 与 UNION 与 IN() - 大表和许多 WHERE 条件

php - 如何在 MySQL 中获取登录用户不是好友的所有用户?

PHP fatal error : Allowed memory size of xxx bytes exhausted (tried to allocate XX bytes)

sql - 如何在其他过程中使用存储过程中创建的查询