真正卡住I/O的是触发器内高频写操作放大redo/binlog刷盘、脏页积压触发Furious Flushing或锁竞争导致事务排队;需查触发器实际执行频次与DML类型,实测磁盘IOPS设innodb_io_capacity,调大innodb_log_file_size,并确保涉及表有合适索引。

MySQL 触发器本身不直接导致磁盘 I/O 异常升高——真正出问题的,是触发器内部执行的语句(尤其是写操作)叠加了主事务的 I/O 压力,或引发隐式锁、日志刷盘放大、缓冲池争用等连锁反应。排查和修复必须绕过“是触发器惹的祸”这个表层归因,直击其实际行为。
查清触发器是否真在高频执行写入
很多场景下,I/O 高只是“看起来和触发器有关”:比如业务批量导入时顺带触发了 INSERT AFTER 触发器,但罪魁其实是主 SQL 本身;或者触发器只读(SELECT),却因锁等待拉长事务时间,间接拖累刷盘节奏。
实操建议:
- 用
SHOW TRIGGERS确认触发器定义,重点看是否含INSERT/UPDATE/DELETE、INSERT INTO ... SELECT、或调用存储函数(可能含写逻辑) - 开启通用日志(
general_log = ON)并过滤关键词:grep -i "trigger_name" /var/lib/mysql/general.log,确认单位时间内触发次数是否异常(如每秒上百次) - 对比关闭触发器前后 I/O 变化:
ALTER TABLE tbl_name DISABLE TRIGGER trigger_name(测试环境验证后操作)
检查触发器是否放大 redo / binlog 刷盘压力
触发器内每条 DML 都会生成额外 redo log 条目,并计入 binlog event;若主事务已设 innodb_flush_log_at_trx_commit = 1 且 sync_binlog = 1,一个主 INSERT 带 3 行触发器 INSERT,就变成 4 次 fsync —— I/O 直接翻倍。
常见错误现象:iostat -x 1 显示 w_await > 20ms、%util 接近 100%,但 SHOW ENGINE INNODB STATUS 中 Log sequence number 增速远超业务 QPS
实操建议:
- 临时降低刷盘强度(仅限非金融/强一致性场景):
SET GLOBAL innodb_flush_log_at_trx_commit = 2,SET GLOBAL sync_binlog = 1000 - 观察
Innodb_os_log_written每秒增量是否回落(SHOW GLOBAL STATUS LIKE 'Innodb_os_log_written') - 若回落明显,说明触发器写入正被高频 fsync 放大;此时应重构触发器逻辑,把多条 DML 合并为单条(如用
INSERT ... ON DUPLICATE KEY UPDATE替代先查后插)
警惕触发器引发的脏页积压与 buffer pool 争用
当触发器执行写操作且并发高时,会快速产生大量脏页;若 innodb_io_capacity 远低于磁盘真实 IOPS,后台线程刷不过来,就会触发 Furious Flushing——主线程被迫同步刷盘,直接卡住所有写入。
关键指标信号:
-
Innodb_buffer_pool_pages_dirty持续 > 70% 总页数 -
Innodb_buffer_pool_wait_free非零且持续增长 -
SHOW ENGINE INNODB STATUS中FILE I/O段显示pending normal aio reads/writes数值高
实操建议:
- 立即用
fio实测磁盘随机写 IOPS:fio --name=randwrite --ioengine=libaio --rw=randwrite --bs=16k --size=1G --runtime=60 --time_based - 将
innodb_io_capacity设为实测值 × 0.7(SSD 通常 1000–4000,HDD ≤ 200),innodb_io_capacity_max设为 2 倍 - 切勿只调高
innodb_io_capacity就完事——必须同步检查innodb_log_file_size是否过小(默认 48MB),否则 checkpoint 频繁会把 redo 压力转成更慢的 data file 随机写
避免触发器嵌套与跨表写入引发的锁竞争
触发器里更新另一张大表,极易造成行锁/间隙锁扩散;若该表无合适索引,还会升级为表锁。此时 Innodb_row_lock_waits 上升,事务排队,redo 和 binlog 缓存堆积,最终表现为 I/O 持续高位。
使用场景典型例子:订单表 INSERT 触发器去更新用户积分表,而积分表未对 user_id 建索引,每次更新都全表扫描加锁。
实操建议:
- 用
SELECT * FROM information_schema.INNODB_TRX查当前长事务,结合INFORMATION_SCHEMA.INNODB_LOCK_WAITS定位锁等待链 - 对触发器中涉及的所有 DML 表,执行
EXPLAIN验证执行计划是否走索引;尤其注意UPDATE/DELETE的WHERE条件字段是否有索引 - 强制异步化:把触发器中的耗时写操作剥离,改用消息队列或定时任务处理(如写入一张中间日志表,由后台进程消费)
真正卡住 I/O 的,从来不是触发器语法本身,而是它在事务上下文中悄悄放大的写负载、锁范围和日志刷盘频率。调参能缓一时,但删掉一个无索引的 UPDATE 或合并三次 INSERT,效果立竿见影。


















