触发器不能手动调用,必须由表上INSERT/UPDATE/DELETE等事件自动触发,且强绑定于特定表;存储过程需显式执行(如EXEC),可跨库跨对象调用,独立于表结构。

触发器不能手动调用,存储过程必须显式执行——这是最根本、最不能绕开的区别。
触发器只能绑定表,存储过程可以跨库、跨对象调用
触发器创建时必须指定 ON 表名,且只能响应该表的 INSERT、UPDATE、DELETE(或 DDL 事件,取决于数据库)。它和表是强耦合的,删了表,触发器自动失效;改表名,触发器不会自动迁移。而存储过程是独立数据库对象,调用时只需 EXEC 过程名 或 CALL 过程名,参数可传入任意值,甚至能跨数据库查询(如 SELECT * FROM other_db.dbo.table)。
常见错误现象:CREATE TRIGGER tr_log ON non_existent_table 直接报错;而 EXEC sp_missing_proc 只在执行时才提示“找不到对象”。
- 触发器无法在视图、临时表(SQL Server 中)或内存表上创建(部分数据库支持,但行为受限)
- 存储过程可嵌套调用其他存储过程,但不能在触发器里直接
EXEC同一事务上下文中的另一个触发器(易引发递归或死锁) - MySQL 中触发器不能包含
CALL语句调用存储过程(5.7 及以前版本限制,8.0+ 允许但需谨慎)
触发器隐式参与事务,存储过程需自行控制事务边界
当你执行一条 UPDATE,它触发的 AFTER UPDATE 触发器里的所有 SQL 自动属于同一事务:任一语句失败,整个 UPDATE + 触发器逻辑全部回滚。而存储过程默认不开启事务,除非你显式写 BEGIN TRANSACTION 和 COMMIT/ROLLBACK。
这意味着:INSERT INTO orders 失败时,哪怕触发器里已成功写入 audit_log 表,也会一起被撤销;但如果你把同样逻辑写成存储过程,忘了加事务,就可能出现订单入库成功、日志却漏写的情况。
- SQL Server 中
INSTEAD OF触发器会替代原操作,原语句不执行,只执行触发器内容——此时事务完全由触发器代码控制 - PostgreSQL 的触发器函数返回
NULL可阻止原INSERT执行,这相当于在事务内做“条件拦截”,但依然无法脱离主语句事务 - 不要在触发器里尝试
COMMIT或SAVE TRANSACTION(SQL Server),会直接报错Transaction count after EXECUTE indicates a mismatch
触发器有 inserted/deleted 伪表,存储过程靠参数传数据
SQL Server 和 PostgreSQL 的行级触发器能直接访问 inserted 和 deleted 这两个内存临时表(MySQL 用 NEW/OLD)。它们反映本次 DML 操作影响的行——比如 UPDATE 同时有旧值(deleted)和新值(inserted),可用来比对字段变更、记录差异日志。
存储过程没有这种机制。你要获取变更前后的数据,得靠传参(如传入旧 @old_status 和新 @new_status),或自己查表(SELECT ... WHERE id = @id),多一次 I/O,也容易因并发导致读到脏数据。
-
inserted/deleted是只读的,不能UPDATE inserted,也不能INSERT INTO inserted - 批量操作(如
UPDATE TOP(1000) ...)会让inserted包含多行,触发器必须用集合操作处理,不能假设只有一行 - Oracle 中对应的是
:NEW和:OLD记录变量,语法不同但语义一致
真正难的不是语法区别,而是决策点:该用触发器还是存储过程,本质是在问“这个逻辑是否必须和某次数据变更原子性绑定”。如果答案是肯定的——比如扣库存必须和下单严格同步、删主记录必须连带清空子表——那就用触发器;如果只是封装常用查询或批处理任务,或者需要灵活控制执行时机和参数组合,就该选存储过程。

















