游标在sql触发器中必然导致性能严重下降,因其每次插入都需初始化、行拷贝、锁检查及重复解析,易引发io暴涨、元数据锁阻塞和死锁;应改用集合操作如join、批量insert/update替代。

在 SQL 触发器中用游标,基本等于主动给写入链路装上减速带——不是“可能卡”,而是“必然卡”,且并发一上来就锁死。
触发器里开游标 = 每行插入都启动一次微型数据库会话
触发器本身就在事务内同步执行,而游标(尤其是 MySQL/SQL Server 的默认类型)每次 OPEN 都要预加载结果集、维护内部状态、申请元数据锁。更致命的是:FETCH 不是轻量指针移动,而是完整的一次行拷贝 + 变量赋值 + 锁检查。100 行插入 → 100 次游标初始化 + 100×N 次 FETCH 开销(N 是循环体内语句数)。
- MySQL 中
DECLARE CURSOR FOR SELECT ...在触发器里无法复用执行计划,每次OPEN都重新解析、优化、分配内存 - 游标结果集若超
tmp_table_size,直接落盘成Created_tmp_disk_tables,IO 爆涨 - 触发器没独立事务边界,游标打开期间一直持有表级元数据锁(
Waiting for table metadata lock),阻塞ALTER TABLE等 DDL
嵌套查询 + 游标 = O(N×M) 性能核弹
最常见错误:在游标循环里再查大表。比如 FETCH cur INTO @user_id 后立刻 SELECT COUNT(*) FROM logs WHERE user_id = @user_id。这会让单次插入从 O(1) 变成 O(M),1000 行插入实际触发 1000 次全表扫描。
- 哪怕
logs.user_id有索引,每次查询仍需独立走索引 B+ 树 + 回表,无法合并为一次 JOIN - 所有嵌套查询都被纳入主事务,锁范围扩大,极易引发死锁或锁等待超时(
Lock wait timeout exceeded) - EXPLAIN 显示 type=ALL 或 Extra=Using temporary/Using filesort → 实锤性能崩坏
替代方案不是“怎么写游标”,而是“根本别用游标”
95% 的触发器游标需求,本质是要对一批数据做关联操作——这不是过程式任务,是集合运算问题。强行用游标,等于让数据库退化成单行计算器。
- 逐行更新 → 改用
UPDATE ... FROM(SQL Server)、UPDATE JOIN(MySQL)或MERGE(PostgreSQL) - 逐行插入日志 → 改用
INSERT INTO log_table SELECT ... FROM inserted(SQL Server)或临时表 + 批量 INSERT - 需要前一行状态?优先用窗口函数(
LAG(),SUM() OVER)+ CTE,而非游标变量传递 - 真要过程逻辑(如风控规则链),提至应用层用 Redis Lua 或流处理引擎,数据库只存终态
真正难的不是写出能跑的游标,而是看到 AFTER INSERT 里那行 DECLARE cur CURSOR 时,立刻意识到:这里本该是一条 JOIN,而不是一个循环。











