触发器查大表会延长锁持有时间,因其与主事务共享同一事务ID和锁生命周期;若无索引导致全表扫描,每行都可能加锁且锁延迟至事务结束才释放,引发严重锁等待。

触发器里查大表,不是“慢一点”,而是直接把主事务拖进锁等待泥潭——因为查大表的 SQL 和原始 INSERT/UPDATE 共享同一个事务 ID、同一套锁生命周期。
为什么查大表会让锁持有时间不可控地延长
触发器内执行的任何 SQL 都不是独立事务,它和外面那条 INSERT 或 UPDATE 绑定在同一个事务里。一旦触发器里有类似 SELECT COUNT(*) FROM orders_history WHERE user_id = NEW.user_id 这种操作:
- 如果
orders_history没有user_id索引,就会全表扫描,每扫一行都可能加记录锁或间隙锁 - 锁不会在
SELECT结束时释放,而要等到整个事务COMMIT或ROLLBACK才释放 - 主语句早就执行完了,但事务卡在触发器里等扫描结果,其他会话想改同一行?只能排队等
常见踩坑写法与真实后果
这些看着“只是读一下”的语句,在触发器里全是定时炸弹:
-
SELECT MAX(seq_no) FROM log_archive WHERE date_key = '20261002':没索引 → 全表扫描 + 间隙锁争抢,尤其在 RR 隔离级别下 -
UPDATE summary_stats SET total = (SELECT SUM(amount) FROM trans WHERE month = '202610'):N 行插入 → N 次大表子查询,锁时间线性叠加 -
INSERT INTO audit_log SELECT * FROM temp_batch WHERE batch_id = NEW.batch_id:隐式锁住temp_batch所有匹配行,还可能触发二级索引锁升级
现象就是:单条 INSERT 耗时从 5ms 涨到 3s,SHOW PROCESSLIST 显示状态是 Updating,但 INFORMATION_SCHEMA.INNODB_TRX 里 TRX_STATE 是 RUNNING,TRX_STARTED 已过去两分钟——你根本看不到触发器里的 SQL,只看到原始语句。
怎么快速验证是不是触发器查大表惹的祸
别翻代码,直接禁用触发器做对比测试:
- MySQL:用
ALTER TRIGGER tr_name DISABLE(8.0.19+)或临时注释掉触发器体内的查询逻辑,再重放一次操作 - SQL Server:
DISABLE TRIGGER tr_name ON table_name - 观察是否还出现
Lock wait timeout exceeded或Waiting for table metadata lock
注意:禁用后必须确认业务影响,比如审计日志断档、下游同步延迟——这不是临时方案,而是定位手段。
真正可行的替代路径
触发器不是不能用,但绝不能让它干重活:
- 大表统计类需求,改用
BLACKHOLE日志表(如INSERT INTO summary_log VALUES (NEW.id, 'update')),由定时任务异步聚合 - 跨表关联读取,只允许基于主键或唯一索引的单行查,例如
SELECT qty FROM stock WHERE sku = NEW.sku,且sku必须有唯一索引 - 所有触发器内 DML / SELECT 都要过一遍
EXPLAIN FORMAT=TRADITIONAL,确保type是const或ref,不是ALL或index
最常被忽略的一点:触发器里没有“只读”这回事——只要它访问了数据,就可能拿锁;只要它没跑完,主事务就交不出锁。问题不在“查”,而在“查得重、查得慢、查得没索引”。

















