本文目录导读:

- 第一阶段:定位瓶颈(最重要,占80%的工作量)
- 第二阶段:数据库侧优化(通常能解决80%的慢查询)
- 第三阶段:PHP 代码侧优化
- 第四阶段:缓存策略(降低数据库压力的“大招”)
- 第五阶段:基础设施与架构调整(终极方案)
- 给你的执行清单
PHP 慢查询优化是一个非常系统性的工程,但不要一上来就改代码,正确的顺序应该是:先定位瓶颈(在数据库还是PHP本身),再针对性优化。
以下是系统性的入手指南,按优先级排序:
第一阶段:定位瓶颈(最重要,占80%的工作量)
慢查询的根源可能在数据库(SQL慢),也可能在PHP(CPU计算慢、IO阻塞),甚至可能是在网络传输。
开启慢查询日志(数据库侧)
这是最快定位“烂SQL”的方式。
- MySQL:在
my.cnf中开启,设置阈值(例如2秒)。slow_query_log = ON long_query_time = 2 log_queries_not_using_indexes = 1
- 开启后,用
mysqldumpslow或pt-query-digest工具分析日志,找出 执行次数多 和 平均耗时高 的SQL。
使用PHP框架的调试工具(应用侧)
- Laravel:开启
APP_DEBUG=true,使用Debugbar查看页面加载时执行的每一条SQL及耗时。 - ThinkPHP:开启“Trace”和SQL日志。
- 如果没框架,建议在数据库查询入口,统一加一个 打点函数,记录每个SQL的耗时,写入日志文件。
使用 APM 工具(一键定位)
- 推荐:
SkyWalking、Pinpoint、OneAPM,这能直接告诉你整个请求链路中,是PHP函数慢,还是数据库慢,还是Redis慢。
第二阶段:数据库侧优化(通常能解决80%的慢查询)
如果确认是SQL慢,按下述步骤操作:
审视 SQL 语句(避免低级错误)
- *避免 `SELECT `**:只取需要的字段,减少数据传输量。
- 避免
LIKE '%关键词%':这会导致全表扫描,如果业务必须用,考虑引入 Elasticsearch 或搜索引擎。 - 避免函数或计算在索引列上:如
WHERE DATE(created_at) = '2023-01-01'是慢的,应写成WHERE created_at >= '2023-01-01' AND created_at < '2023-01-02'。 - 避免隐式类型转换:
WHERE phone = 13800138000(字符串字段用数字查),会导致索引失效。
分析执行计划(EXPLAIN)
对慢SQL执行 EXPLAIN SELECT ...,重点看三列:
- type:如果出现
ALL(全表扫描),必须优化。 - key:是否命中了索引(如果为 NULL,说明没走索引)。
- rows:扫描了多少行,行数越多越慢,目标是把扫描行数降下来。
建立合适索引(核心优化手段)
- 单列索引:用于高频查询的热点字段。
- 复合索引:遵循 最左前缀原则,例如查询条件是
where user_id=? and status=?,应建(user_id, status)联合索引。 - 覆盖索引:如果查询的字段都在索引里(Extra显示:Using index),查询速度会极快,无需回表。
分页优化
-
深分页问题:
LIMIT 1000000, 20很慢。 -
优化方案:延迟关联。
-- 原写法:慢 SELECT * FROM orders ORDER BY id LIMIT 1000000, 20; -- 优化后:快(通过子查询先查出主键,再关联) SELECT o.* FROM orders o INNER JOIN (SELECT id FROM orders ORDER BY id LIMIT 1000000, 20) t ON o.id = t.id;
第三阶段:PHP 代码侧优化
如果数据库很快,但PHP响应慢,按以下顺序排查:
N+1 查询问题(最常见)
- 现象:循环查询数据库,在循环体里调用Query Builder查询,比如循环100次用户,查了100次数据库。
- 对策:使用 预加载(预载)。
- Laravel:使用
with()方法(预先加载关联关系)。 - ThinkPHP:使用
with()方法。 - 原生代码:先查用户列表,再用
IN一次性查关联数据,最后在PHP中组装。
- Laravel:使用
阻塞式 IO 调用(外部接口慢)
- 现象:PHP调用第三方API或远程接口,网络超时导致PHP进程阻塞。
- 对策:
- 设置超时时间:curl设置
CURLOPT_TIMEOUT为合理的秒数(如3秒)。 - 异步化:如果不需要立即返回结果,用消息队列(RabbitMQ/Kafka)异步处理。
- 并发请求:如果有多个不相关的HTTP请求或多个Redis查询,可以用
Swoole或curl_multi_exec并发发出,而不是串行等待。
- 设置超时时间:curl设置
循环里的复杂逻辑
- 避免在循环内做缓存读写、计算MD5、正则匹配等耗时操作。
- 提前把循环内需要的缓存数据一次性取出来(如批量获取用户信息,而不是每条取一次)。
内存溢出与GC
- 大数据量循环处理时,排查是否大量使用静态数组导致内存暴涨,进而影响GC性能。
第四阶段:缓存策略(降低数据库压力的“大招”)
很多慢查询是因为QPS太高,数据库“忙不过来”。
- Redis 缓存:把热点数据(如商品详情、用户信息)缓存到Redis。
- 更新策略:Cache Aside Pattern(先更新数据库,再删缓存)。
- 本地缓存(APCu / Memcached):应对进程内高频读取的基础数据。
第五阶段:基础设施与架构调整(终极方案)
如果上述都做了还是慢,可能是硬件或架构问题:
- 读写分离:主库负责写,从库负责读,分散压力。
- 分库分表:单表数据量超千万行时,索引再优化也慢,需要水平拆分。
- 硬件升级:更换SSD硬盘(NVMe)、增加内存(让MySQL的InnoDB Buffer Pool足够大,热数据全在内存)——这是最直接有效但成本最高的手段。
给你的执行清单
如果现在就要开始干,请按这个顺序做:
- 开启慢查询日志,抓出最慢的20条SQL。
- 用
EXPLAIN分析,查看type和rows,为慢SQL 建立/调整索引。 - 检查应用代码,搜索循环中的 SQL 查询,改成
IN批量查询或使用框架的预加载。 - 为热点数据加 Redis 缓存(比如首页数据、分类数据)。
- 重复测试,对比优化前后的
Query Time耗时。
提示:优化时,不要靠猜,先看监控数据,如果数据库CPU占用率很低,但PHP CPU飙高,那问题在代码;如果数据库CPU 100%,那基本就是SQL或索引的问题。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播