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 1356 (HY000): View 'xxx' references invalid table(s) or column(s) 或更直白的 Unknown syntax。如果你的代码里还留着它,现在就得改。
为什么不能继续用?性能和语义问题太严重
这个提示符本意是让 MySQL 在 LIMIT 查询后“顺便”算出全量行数,但实际执行时并不会真的只扫一次表——优化器仍可能执行两遍扫描(尤其在无合适索引时),且无法利用缓存、不支持并行、还会干扰查询重写。社区早就不推荐,MySQL 官方也明确标记为 deprecated(自 5.7.20 起)长达多年,最终移除是必然。
- 即使你在 5.7 或 8.0.16 用它,
FOUND_ROWS()返回值也不保证准确,比如中间有 INSERT/UPDATE/DELETE 就会失效 - 它和事务隔离级别耦合:在
REPEATABLE READ下,FOUND_ROWS()可能返回快照中的旧计数 - 无法和大多数 ORM(如 SQLAlchemy、MyBatis)的分页插件协同,容易引发 N+1 或错位分页
替代方案:用子查询或单独 COUNT(*),但要注意 WHERE 条件一致性
最稳妥的做法是把分页查询和总数查询拆开,显式写出相同的 WHERE 条件。别指望“复用 SQL 字符串”,手动复制粘贴都比拼接字符串可靠。
例如原写法(已失效):
SELECT SQL_CALC_FOUND_ROWS * FROM users WHERE status = 1 LIMIT 20 OFFSET 0; SELECT FOUND_ROWS();
应改为:
SELECT * FROM users WHERE status = 1 LIMIT 20 OFFSET 0; SELECT COUNT(*) FROM users WHERE status = 1;
- 两个查询的
WHERE必须完全一致,包括参数绑定顺序、NULL 处理(如status = ?vsstatus ?)、函数调用(DATE(created_at)等) - 如果条件复杂(含 JOIN、子查询),建议把核心过滤逻辑提取成视图或 CTE(MySQL 8.0+ 支持),避免重复书写
- 对大表,
COUNT(*)可能慢——这时得看是否真需要精确总数;若只是做“是否有下一页”,用SELECT 1 FROM ... LIMIT 1 OFFSET N更轻量
ORM 和分页组件怎么适配?
主流框架基本已弃用 SQL_CALC_FOUND_ROWS 支持。比如 Laravel 的 paginate() 在 MySQL 8.0+ 默认走双查模式;Django 的 Paginator 也要求你传入 queryset,内部自动拆 COUNT + LIMIT。
- 检查你用的分页库文档,确认其 MySQL 版本兼容性,有些老版本(如 PageHelper 5.1.8 之前)仍尝试用
SQL_CALC_FOUND_ROWS,必须升级 - 手写分页逻辑时,别在同一个事务里混用
SELECT ... LIMIT和SELECT FOUND_ROWS()——不仅无效,还可能因连接复用导致计数错乱 - 如果业务允许估算总数(如后台管理列表),可用
EXPLAIN的rows值近似,但注意这是优化器预估,非真实值
真正麻烦的不是语法替换,而是所有依赖 SQL_CALC_FOUND_ROWS 的旧脚本、监控 SQL、BI 报表语句,都得逐条核对 WHERE 是否一致——漏一处,分页就错一片。


















