sql_calc_found_rows 从 mysql 8.0.17 起被彻底移除,执行会报错;其设计虽意图复用扫描计数,但实际需遍历所有匹配行,性能差且阻碍优化;替代方案是合理使用 count(*) 或游标分页。

SQL_CALC_FOUND_ROWS 在 MySQL 8.0+ 中已失效
直接说结论:SQL_CALC_FOUND_ROWS 从 MySQL 8.0.17 开始被彻底移除,任何版本 ≥8.0.17 的实例执行带该关键字的语句会报错 ERROR 1314 (42000): SQL_CALC_FOUND_ROWS is not supported anymore。如果你正在用 8.0 或更新版本(比如 8.0.33、8.4),这条路已经走不通,强行沿用旧代码会直接中断查询。
为什么它曾经快,又为什么必须淘汰
SQL_CALC_FOUND_ROWS 的设计初衷是复用一次扫描过程:在执行带 LIMIT 的主查询时顺带统计全匹配行数,避免二次扫描。但它有个致命问题——即使你只取 10 行,MySQL 仍需遍历所有满足 WHERE 条件的行来计数,无法跳过。这在有复杂条件或大范围索引扫描时,性能反而比单独跑 COUNT(*) 更差。
- 它不走覆盖索引优化路径,
COUNT(*)在主键或唯一非空索引上可极快返回(InnoDB 引擎下常为常量时间) - 它阻塞查询缓存(如果启用)、干扰优化器对
LIMIT的剪枝判断 - 它的返回值
FOUND_ROWS()是会话级临时值,执行任意其他SELECT后即失效,极易因中间插入日志、调试语句等导致误读
替代方案:精准总数必须用 COUNT(*),但要写对
真正“精准”且“可用”的总数统计,只剩 COUNT(*) 这一条路。关键不是换语法,而是控制它的执行成本:
- 确保
WHERE条件能命中索引——否则COUNT(*)就是全表扫,再快的引擎也扛不住 - 避免在
COUNT(*)中混用JOIN和GROUP BY;如需关联后计数,先用子查询或 CTE 提前过滤 ID 集合 - 对超大表(千万级以上),不要在高并发分页接口中实时查总数;可异步更新统计表,或用近似值 + 缓存兜底(如
information_schema.TABLES.TABLE_ROWS,误差 ≤3%) - 示例(安全写法):
SELECT COUNT(*) AS total FROM users WHERE status = 1 AND created_at > '2025-01-01';
比SELECT SQL_CALC_FOUND_ROWS * FROM users WHERE status = 1 AND created_at > '2025-01-01' LIMIT 20 OFFSET 0;
+FOUND_ROWS()更稳定、更可预测
深分页场景下,“总数”本身可能就是伪需求
用户翻到第 1000 页时,还关心“总共 12487 页”吗?往往不关心。很多业务真正需要的是“是否有下一页”,而不是精确总页数。此时可用游标分页(cursor-based pagination):用上一页最后一条记录的 id 或时间戳作为下一页起点,完全绕开总数计算。
比如:
SELECT * FROM posts WHERE id —— 只需知道有没有第 21 条,就知道是否还能翻页。这种模式在 Feed 流、订单列表等场景中更健壮,也更容易加缓存。<p>真正难的不是怎么算总数,而是想清楚:这个总数,是不是此刻必须精准、必须实时、必须由数据库当场给出。</p>











