最有效的一招是直接砍掉高频count(*):innodb不存精确行数,每次执行需扫描索引,百万级表单次耗几百毫秒;应立即kill长查询、调大buffer_pool、flush tables止血,并用redis计数器(事务绑定+降级熔断)或触发器计数表根治。

直接砍掉高频 COUNT(*) 是最有效的一招。InnoDB 不存精确行数,每次执行都要扫主键索引,百万级表单次就耗几百毫秒;每秒几十次,CPU 必然拉满,连接池也会被占光。
立刻止血:5分钟内让服务恢复
别等分析完再动手,先保业务:
- 用 SHOW PROCESSLIST 找出长时间运行的 COUNT(*) 进程(Time > 60 秒),立刻 KILL
- 临时调大 innodb_buffer_pool_size(比如设为物理内存的 70%),减少磁盘 IO 带来的 CPU 等待
- 执行 FLUSH TABLES 清理缓存表句柄,避免锁表或元数据争用加重 CPU
- 若已出现连接堆积,可临时限制新连接:SET GLOBAL max_connections = 200(按实际负载调整)
根治方案:用 Redis 计数器替代实时 COUNT(*)
这不是简单缓存结果,而是把计数逻辑搬到写操作链路里:
- 在业务代码中,INSERT 成功后同步 INCR user:active:count,DELETE 后同步 DECR user:active:count
- 务必和数据库事务绑定——用 @Transactional + afterCommit 回调 或 try-finally 保证 DB 写成功才更新 Redis
- 首次加载用 SETNX + EX 30 加分布式锁,防多实例并发初始化导致计数错乱
- Redis 宕机时要有降级:自动 fallback 到 SELECT COUNT(*) FROM t WHERE ...,并加熔断(例如 5 秒内失败 3 次即拒绝再查)
备选路径:触发器维护专用计数表(适合读远多于写的场景)
当应用层改造成本高、且变更频率较低时可用:
- 建一张 counter_table,含
counter_key VARCHAR(64) PRIMARY KEY和counter_value BIGINT UNSIGNED - 在目标表上建 INSERT/DELETE 触发器,用 INSERT ... ON DUPLICATE KEY UPDATE counter_value = counter_value + 1 维护
- 注意避坑:TRUNCATE 不触发、LOAD DATA 不捕获、主从延迟会导致从库计数滞后——这些地方得补异步校准任务
- 写入量超每秒 500 次就慎用,否则触发器本身会拖慢 DML 性能
顺便检查:有没有其他放大 COUNT 压力的错误习惯
高频 COUNT 往往不是孤立问题,常伴生以下隐患:
- SQL 中嵌套子查询做 COUNT,比如
WHERE city IN (SELECT city FROM ...)—— 改成 JOIN 或临时表预计算 - 没走索引的 COUNT 条件,如
COUNT(*) WHERE status=1 AND create_time > '2025-01-01'却没给(status, create_time)建联合索引 - 应用层未做请求合并,多个前端接口各自调一次 COUNT,应统一聚合或加客户端缓存
- 误开 query_cache(MySQL 8.0 已移除),在高写场景下反而引发锁竞争,徒增 CPU 开销











