
PHP 中数据库查询性能问题,往往不在于 PHP 代码本身,而在于 SQL 写法、索引缺失或数据访问模式不合理。重写查询不是简单换写法,而是结合执行计划、业务语义和数据分布做有针对性的调整。
避免 SELECT *,明确字段列表
全字段查询会增加网络传输、内存占用和解析开销,尤其当表中存在 TEXT、BLOB 或大量冗余字段时。更严重的是,它可能让 MySQL 无法使用覆盖索引(Covering Index)。
- 把
SELECT * FROM users WHERE status = 1改为SELECT id, name, email FROM users WHERE status = 1 - 如果只需要计数,直接用
SELECT COUNT(*),不要查出全部记录再用 PHPcount() - 关联查询时,只取业务真正需要的字段,避免跨表拖入大量无用列
用 JOIN 替代子查询(尤其相关子查询)
MySQL 对某些嵌套子查询(特别是 WHERE 中依赖外层字段的相关子查询)优化能力较弱,可能触发全表扫描或临时表。等价的 JOIN 通常能走索引且执行更快。
- 低效写法:
SELECT name FROM orders WHERE user_id IN (SELECT id FROM users WHERE city = 'Shanghai') - 优化后:
SELECT o.name FROM orders o JOIN users u ON o.user_id = u.id WHERE u.city = 'Shanghai' - 注意:JOIN 前确认关联字段有索引(如
users.id和orders.user_id),否则反而更慢
合理使用 LIMIT 和分页优化
深分页(如 LIMIT 10000, 20)会导致 MySQL 扫描前 10020 行再丢弃前 10000 行,效率急剧下降。对高偏移量场景需改用游标分页或延迟关联。
立即学习“PHP免费学习笔记(深入)”;
- 用主键/时间戳作为游标:
SELECT * FROM logs WHERE created_at - 延迟关联优化(适用于需查多字段的大表分页):
SELECT l.* FROM logs l INNER JOIN (SELECT id FROM logs ORDER BY id LIMIT 10000, 20) AS tmp USING(id) - 避免在分页接口中暴露总页数(
COUNT(*)全表统计),可改用“是否有下一页”逻辑判断
拆分复杂查询,交由 PHP 协同处理
单条超长 SQL 可读性差、调试难,且容易因某一部分(如 GROUP BY + 多表 JOIN + 子查询嵌套)导致执行计划退化。适度拆分,利用 PHP 数组操作补足逻辑,反而更稳定高效。
- 例如统计“每个分类下最新 3 篇文章”,不必强求一条 SQL 实现;可先查出分类 ID 列表,再用
WHERE category_id IN (...)批量查文章,最后用 PHP 按分类归组并截取 - 聚合类需求(如带条件去重计数)若 SQL 表达成本高,可考虑查出明细后用
array_unique或array_reduce在 PHP 层计算 - 拆分前提:网络往返可控(如用 PDO::beginTransaction 批量 fetch)、数据量适中(单次结果集建议
查询重写不是越短越好,也不是越“炫技”越好。核心是让 MySQL 少扫描、少排序、少临时表,同时让 PHP 逻辑清晰、易于维护。上线前务必用 EXPLAIN 验证执行计划,对比真实数据下的响应时间和 CPU/IO 消耗。











