所有分页查询必须显式添加 deleted_at is null 条件,否则软删除数据会混入总数和结果集,导致页码错乱、总数不准;count 与主查询的 where 条件须完全一致,推荐复用同一条件变量或闭包。

翻页SQL里漏掉 deleted_at IS NULL 条件
软删除数据(比如用 Laravel 的 SoftDeletes trait 或自定义 deleted_at 字段)默认仍会进入 COUNT(*) 和主查询结果,除非显式过滤。翻页函数若直接套用 SELECT * FROM posts LIMIT ? OFFSET ?,就会把已软删的记录也计入总条数、甚至返回给前端。
实操建议:
- 所有分页相关的
COUNT查询必须加上WHERE deleted_at IS NULL(或WHERE is_deleted = 0,依字段设计而定) - 主数据查询同理,不能只在 PHP 层
array_filter掉软删项——那会导致页码错乱、总数不准 - 如果用 Doctrine 或 PDO 手写 SQL,注意 WHERE 子句顺序:先加软删条件,再加业务条件,避免索引失效
用 Laravel 的 withTrashed() / onlyTrashed() 干扰了分页逻辑
调用 withTrashed() 会取消软删除过滤,onlyTrashed() 则只查已删数据——这两者都不符合“仅展示有效数据+正常分页”的需求。常见错误是全局作用域没生效,或手动调用了这些方法却忘了还原。
实操建议:
- 确保模型启用了
SoftDeletes,且没有在查询构造器中误调withTrashed() - 如需临时绕过软删除(例如后台审核列表),应显式使用
withoutGlobalScopes()+ 手动加WHERE,而不是依赖withTrashed() - 检查是否在分页前调用了
->where(...)->withTrashed(),这种链式调用会让后续paginate()失去软删过滤
自定义翻页函数里 COUNT 和 SELECT 不一致
手写分页时,常把 COUNT 查询和主查询拆成两个语句。若两者 WHERE 条件不完全同步(比如 COUNT 加了 deleted_at IS NULL,但主查询漏了),就会出现“总页数对不上”“最后一页空数据”等问题。
实操建议:
- 把公共 WHERE 条件抽成变量或闭包,COUNT 和 SELECT 都复用同一组条件
- 用 PDO 时,避免拼接 SQL 字符串,改用预处理参数 + 统一条件数组
- 示例片段:
$baseWhere = "status = ? AND deleted_at IS NULL";<br>$count = $pdo->prepare("SELECT COUNT(*) FROM posts WHERE $baseWhere");<br>$stmt = $pdo->prepare("SELECT * FROM posts WHERE $baseWhere ORDER BY id DESC LIMIT ? OFFSET ?");
MySQL SQL_CALC_FOUND_ROWS 在软删除场景下失效
旧式分页喜欢用 SQL_CALC_FOUND_ROWS + FOUND_ROWS() 获取总数,但它统计的是原始结果集行数——如果主查询没加软删条件,FOUND_ROWS() 就包含软删数据,导致分页控件显示错误总页数。
实操建议:
- 直接弃用
SQL_CALC_FOUND_ROWS,改用显式COUNT(*)查询(现代 MySQL 版本中性能差距可忽略) - 若坚持用它,必须确保主查询的 WHERE 完全等价于 COUNT 查询的 WHERE,包括软删判断
- 注意:PHP 的
mysqli中FOUND_ROWS()返回值需用mysqli_query($conn, 'SELECT FOUND_ROWS()')单独获取,不是自动附带
实际分页中最容易被忽略的,是「软删除字段的 NULL 值判断」是否覆盖所有查询入口——哪怕一个 API 路由、一个后台导出脚本、一个队列任务里的查询漏掉了 deleted_at IS NULL,都会让分页总数或内容出错。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!











