You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PostgreSQL如何实现invoiceitems表sortorder自动重排且避免触发器无限递归

方案1:使用内置pg_trigger_depth()函数限制递归(最简便)

pg_trigger_depth()返回当前触发器的嵌套层数,首次触发时值为1,触发器内部的UPDATE操作再次触发本触发器时,层数会变为2,此时直接退出即可阻断递归,完全兼容PostgreSQL 11及以上版本。
触发器函数示例:

CREATE OR REPLACE FUNCTION reorder_invoice_items()
RETURNS TRIGGER AS $$
DECLARE
    target_invoice INTEGER;
BEGIN
    -- 嵌套层数大于1说明是重排逻辑内部触发的UPDATE,直接退出
    IF pg_trigger_depth() > 1 THEN
        RETURN NULL;
    END IF;

    -- 获取需要重排的发票ID
    IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN
        target_invoice := NEW.idinvoice;
    END IF;
    -- 如需支持删除条目后自动重排,可放开以下注释
    -- IF TG_OP = 'DELETE' THEN
    --     target_invoice := OLD.idinvoice;
    -- END IF;

    -- 你的重排逻辑
    WITH pre AS (
        SELECT idinvoiceitem,10*ROW_NUMBER() OVER (ORDER BY sortorder,idinvoiceitem) AS rownum
        FROM invoiceitems
        WHERE idinvoice = target_invoice
        ORDER BY sortorder,idinvoiceitem
    )
    UPDATE invoiceitems di
    SET sortorder=rownum
    FROM pre p
    WHERE p.idinvoiceitem=di.idinvoiceitem;

    RETURN NULL;
END;
$$ LANGUAGE plpgsql;

创建触发器,使用AFTER行级触发器,仅在必要操作时触发减少性能损耗:

CREATE TRIGGER trigger_reorder_invoice_items
AFTER INSERT OR UPDATE OF sortorder, idinvoice
ON invoiceitems
FOR EACH ROW
EXECUTE FUNCTION reorder_invoice_items();

方案2:使用会话级变量作为信号量(适合多触发器嵌套场景)

如果业务中存在其他触发器嵌套调用,用深度判断可能出现误判,可以用事务级临时变量作为重排标记,避免递归:

CREATE OR REPLACE FUNCTION reorder_invoice_items()
RETURNS TRIGGER AS $$
DECLARE
    target_invoice INTEGER;
BEGIN
    -- 检测重排标记已存在则直接退出
    IF current_setting('biz.invoice_reorder_running', true) = 'true' THEN
        RETURN NULL;
    END IF;
    -- 标记重排中,set_config第三个参数为true表示变量仅当前事务有效
    PERFORM set_config('biz.invoice_reorder_running', 'true', true);

    IF TG_OP = 'INSERT' OR TG_OP = 'UPDATE' THEN
        target_invoice := NEW.idinvoice;
    END IF;

    -- 重排逻辑
    WITH pre AS (
        SELECT idinvoiceitem,10*ROW_NUMBER() OVER (ORDER BY sortorder,idinvoiceitem) AS rownum
        FROM invoiceitems
        WHERE idinvoice = target_invoice
        ORDER BY sortorder,idinvoiceitem
    )
    UPDATE invoiceitems di
    SET sortorder=rownum
    FROM pre p
    WHERE p.idinvoiceitem=di.idinvoiceitem;

    -- 清除标记
    PERFORM set_config('biz.invoice_reorder_running', 'false', true);
    RETURN NULL;
END;
$$ LANGUAGE plpgsql;

并发优化建议

如果存在多会话同时修改同一张发票条目的场景,可以在重排逻辑前增加行锁避免顺序冲突:

-- 锁定对应发票行,其他会话修改同发票条目时会等待当前重排完成
SELECT 1 FROM invoices WHERE idinvoice = target_invoice FOR UPDATE;

内容的提问来源于stack exchange,提问作者JC Boggio

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.22 00:15:03