CodeGym /课程 /SQL SELF /开发trigger时常见错误分析

开发trigger时常见错误分析

SQL SELF
第 58 级 , 课程 4
可用

同学们,现在你们已经掌握了trigger的知识,知道它们的类型、工作原理,甚至已经能写出各种用途的trigger了。但编程里常常这样:知道能做什么很重要,但知道不能做什么也同样重要。今天我们就来聊聊大家在用trigger时经常踩的坑,这样你们以后能少踩点雷,省下不少调试时间,甚至可能省下几天的生命!

trigger递归:trigger自己调用自己

这绝对是新手最容易犯的错。想象一下,你写了个trigger,更新表里某个字段,比如last_modified。但只要这个字段一变,update操作又会触发同一个trigger。这样就死循环了,最后你的服务器直接栈溢出挂掉。

例子:

CREATE OR REPLACE FUNCTION update_last_modified()
RETURNS TRIGGER AS $$
BEGIN
    -- 更新last_modified字段
    UPDATE my_table
    SET last_modified = NOW()
    WHERE id = NEW.id;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER after_update
AFTER UPDATE ON my_table
FOR EACH ROW
EXECUTE FUNCTION update_last_modified();

这里哪里出问题了?UPDATE操作在函数里又触发了同一个trigger,死循环就这样来了。

怎么避免:

OLD变量,先比较下值再决定要不要改:

CREATE OR REPLACE FUNCTION update_last_modified_safe()
RETURNS TRIGGER AS $$
BEGIN
    -- 检查值是否真的变了
    IF NEW.last_modified IS DISTINCT FROM OLD.last_modified THEN
        NEW.last_modified = NOW();
    END IF;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

一定要保证trigger里别乱搞没必要的操作。

错误使用OLDNEW

这俩变量是你写trigger时的好基友,但新手用不好就会头大。OLD保存的是改动前的数据,NEW是改动后的数据。

常见错误就是搞混了,或者在不该用的地方用了。比如你写BEFORE INSERT的trigger时,OLD根本不存在——因为这行数据还没插进去呢。

错误例子:

-- 这会报错,因为插入时没有OLD
CREATE OR REPLACE FUNCTION log_inserts()
RETURNS TRIGGER AS $$
BEGIN
    INSERT INTO audit_log (old_data, new_data)
    VALUES (OLD.my_column, NEW.my_column);
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

怎么避免:

一定要搞清楚什么时候能用OLDNEW

  • OLD只有UPDATEDELETE时能用。
  • NEW只有INSERTUPDATE时能用。

一个操作上挂多个trigger

PostgreSQL允许你在同一个表、同一个操作上挂好几个trigger。看起来很灵活,其实很容易乱套,trigger之间互相打架,或者都改同一份数据。

例子:

-- trigger 1
CREATE OR REPLACE FUNCTION trigger_one()
RETURNS TRIGGER AS $$
BEGIN
    -- trigger 1的逻辑
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- trigger 2
CREATE OR REPLACE FUNCTION trigger_two()
RETURNS TRIGGER AS $$
BEGIN
    -- trigger 2的逻辑
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

-- 创建两个trigger
CREATE TRIGGER trigger_one AFTER INSERT ON my_table EXECUTE FUNCTION trigger_one();
CREATE TRIGGER trigger_two AFTER INSERT ON my_table EXECUTE FUNCTION trigger_two();

这俩trigger都会在my_table插入时触发。如果逻辑没同步好,结果就很难预料了。

怎么避免:

  • 提前规划好trigger的架构。
  • 如果逻辑差不多,合成一个trigger就行了。

性能问题

trigger会给每次相关操作加点“负担”。如果你在大表或者高频操作上用trigger,性能可能会掉得很惨。

错误例子:

CREATE OR REPLACE FUNCTION heavy_trigger_function()
RETURNS TRIGGER AS $$
BEGIN
    -- 每次更新都跑个重操作
    PERFORM some_heavy_query();
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

CREATE TRIGGER performance_killer AFTER UPDATE ON huge_table EXECUTE FUNCTION heavy_trigger_function();

怎么避免:

  • trigger里逻辑越简单越好。真要做重活,考虑放到后台任务里。
  • WHEN条件限制trigger的触发:
CREATE TRIGGER optimized_trigger
AFTER UPDATE ON my_table
WHEN (OLD.column_name IS DISTINCT FROM NEW.column_name)
EXECUTE FUNCTION light_function();

trigger和事务

trigger是在你SQL语句的事务里执行的。如果trigger里报错,整个事务都会回滚。有时候这挺好,但如果没想好怎么处理错误,可能会出大问题。

错误例子:

CREATE OR REPLACE FUNCTION error_prone_trigger()
RETURNS TRIGGER AS $$
BEGIN
    -- 故意抛个错
    RAISE EXCEPTION '出错啦!';
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

这个trigger一触发,你主SQL的事务就全回滚了。

怎么避免:

在trigger里加错误处理,尽量别影响主事务:

CREATE OR REPLACE FUNCTION safe_trigger()
RETURNS TRIGGER AS $$
BEGIN
    BEGIN
        -- 可能出错的代码
        INSERT INTO another_table VALUES (NEW.data);
    EXCEPTION
        WHEN OTHERS THEN
            RAISE NOTICE '出错了,但我们优雅地处理了。';
    END;
    RETURN NEW;
END;
$$ LANGUAGE plpgsql;

实用建议

  1. trigger越简单越好。 如果你觉得trigger太大太复杂,最好拆成几个函数,或者重新想想逻辑。

  2. 一定要先在小数据量上测试trigger。 别一上来就挂到重要表上,先在测试环境里试试。

  3. 写好trigger的文档。 过几个月你或者同事可能都忘了当初为啥要写这个trigger。文档写清楚,省得以后头疼。

  4. 能在应用层解决的事尽量别用trigger。 trigger适合自动化、需要立刻执行的小任务,但复杂业务逻辑还是放应用层靠谱。

  5. 注意性能。 经常监控trigger对数据库性能的影响,尤其是数据量或压力变大时。

有了这些建议和你今天学到的知识,你不仅能写trigger,还能写出靠谱、高效、不坑人(也不坑自己)的trigger!

1
调查/小测验
行级和表级触发器第 58 级,课程 4
不可用
行级和表级触发器
行级和表级触发器
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION