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

PLSQL触发器触发ORA-00060死锁问题排查与修复咨询

问题描述

实现PLSQL触发器时遇到ORA-00060:检测到死锁等待资源错误。此前为解决触发器中的变异表问题使用了pragma autonomous_transaction,但当前因操作同一张PAYMENT表引发死锁,需调整时序或其他方式解决。

相关代码

1) 金额校验存储过程

CREATE OR REPLACE PROCEDURE CHECK_AMOUNT(f_id INTEGER, p_amt IN OUT NUMBER, r_days INTEGER)
IS
    going_rate NUMBER;
    INVALID_AMT EXCEPTION;
BEGIN
    SELECT RENTAL_RATE INTO going_rate
    FROM FILM
    WHERE FILM_ID = f_id;

    IF p_amt > (going_rate * r_days) THEN
        RAISE INVALID_AMT;
    ELSIF p_amt < 0 THEN
        p_amt := 0;
    END IF;

    EXCEPTION
        WHEN INVALID_AMT THEN
            DBMS_OUTPUT.PUT_LINE('Invalid amount for film (id) ' || f_id || ', maximum is ' || going_rate * r_days || '.');
END;

存储过程测试代码

DECLARE
pmt NUMBER;
BEGIN
    pmt := 20; -- triggers error
    --pmt := -20; -- also works as sepcified
    CHECK_AMOUNT(200, pmt, 3);
    DBMS_OUTPUT.put_line (pmt);
END;

2) 金额校验触发器

CREATE OR REPLACE TRIGGER CHECK_AMOUNT_TRG
BEFORE INSERT OR UPDATE
ON PAYMENT FOR EACH ROW
DECLARE
    pragma autonomous_transaction;
    pmt NUMBER;
    f_id INTEGER;
BEGIN
    SELECT FILM_ID, AMOUNT INTO f_id, pmt
        FROM INVENTORY
        JOIN RENTAL
        ON INVENTORY.INVENTORY_ID = RENTAL.INVENTORY_ID
        JOIN PAYMENT
        ON RENTAL.RENTAL_ID = PAYMENT.RENTAL_ID
        WHERE PAYMENT_ID = :NEW.PAYMENT_ID;
    CHECK_AMOUNT(f_id, pmt, 3);
END;

触发器测试更新语句

UPDATE PAYMENT
SET AMOUNT = 25
WHERE PAYMENT_ID = 6500;

UPDATE PAYMENT
SET AMOUNT = 1
WHERE PAYMENT_ID = 3000;

UPDATE PAYMENT
SET AMOUNT = -10
WHERE PAYMENT_ID = 1200;

ROLLBACK;

3) 操作日志触发器及测试

ALTER TABLE PAYMENT ADD user_modified VARCHAR(50);

CREATE OR REPLACE TRIGGER LOG_PAYMENT
AFTER INSERT OR UPDATE
ON PAYMENT FOR EACH ROW
DECLARE
    pragma autonomous_transaction;
BEGIN
    UPDATE PAYMENT
        SET PAYMENT.user_modified = USER, PAYMENT.LAST_UPDATE = SYSTIMESTAMP
        WHERE PAYMENT_ID = :NEW.PAYMENT_ID;
END;

日志触发器测试更新语句

UPDATE PAYMENT
SET AMOUNT = 25
WHERE PAYMENT_ID = 6500;

UPDATE PAYMENT
SET AMOUNT = 1
WHERE PAYMENT_ID = 3000;

UPDATE PAYMENT
SET AMOUNT = -10
WHERE PAYMENT_ID = 1200;

ROLLBACK;
PAYMENT表结构

PAYMENT_ID, CUSTOMER_ID, STAFF_ID, RENTAL_ID, AMOUNT, PAYMENT_DATE, LAST_UPDATE, USER_MODIFIED(USER_MODIFIED为新增列)

解决方案

死锁根源是自治事务与主事务操作同一张表的同一行,互相等待对方释放锁,且两个触发器的自治事务都是不必要的,调整方案如下:

1) 重构CHECK_AMOUNT_TRG触发器

原触发器错误地在自治事务中查询PAYMENT表,且完全可以通过:NEW直接获取RENTAL_ID,无需关联PAYMENT表,同时去掉自治事务:

CREATE OR REPLACE TRIGGER CHECK_AMOUNT_TRG
BEFORE INSERT OR UPDATE
ON PAYMENT FOR EACH ROW
DECLARE
    f_id INTEGER;
    v_amt NUMBER := :NEW.AMOUNT;
BEGIN
    -- 通过RENTAL_ID关联获取FILM_ID,无需查询PAYMENT表
    SELECT i.FILM_ID INTO f_id
    FROM INVENTORY i
    JOIN RENTAL r ON i.INVENTORY_ID = r.INVENTORY_ID
    WHERE r.RENTAL_ID = :NEW.RENTAL_ID;
    
    CHECK_AMOUNT(f_id, v_amt, 3);
    -- 将校验后的金额赋值回:NEW
    :NEW.AMOUNT := v_amt;
END;
  • 去掉pragma autonomous_transaction,避免跨事务锁等待
  • 直接使用:NEW.RENTAL_ID关联查询FILM_ID,不再访问PAYMENT表,彻底解决变异表问题

2) 重构LOG_PAYMENT触发器

原触发器在AFTER阶段用自治事务更新同一张表,导致主事务锁与自治事务锁冲突。改为在BEFORE阶段直接设置字段值,无需额外UPDATE操作:

CREATE OR REPLACE TRIGGER LOG_PAYMENT
BEFORE INSERT OR UPDATE
ON PAYMENT FOR EACH ROW
BEGIN
    :NEW.user_modified := USER;
    :NEW.LAST_UPDATE := SYSTIMESTAMP;
END;
  • 去掉自治事务,直接在BEFORE触发器中修改:NEW字段,无需更新表,完全避免锁冲突
  • BEFORE触发器中可以直接修改:NEW的值,提交后会自动写入表,无需额外DML操作

关键说明

  • 自治事务(autonomous_transaction)是独立于主事务的事务,使用时如果操作主事务已锁定的资源,必然会引发锁等待甚至死锁,非必要不要使用
  • 变异表问题的正确解决方式不是依赖自治事务,而是通过:NEW/:OLD直接获取字段值,或使用复合触发器,避免在触发器中直接查询触发表

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 05:10:49