SQL触发器无法仅回滚单行,因其运行在外部DML语句的事务上下文中,整个语句为原子操作单元;报错则全语句回滚,成功则全部提交,MySQL的SIGNAL或SQL Server的RAISERROR+ROLLBACK均作用于整个事务。

SQL触发器里根本不能“仅回滚单行”
触发器没有单行事务的概念。它运行在外部 DML 语句的事务上下文中,而 INSERT/UPDATE/DELETE 语句本身要么影响多行(如 INSERT INTO t SELECT ...),要么就是单行(如 INSERT INTO t VALUES (...))。但无论多少行,整个语句就是一个原子操作单元——触发器报错,整个语句失败;触发器成功,整个语句提交。所谓“只回滚某一行”,在标准 SQL 触发器机制中不存在。
MySQL BEFORE 触发器用 SIGNAL 看似“单行拦截”,实则是语句级终止
很多人误以为 SIGNAL SQLSTATE '45000' 能让触发器只干掉当前正在插入的那一行,其实不是:它会让整条 INSERT 语句失败并回滚所有已尝试插入的行(哪怕只有一行出错,整批也全废)。
-
INSERT INTO orders VALUES (1,'2026-11-02',-50)→ 触发器检查NEW.amount → <code>SIGNAL→ 整条语句回滚,0 行写入 -
INSERT INTO orders SELECT id,dt,amt FROM staging WHERE id IN (101,102,103)→ 其中第 102 行触发SIGNAL→ 整个SELECT ... INSERT失败,0 行写入 - 你无法让 101 和 103 成功、仅丢弃 102 —— MySQL 不支持部分插入(partial insert)语义
真想实现“错一行、过其余”,得绕开触发器
触发器是声明式校验,不是流程控制器。要达成“单行失败不影响其他行”,必须把控制权拿到应用层或存储过程里,逐行处理:
- 禁用触发器(
RENAME TABLE orders TO orders_off+ 创建同名视图/空表占位),改用带错误捕获的存储过程 - 在存储过程中对每一行调用
DECLARE CONTINUE HANDLER FOR SQLEXCEPTION,记录错误到日志表,然后CONTINUE,不中断循环 - 用临时表暂存待插入数据,先
INSERT ... SELECT ... ON DUPLICATE KEY UPDATE或INSERT IGNORE处理冲突,再单独查出失败行做补偿 - 应用层用事务分批次(比如每 10 行一个事务),失败只回滚当批,不影响前面已提交的批次
最容易被忽略的一点:autocommit=true 让所有回滚都失效
哪怕你写对了 ROLLBACK TRANSACTION 或 SIGNAL,只要连接处于自动提交模式,事务根本没真正开启。MySQL 默认 autocommit=1,SQL Server 驱动(如 go-mssqldb)也常默认 autocommit=true。必须显式设置 autocommit=false,否则任何 ROLLBACK 都只是静默忽略。

















