同学们,现在你们已经掌握了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里别乱搞没必要的操作。
错误使用OLD和NEW
这俩变量是你写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;
怎么避免:
一定要搞清楚什么时候能用OLD和NEW:
OLD只有UPDATE和DELETE时能用。NEW只有INSERT和UPDATE时能用。
一个操作上挂多个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;
实用建议
trigger越简单越好。 如果你觉得trigger太大太复杂,最好拆成几个函数,或者重新想想逻辑。
一定要先在小数据量上测试trigger。 别一上来就挂到重要表上,先在测试环境里试试。
写好trigger的文档。 过几个月你或者同事可能都忘了当初为啥要写这个trigger。文档写清楚,省得以后头疼。
能在应用层解决的事尽量别用trigger。 trigger适合自动化、需要立刻执行的小任务,但复杂业务逻辑还是放应用层靠谱。
注意性能。 经常监控trigger对数据库性能的影响,尤其是数据量或压力变大时。
有了这些建议和你今天学到的知识,你不仅能写trigger,还能写出靠谱、高效、不坑人(也不坑自己)的trigger!
GO TO FULL VERSION