应直接用 select count(*) 统计行数,而非 mysqli_num_rows() 或 pdostatement::rowcount();前者由数据库高效准确计算,后者仅在已完整获取结果集时才可靠,且对 select 行为不一致、不可靠。

直接用 SELECT COUNT(*),别在 PHP 里用 mysqli_num_rows() 或 PDOStatement::rowCount() 拿查询结果集行数来“统计”——除非你已经 fetch 完全部数据,否则它不可靠、低效、还可能出错。
为什么 mysqli_num_rows() 经常返回 0 或 -1
这个函数只对**已缓冲的完整结果集**有效。如果你用的是无缓冲查询(mysqli_use_result())、或用了 mysqli_prepare() + mysqli_stmt_execute() 但没调 mysqli_stmt_store_result(),mysqli_num_rows() 就拿不到真实行数。
- MySQLi 默认预处理语句不自动缓冲,
mysqli_stmt_num_rows()在execute()后立刻调用会返回 -1 - 如果用
mysqli_query($conn, $sql)但连接启用了MYSQLI_STORE_RESULT外的模式,也可能返回 0 - 它本质是读取客户端已接收的行数,不是服务端实际匹配数
PDO::rowCount() 在 SELECT 上行为不一致
PDO::rowCount() 对 SELECT 的支持完全取决于底层驱动和是否启用 PDO::MYSQL_ATTR_USE_BUFFERED_QUERY。MySQL 驱动下默认不保证返回正确值,官方文档明确写 “For most databases, PDOStatement::rowCount() does not return the number of rows affected by a SELECT statement.”
- PostgreSQL 和 SQLite 的 PDO 实现甚至直接返回 -1
- 即使 MySQL 返回了数字,也可能是缓存结果集大小,而非 SQL 实际扫描行数
- 它更适合
INSERT/UPDATE/DELETE场景,别拿来当 count 用
真正安全高效的统计:全交给数据库
让 MySQL 自己算,再取一个整数字段回来,这是唯一既准确又省资源的方式。尤其当表大、带 WHERE、JOIN 或 GROUP BY 时,避免把几万行数据全拉到 PHP 再计数。
// ✅ 推荐:COUNT(*) 是最直接、优化器友好、且语义清晰的写法 $sql = "SELECT COUNT(*) AS cnt FROM users WHERE status = ? AND created_at > ?"; $stmt = $pdo->prepare($sql); $stmt->execute(['active', '2024-01-01']); $count = (int) $stmt->fetchColumn(); // ⚠️ 注意:不要用 COUNT(id) 替代 COUNT(*),除非你明确要排除 NULL —— 但主键 id 不会为 NULL,所以纯属多此一举 // ❌ 错误示例:$count = $stmt->rowCount(); // 不可靠
- 加索引能极大加速
COUNT(*),特别是覆盖索引(如INDEX(status, created_at)) - 如果只是判断“是否存在”,用
SELECT 1 FROM ... LIMIT 1+fetch()更快 - 对超大表分页场景,
COUNT(*)可能变慢,此时考虑估算(SHOW TABLE STATUS)或维护计数缓存
什么时候才该用 mysqli_num_rows() 或 rowCount()
仅限于你**已经完整获取并遍历了结果集**,且只是想“回看”一下刚才取了多少行——比如调试日志、临时校验、或封装了一个 fetchAll() 方法后顺手返回长度。
- 它不是统计手段,而是“结果集消费后的副产品”
- 若代码中先
fetch()再num_rows(),说明逻辑冗余;应直接改用COUNT(*) - 在流式处理(如导出 CSV)中,永远别依赖它做总数判断
复杂点在于:很多人混淆了“结果集行数”和“满足条件的总行数”。前者是客户端看到的,后者是服务端真实的。只要没 fetch 完,前者就不可信;而后者,数据库比 PHP 更懂怎么算。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











