count(*)在千万级innodb表上慢是因为需全表或聚簇索引扫描,无行数缓存;myisam快因维护精确行数但不支持事务;优化方案包括用计数表、非空列索引、information_schema估算等。

为什么 COUNT(*) 在千万级表上慢得像卡住
因为 MySQL 默认走聚簇索引全扫描——哪怕你只想要一个数字,它也得把整张表(或至少主键索引)从头读到尾。InnoDB 没有内置行数缓存,COUNT(*) 不是查元数据,是真算。
常见错误现象:SELECT COUNT(*) FROM orders 耗时 8 秒、执行计划显示 type: ALL、慢日志里反复出现、监控看到 IO 持续拉满。
- 使用场景:后台管理页分页总数、数据看板实时统计、导出前校验条数
- MyISAM 表快是因为它维护了精确行数,但不支持事务,现在基本不用
-
COUNT(1)和COUNT(*)在 InnoDB 下性能完全一样,别信“用 1 更快”的老黄历 - 加
WHERE条件后,哪怕有索引,也可能因回表或范围扫描导致变慢
用单独计数表替代实时 COUNT(*)
核心思路:把“实时计算”换成“异步更新”,用空间换时间。适合写少读多、允许秒级延迟的场景(比如后台用户看总订单数)。
实操建议:
- 建一张极简表:
CREATE TABLE order_count (total BIGINT NOT NULL DEFAULT 0); INSERT INTO order_count VALUES (0); - 所有写操作(
INSERT/DELETE)必须配套更新该表,用事务包住:START TRANSACTION; INSERT INTO orders (...) VALUES (...); UPDATE order_count SET total = total + 1; COMMIT;
- 避免用
REPLACE INTO或INSERT ... ON DUPLICATE KEY UPDATE做计数更新,容易在高并发下丢更新 - 如果业务有批量导入,别循环单条
INSERT + UPDATE,改用INSERT ... SELECT COUNT(*) FROM temp_table一次性加总
COUNT(*) 加索引也没用?那什么索引能加速
普通二级索引对 COUNT(*) 几乎没帮助——MySQL 5.7+ 虽支持索引覆盖优化,但前提是查询能走「最小索引」且不含 WHERE。真正有效的只有这几种情况:
- 明确指定非空列:用
COUNT(non_null_column)且该列上有二级索引,MySQL 可能选这个索引(比主键窄),但收益有限 - 带
WHERE条件时,确保条件字段有高效索引,例如COUNT(*) WHERE status = 'paid',给status建索引才有效 - 复合索引要注意顺序:如果常查
COUNT(*) WHERE shop_id = ? AND created_at > ?,索引应为(shop_id, created_at),而非反过来 - 别给大文本列(如
TEXT)建索引指望加速COUNT,索引本身会巨大且无益
近似值够用时,直接查 information_schema.TABLES
当你要的只是“大概多少万条”,比如做容量预估、冷热数据判断,TABLE_ROWS 字段就是现成答案——它是采样估算,毫秒返回。
注意点:
- 执行:
SELECT TABLE_ROWS FROM information_schema.TABLES WHERE TABLE_SCHEMA = 'db_name' AND TABLE_NAME = 'orders'; - 误差可能达 40%,尤其表刚大批量写入后未 ANALYZE,数值滞后
- MyISAM 返回精确值,InnoDB 是估算,别拿它校验业务逻辑
- 权限问题:普通用户可能看不到
information_schema,需授予SELECT权限
真实项目里,最常被忽略的是计数表的更新一致性——不是忘了加,而是漏了在某个异常分支(比如事务回滚、消息重试、脚本直连)里没同步减。一旦偏差产生,很难自动修复。











