前言
最近在开发个人任务清单项目,建表的时候踩了一个经典老坑: MySQL5.7 的 DATE 类型不支持直接写默认当前日期 。 任务日期字段是 NOT NULL,新增任务时如果前端不传日期,直接 SQL 报错。 MySQL8.0 已经支持 DEFAULT (CURRENT_DATE),但很多老项目还停留在 5.7,这个时候触发器就是很好的兜底方案。
很多同学对触发器的印象比较模糊,只听过名字,不知道什么时候该用、什么时候要避开。今天拿这张任务清单表完整举例,把触发器讲透。
一、什么是数据库触发器?
触发器,可以理解成绑定在数据表上,数据库原生自带的事件监听器 。 只要这张表执行 INSERT / UPDATE / DELETE,数据库会自动执行预先写好的 SQL 逻辑,不需要后端业务代码手动调用。
两个核心执行时机
BEFORE:操作执行之前 触发,可以修改待入库的行数据(NEW),适合字段填充、数据校验AFTER:数据入库完成之后触发,不能修改本行记录,多用于日志、跨表数据同步
关键字 NEW / OLD(重中之重)
NEW:即将插入 / 更新的新行。INSERT 只有 NEW;UPDATE 同时有 NEW、OLDOLD:修改前、即将删除的旧行。DELETE 只有 OLD
口诀:新增只有 NEW,修改新旧都有,删除只有 OLD。
二、现象 → 业务实战举例
现象 MySQL5.7 限制:DATE 字段不能直接设置默认当前日期。任务日期 NOT NULL,插入不传值直接报错。
业务场景 表:daily_task_list,每日清单任务表 需求:
- 用户新建任务,可以手动选择任务日期
task_date; - 用户不选日期时,自动填充为今日;
- 用户手动指定日期,则保留传入的值,触发器不覆盖。
解决方案:BEFORE INSERT触发器,插入前判断,如果 task_date 为 NULL,自动赋值CURDATE()。
完整触发器 SQL(直接复制可用)
sql
DELIMITER //
CREATE TRIGGER trg_daily_task_list_before_insert
BEFORE INSERT ON daily_task_list
FOR EACH ROW
BEGIN
IF NEW.task_date IS NULL THEN
SET NEW.task_date = CURDATE();
END IF;
END //
DELIMITER ;
逐行拆解代码
DELIMITER //临时修改 SQL 结束符。触发器体内包含;,如果不改结束符,MySQL 会判定触发器代码中途结束。执行完成后DELIMITER ;恢复默认分号。
Navicat 图形界面创建触发器,不需要写 DELIMITER。
BEFORE INSERT ON daily_task_list绑定触发器,监听daily_task_list的新增操作,写入数据库之前执行。FOR EACH ROW行级触发器,MySQL 仅支持行触发器:每新增一行,触发器执行一次。固定写法。IF NEW.task_date IS NULL判断本次待插入记录里,任务日期是否为空。NEW.task_date代表本次准备入库的 task_date 字段。SET NEW.task_date = CURDATE();修改待入库行,自动设置为当日日期。
三、本地测试验证(直接复制执行)
先建简易测试表,新手推荐先在测试库跑一遍,不要直接上生产表
sql
CREATE TABLE test_task(
id INT AUTO_INCREMENT PRIMARY KEY,
task_date DATE NOT NULL,
content VARCHAR(100)
);
创建触发器:
sql
DELIMITER //
CREATE TRIGGER trg_test_task_before_insert
BEFORE INSERT ON test_task
FOR EACH ROW
BEGIN
IF NEW.task_date IS NULL THEN
SET NEW.task_date = CURDATE();
END IF;
END //
DELIMITER ;
测试两条插入语句:
sql
-- 不传task_date,触发器自动填充今日日期
INSERT INTO test_task(content) VALUES('测试自动填充日期任务');
-- 手动指定日期,触发器不覆盖,保留传入的值
INSERT INTO test_task(task_date,content) VALUES('2026-12-01','手动指定日期任务');
SELECT * FROM test_task;
执行查询,可以看到:第一条自动填充今天,第二条保留我们写的日期。
四、触发器的优缺点(后端开发重点)
✅ 优点
- 规则统一:数据库层约束。多端、多个项目读写同一张表,不用在每一处业务代码重复写填充逻辑。
- 简化业务代码:简单默认值、基础校验,后端代码不用额外处理。
- 自动触发:底层监听表变更,无需业务代码调用。
⚠️ 缺点(踩坑重点,面试常问)
- 逻辑隐藏:触发器写在数据库里,业务代码看不见。排查 BUG 非常容易遗忘这段逻辑,属于隐形坑。
- 同事务执行:触发器和原始 INSERT 在同一个事务,触发器内部报错,整条插入直接回滚。
- 写入性能损耗:每次增删改都会额外执行触发器,复杂逻辑会拖慢写入。
- 调试困难:无法断点调试,问题排查只能靠日志。
五、适用场景 & 避坑建议
✅ 适合:简单字段默认填充、基础数据合法性校验、操作日志归档、轻量跨表同步 ❌ 不适合:复杂业务计算、大量查询、复杂业务流程。这类逻辑尽量放在业务代码(PHP/Java),便于维护排查。
场景小结:本次触发器只是为了兼容 MySQL5.7 环境,实现 date 默认当日日期。项目环境如果能升级 MySQL8.0,优先直接设置字段默认值,尽量少用触发器。
六、触发器常用管理 SQL
sql
-- 查看当前库全部触发器
SHOW TRIGGERS;
-- 查看单个触发器完整定义
SHOW CREATE TRIGGER trg_daily_task_list_before_insert;
-- 删除触发器,修改触发器只能先删再重建
DROP TRIGGER IF EXISTS trg_daily_task_list_before_insert;
结尾
触发器不是万能神器,属于数据库的 "隐藏能力",能不用就尽量不用。但遇到老版本 MySQL 这种场景,它又是很优雅的解决方案。 你项目里有用触发器踩过什么坑?欢迎评论区交流。