编程 MySQL 9.7.0 级联删除会触发子表触发器了,默认关,而且只按会话算

2026-11-07 21:01:19

MySQL 9.7.0 级联删除会触发子表触发器了,默认关,而且只按会话算

MySQL 9.7.0(2026-04-21)的 Release Notes 里加了一条行为开关:

Foreign key CASCADE and SET NULL actions performed by SQL-layer foreign key handling can now activate BEFORE and AFTER triggers on affected child rows. Enable this behavior for a session by setting the new enable_cascade_triggers system variable to ON. The default is OFF.

Cascade-induced triggers execute as part of the initiating statement. Trigger errors cause the statement and its cascaded changes to be rolled back. Row-based replication records the changes made by the triggers without reexecuting them on replicas; statement-based replication uses the source's enable_cascade_triggers setting. (WL #17024)

旧行为:级联在存储引擎层完成,触发器根本没参与

MySQL 一直以来的做法是,外键的 ON DELETE CASCADE / ON UPDATE SET NULL 由 InnoDB 在存储引擎层直接完成,不经过 SQL 层的触发器机制,所以子表上的 BEFORE / AFTER 触发器不会被调用。

这跟应用层手写“先删子表再删主表”的行为不一致。审计日志漏记、库存没回滚这类问题,经常就出在这条路径上。

9.7 把这件事做成了可选项,默认仍然保持旧行为。

enable_cascade_triggers 是 SESSION 级变量

这个变量是会话级布尔值,ON / OFF,默认 OFF。开启方式:

SET SESSION enable_cascade_triggers = ON;

-- 确认当前连接的状态
SELECT @@session.enable_cascade_triggers;

注意是 SESSION 而不是 GLOBAL。在连接池场景下,这意味着必须对每一条连接都设置,否则会出现同一条业务请求时而有触发器、时而没有的情况——取决于这次拿到了哪条连接。用 my.cnf 的 [mysqld] 段写默认值只能覆盖“新连接的默认值”,对已经建立的连接无效:

[mysqld]
enable_cascade_triggers = ON

连接池的会话初始化语句(如 connectionInitSql 之类)里补一条 SET SESSION,比只改配置文件更可靠。

开启后:级联引发的触发器属于触发语句的一部分

不是“级联完成后再补跑触发器”,而是整条语句的一部分。所以触发器里报错,级联变更会跟着一起回滚:

CREATE TABLE parent (
  id INT PRIMARY KEY
) ENGINE=InnoDB;

CREATE TABLE child (
  id INT PRIMARY KEY,
  parent_id INT,
  FOREIGN KEY (parent_id) REFERENCES parent(id) ON DELETE CASCADE
) ENGINE=InnoDB;

CREATE TABLE child_audit (
  child_id INT,
  note VARCHAR(64)
) ENGINE=InnoDB;

CREATE TRIGGER trg_child_ad
AFTER DELETE ON child
FOR EACH ROW
BEGIN
  INSERT INTO child_audit VALUES (OLD.id, 'deleted by cascade');
END;
SET SESSION enable_cascade_triggers = ON;
DELETE FROM parent WHERE id = 1;
-- 子表行被级联删除,trg_child_ad 被调用,审计表写入

触发器内部如果执行 SIGNAL SQLSTATE '45000',父表这一行的删除同样会失败,子表的级联删除也不会留下半截状态。变量为 OFF 时,上面的 DELETE 照样删掉子表数据,只是 child_audit 不会多出记录。

复制:row 和 statement 不一样

  • row 格式:触发器在主库执行,产生的行变更直接记进 binlog;从库只按行重放,不会再执行触发器。也就是说从库上的审计表数据来自主库的行变更,触发器本身不动。
  • statement 格式:从库执行同一条 SQL,用的是主库的 enable_cascade_triggers 设置。

如果审计表在从库侧还有额外的写入逻辑,row 格式下这部分不会触发,别拿从库的审计表当成触发器执行过的证据。

排查顺序:级联路径 → 会话变量 → binlog_format

遇到“子表数据没了但审计表没记录”这类问题,按这个顺序看:

  1. 确认是不是级联路径:外键定义里是不是 ON DELETE CASCADE / ON UPDATE SET NULL,触发器是不是建在子表上。应用层自己发起的 DELETE 走的是普通触发器路径,跟这个变量无关。
  2. 看会话变量:SELECT @@session.enable_cascade_triggers;,而不是 @@global。连接池环境下尤其要确认当前这条连接的实际值。
  3. 看 binlog_format:row 格式下从库不重放触发器,问题可能出在复制侧而不是主库的变量配置。
复制全文 生成海报 MySQL 触发器 外键

推荐文章

程序员茄子在线接单