触发器中直接UPDATE统计表易锁表,应改用INSERT...ON DUPLICATE KEY UPDATE或异步日志聚合;需确保唯一索引、避免同一表递归操作、注意binlog_format与sql_log_bin配置。

触发器里用 UPDATE 统计次数容易锁表
直接在触发器里对统计表做 UPDATE ... SET count = count + 1 看似简单,但高并发下会引发行锁争用,尤其当多个事务同时更新同一行(比如统计「用户ID=123的修改次数」)时,UPDATE 会等锁,拖慢主业务。
更稳妥的做法是先 INSERT INTO stats_log (table_name, row_id, op_type, created_at) VALUES (...) 记日志,再用异步任务或定时聚合。如果非得实时统计,至少改用 INSERT ... ON DUPLICATE KEY UPDATE,避免先查后更新的两阶段操作。
- 确保统计表有唯一索引(如
(table_name, row_id, op_type)),否则ON DUPLICATE KEY UPDATE无效 - 不要在触发器里调用存储过程做复杂计算——MySQL 触发器不支持事务内嵌套长耗时逻辑
- 注意
NEW和OLD在BEFORE/AFTER中的可用性差异:比如BEFORE UPDATE里能改NEW.col,但读不到最终写入值
INSERT 触发器统计新增频次要防重复插入
想统计某张表每天新增多少条,常见错误是在 INSERT 触发器里直接往日期维度统计表写一行:INSERT INTO daily_count (date, table_name, cnt) VALUES (CURDATE(), 'orders', 1)。问题在于:如果当天已有记录,这条语句会报 Duplicate entry 错误,导致整个 INSERT 失败。
正确做法是用 INSERT ... ON DUPLICATE KEY UPDATE cnt = cnt + 1,前提是 daily_count 表有联合唯一键(如 (date, table_name))。
-
CURDATE()返回的是当前会话时区时间,若数据库跨时区部署,需确认是否和业务期望的“日”一致 - 避免用
NOW()替代CURDATE()做分区依据——它带时分秒,会导致每天生成多条记录 - 触发器里不能用
SELECT ... FOR UPDATE,所以别试图手动加锁再判断再插入
DELETE 触发器统计删除行为要注意 OLD 数据有效性
DELETE 触发器中只有 OLD 可用,且只含被删行原始值。常有人想顺手把关联表也删掉,或记一条「软删标记」,但 MySQL 触发器不允许在 DELETE 触发器里再对**同一张表**执行 DELETE 或 UPDATE(会报 Can't modify same table 错误)。
如果真要联动清理,得换思路:把要删的 OLD.id 写进中间日志表,再由外部脚本或事件调度器处理。
-
OLD在BEFORE DELETE和AFTER DELETE中都有效,但AFTER阶段原记录已物理消失,仅剩触发器上下文中的快照 - 不要在触发器里调用
SLEEP()或复杂函数——它会阻塞主 DML,影响所有后续请求 - 统计「谁删的」需要依赖应用层传入的
user_id字段,触发器本身拿不到连接用户信息(除非用USER(),但不可靠)
触发器统计结果不准?先查 binlog_format 和 sql_log_bin
线上发现统计数比实际少,大概率是主从复制或备份场景干扰了触发器执行。MySQL 的触发器默认不走 binlog(sql_log_bin=0 时完全不触发),而 binlog_format=STATEMENT 下,某些函数(如 NOW()、UUID())在从库重放时可能产生不同结果,连带让触发器逻辑偏移。
生产环境建议统一设为 binlog_format=ROW,并确认 sql_log_bin=1(尤其在用 mysqldump --single-transaction 恢复时,临时关闭 binlog 会导致触发器失效)。
- 用
SHOW VARIABLES LIKE 'binlog_format'和SELECT @@sql_log_bin快速验证当前会话设置 - 触发器本身不会被记录进 binlog,但触发器引起的 DML 会被记录——这点常被误认为「触发器没跑」
- 如果用了 GTID,还要检查
enforce_gtid_consistency=ON是否限制了某些函数使用(如UUID())










