存储函数必须返回标量值,仅支持IN参数,禁止DML操作;触发器自动响应表的增删改事件,可更新其他表但不可递归操作本表;二者按“计算”与“监控”职责分离使用。

存储函数必须返回值,且只能用在 SELECT 等查询语句里
MySQL 的 STORED FUNCTION 本质是“计算型工具”:它接受输入参数,执行逻辑(如折扣计算、日期格式化),最终 必须返回一个标量值。你不能在函数里写 INSERT、UPDATE 或 DELETE —— 这是硬性限制,否则创建会直接报错 ERROR 1418。
- 调用方式很像内置函数:
SELECT get_discounted_price(order_id) FROM orders; - 参数只有
IN类型,不支持OUT或INOUT;也不能显式声明IN,直接写参数名即可 - 函数体内不能开启事务、不能调用存储过程、不能使用临时表(某些版本)
- 适合场景:订单金额四舍五入、用户等级映射(ID → 文字)、生成订单编号前缀等纯计算任务
触发器自动响应增删改,但不能手动调用
触发器(TRIGGER)不是函数,也不是过程,它是绑定在某张表上的“隐形守卫”。只要对这张表执行 INSERT、UPDATE 或 DELETE,且满足 BEFORE/AFTER 时间点,它就自动执行——你连 CALL 都不需要写。
-
OLD和NEW是唯一能访问变更数据的方式:NEW.quantity是插入/更新后的新值,OLD.price是删除/更新前的旧值 - 一张表同一事件+同一时间点只允许一个触发器(比如不能有两个
BEFORE INSERT),想实现多逻辑就得全塞进一个触发器里 - 常见翻车点:在
AFTER UPDATE里又去更新同一张表,会触发递归调用,导致ERROR 1442 - 典型用途:库存扣减后写日志、用户名修改时同步更新昵称字段、敏感字段变更审计
为什么不能在函数里操作表,却能在触发器里更新其他表?
这是 MySQL 的设计分层决定的:FUNCTION 定位是“表达式求值单元”,必须可重复、无副作用,才能安全用于索引生成、视图定义、WHERE 条件等上下文;而 TRIGGER 明确属于 DML 生命周期的一部分,天然允许副作用。
- 函数中执行
UPDATE t_log SET count = count + 1→ 直接报错ERROR 1418或ERROR 1305(取决于版本和 super 权限) - 触发器中做同样操作完全合法,但要注意:如果更新的是当前被触发的同一张表,
BEFORE可以改NEW值,AFTER则禁止再操作本表 - 性能影响真实存在:每个触发器都增加单条 SQL 的执行路径长度,高并发写入时可能成为瓶颈,别在触发器里加复杂循环或远程 HTTP 调用
实际开发中该选哪个?看调用时机和职责边界
判断标准很简单:你要的是“算一个数”,还是“盯一次动作”?
- 需要在
SELECT里动态计算列值 → 用FUNCTION(比如get_user_status(user_id)) - 需要确保每次删用户,历史行为记录自动归档 → 用
BEFORE DELETE触发器 - 想让订单创建时自动填充
created_by字段,但应用层可能漏传 → 用BEFORE INSERT触发器兜底 - 千万别为了“复用逻辑”把业务校验塞进函数再在触发器里调用——函数不能改数据,触发器又不能传参调用函数返回多值,二者耦合反而更难维护
最常被忽略的一点:触发器不会在批量操作(如 LOAD DATA INFILE 或某些 ORM 的 bulk insert)中默认启用,需确认 SQL_LOG_BIN 和会话级 autocommit 设置;而函数只要语法对,任何 SELECT 都能用。










