pdo::rowcount() 对 select 不可靠,应改用 count(*) 单独查询总数;仅当结果极小且需多次遍历时才可用 fetchall()+count()。

PDO::rowCount() 对 SELECT 语句不可靠
直接调用 PDOStatement::rowCount() 获取 SELECT 查询的行数,在大多数 PDO 驱动(如 mysql、pgsql、sqlsrv)下返回的是 0 或未定义值,不是真实结果集行数。这是 PDO 规范明确允许的行为——它只保证对 INSERT、UPDATE、DELETE 返回准确影响行数,而对 SELECT 不作承诺。
常见错误现象:
- 执行 $stmt->execute(); var_dump($stmt->rowCount()); 输出 0,即使查询返回了 100 行;
- 在 MySQL 中启用 PDO::MYSQL_ATTR_USE_BUFFERED_QUERY 后看似能返回正确值,但这是驱动特例,不能跨数据库移植;
- 依赖 rowCount() 做分页判断或空结果处理,导致逻辑错乱。
安全可靠的方法:用 COUNT(*) 单独查总数
真正可移植、高效、不加载数据到内存的方式,是把“查数据”和“查总行数”拆成两个独立 SQL:
- 先执行
SELECT COUNT(*) FROM table WHERE ...获取精确总数,再用fetchColumn()提取数值; - 再执行实际的
SELECT * FROM table WHERE ... LIMIT ...拿分页数据; - 避免在 PHP 层用
fetchAll()+count(),尤其当结果可能上万行时,会吃光内存并拖慢响应。
示例:
$countStmt = $pdo->prepare("SELECT COUNT(*) FROM users WHERE status = ?");
$countStmt->execute([1]);
$total = (int) $countStmt->fetchColumn();
<p>$dataStmt = $pdo->prepare("SELECT id, name FROM users WHERE status = ? ORDER BY id LIMIT 20 OFFSET 0");
$dataStmt->execute([1]);
$rows = $dataStmt->fetchAll(PDO::FETCH_ASSOC);
</p>
何时可以放心用 fetchAll() + count()
仅当满足以下全部条件时,才考虑一次性拉取再统计:
- 业务确定结果集极小(比如固定配置表,最多几十行);
- 后续还需反复遍历该结果集多次(否则单次
fetch()循环更省内存); - 不介意多一次数据传输和 PHP 数组构造开销;
- 你用的是
sqlite或某些老版本mysql驱动,且已实测rowCount()确实返回了正确值(但别写死逻辑)。
示例(仅限小数据):
$stmt = $pdo->query("SELECT name FROM countries");
$rows = $stmt->fetchAll(PDO::FETCH_COLUMN);
$cnt = count($rows); // 安全,因为 $rows 是明确的数组
注意 sqlsrv 和 pgsql 的特殊行为
SQL Server(sqlsrv)和 PostgreSQL(pgsql)驱动在默认游标模式下,rowCount() 对 SELECT 通常返回 -1,表示“无法确定”。如果你启用了客户端游标(如 PDO::ATTR_CURSOR => PDO::CURSOR_SCROLL),部分版本可能返回正确值,但:
- 这会显著增加数据库连接资源占用;
- PHP 层仍需调用
fetch()多次才能“推进”游标,实际并未节省操作; - 一旦切换数据库类型(比如从 SQL Server 迁移到 MySQL),代码立即失效。
所以不要为兼容某一个驱动去调整游标模式来“修复” rowCount() —— 本质是误用 API。
最易被忽略的一点:很多人以为 PDO::exec() 能用于 SELECT,但它根本不会返回结果集,调用后只会报错或静默失败。查行数这件事,必须区分清楚“执行无结果语句”和“执行有结果查询”两种场景。











