InnoDB 中 COUNT(*) 很慢是因为其需按 MVCC 规则逐行判断可见性并累加,无法像 MyISAM 那样直接读取磁盘存储的总行数;表越大、并发越复杂,全表扫描开销越显著。

为什么直接用 COUNT(*) 在 InnoDB 里很慢
因为 InnoDB 不像 MyISAM 那样把总行数存在磁盘上——它必须按 MVCC 规则,一行行读取并判断当前事务是否可见,再累加。表越大、并发越复杂,COUNT(*) 越容易卡在全表扫描上。哪怕你只查总数、没加 WHERE,只要引擎是 InnoDB,就逃不开这个过程。
用 EXPLAIN 的 rows 值代替 COUNT(*) 取近似值
这是最轻量的“伪优化”:不真正执行查询,只让优化器估算。对无 WHERE 条件的单表统计,EXPLAIN SELECT * FROM t 返回的 rows 字段,通常就是当前事务快照下较合理的行数估计值。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
-
EXPLAIN不锁表、不扫描数据页,毫秒级返回 - 结果不是精确值,但对分页总数、后台监控、趋势展示类场景足够用
- 注意:如果表刚大批量插入/删除过,或统计信息未更新(
ANALYZE TABLE t没跑),rows可能偏差较大 - 不要在强一致性要求的业务逻辑里用它,比如支付订单校验
用 Redis 缓存 COUNT 结果时的关键控制点
缓存本质是用空间换时间,但 COUNT(*) 的缓存比普通字段更难维护——因为每次增删都可能影响总数。不能只靠定时刷新,得结合写操作实时干预。
- 所有写入口(
INSERT/DELETE/UPDATE影响行数的语句)必须同步INCR或DECRRedis key - 避免用 Lua 脚本做“先查后改”,要用原子命令如
INCRBY配合业务层预判增减量 - Redis 中的值必须带版本号或时间戳,防止主从延迟导致缓存与 DB 不一致时无法识别
- 设置合理过期时间(如
EXPIRE count:user 300),作为兜底机制,避免缓存长期脏掉
别踩这些坑:缓存和近似值的边界在哪
近似值和缓存都不是银弹。当你的业务需要精确总数(比如财务对账、审计导出),或者表结构频繁变更(如分表策略调整)、或有跨库聚合需求时,这两招都会失效。此时只能接受 COUNT(*) 的代价,或重构为异步计数表(如每日凌晨跑一个 INSERT INTO daily_count SELECT COUNT(*), CURDATE() FROM t)。真正的优化,永远始于明确“这里到底需不需要精确”。

















