触发器中禁止使用SELECT FOR UPDATE或事务控制语句,应改用INSERT ... ON DUPLICATE KEY UPDATE(MySQL)或INSERT ... ON CONFLICT DO UPDATE(PostgreSQL)实现原子计数,并需确保唯一索引、区分真更新、避免锁竞争及读热点遗漏。

触发器里不能用 SELECT FOR UPDATE 或事务控制语句
想在触发器里直接查当前行的计数、再加一,很容易写成 SELECT count FROM hot_stats WHERE table_name = 'orders' AND row_id = NEW.id FOR UPDATE —— 这在 MySQL 5.7+ 和 PostgreSQL 中会报错,因为触发器执行时已处于父语句事务中,不允许显式加锁或启动新子事务。
实操建议:
- 改用
INSERT ... ON DUPLICATE KEY UPDATE(MySQL)或INSERT ... ON CONFLICT DO UPDATE(PostgreSQL),绕过先查后更的逻辑 - 确保
hot_stats表有唯一索引,比如UNIQUE (table_name, row_id),否则冲突检测失效 - 避免在触发器里调用存储函数做复杂计算,会拖慢主 DML 性能
UPDATE 触发器要区分“真更新”和“假更新”
用户执行 UPDATE users SET name='Alice' WHERE id=123,但 name 原值就是 'Alice',某些数据库(如 MySQL)仍会触发 AFTER UPDATE,导致计数虚高。
实操建议:
- MySQL 中用
IF OLD.column_name != NEW.column_name THEN ... END IF显式判断字段值是否真变 - PostgreSQL 中可用
WHEN (OLD.* IS DISTINCT FROM NEW.*)条件,它自动忽略 NULL 和全字段未变的情况 - 不要只比对单个字段——如果业务上“只要命中 WHERE 就算一次热点”,那就别判,但得提前跟产品对齐语义
计数表设计必须支持高并发写入
多个连接同时更新同一行(比如商品详情页被刷),触发器会并发写同一 hot_stats 记录,容易引发锁等待甚至死锁,尤其在使用 MyISAM 或低配 InnoDB 时。
实操建议:
- 用
INT UNSIGNED存计数,配合ON DUPLICATE KEY UPDATE cnt = cnt + 1,原子递增不依赖锁 - 避免在计数表上建太多二级索引,
INSERT/UPDATE时每个索引都要维护,放大写开销 - 如果日均更新超万次,考虑把计数异步化:触发器只写轻量消息到
hot_log表,由后台任务合并统计
触发器无法捕获 SELECT 引发的热点
这是最常被忽略的一点:触发器只响应 INSERT/UPDATE/DELETE,对 SELECT * FROM orders WHERE user_id = 123 这类高频查询完全无感。所谓“SQL 热点”,其实多数时候是读热点。
实操建议:
- MySQL 可开启
performance_schema,查events_statements_summary_by_digest获取高频 SQL 模板 - PostgreSQL 用
pg_stat_statements扩展,配合queryid聚合相似查询 - 如果非要在应用层补读计数,别在 DAO 层每个
selectById后硬加UPDATE hot_stats...,改用 RedisINCR先记,定时落库
真正难的不是怎么记,是怎么定义“热点”——是单行被查 100 次算热,还是某类查询平均响应超 200ms 算热?这个阈值得从监控数据里反推,不是触发器能决定的。











