Ubuntu 上的 PHP 应用一旦出现慢查询,问题通常不会只停留在数据库层:接口响应会变慢,PHP-FPM 进程可能被拖住,最终体现在用户端就是页面卡顿、请求超时甚至整体服务抖动。相比等线上告警或用户投诉再回头补救,更稳妥的做法是按“先发现、再定位、后优化、持续复盘”的顺序,把数据库查询这条链路系统梳理一遍。
下面这份清单保留了原文里的关键命令和示例,同时按实际排查流程重新组织。你可以据此判断:当前瓶颈到底出在日志采集、SQL 执行计划、索引设计,还是 PHP 代码与缓存策略上;遇到千万级数据表时,也能快速知道下一步该往哪种方向处理。
先把慢查询日志开起来
优化查询前,先确保 MySQL 会把慢查询记录下来。否则你只能凭感觉猜,效率很低。在 MySQL 配置文件 my.cnf 或 my.ini 中加入以下配置:
[mysqld]
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow-query.log
long_query_time = 2
这里的 long_query_time = 2 表示执行时间超过 2 秒的查询都会被写入日志。改完后别忘了重启 MySQL 服务,让配置生效:
sudo systemctl restart mysql
这一步的价值很直接:先把“哪些 SQL 慢”从模糊感受变成可追踪的数据,后续所有分析才有依据。
用 EXPLAIN 和索引定位真正瓶颈
先看执行计划,而不是直接猜问题
拿到慢 SQL 后,不要急着改代码,先用 EXPLAIN 看执行计划。它能告诉你 MySQL 是如何执行这条语句的,比如是否命中索引、预估扫描多少行、是否发生全表扫描。

EXPLAIN SELECT * FROM your_table WHERE your_column = 'value';
如果结果里的 type 是 ALL,通常就意味着在做全表扫描,这往往是最常见的性能瓶颈之一。
该建的索引要建,但别盲目堆索引
查询慢,很多时候不是 SQL 本身复杂,而是关键列没有索引。对于经常出现在 WHERE、JOIN、ORDER BY 后面的字段,可以考虑建立索引:
CREATE INDEX idx_your_column ON your_table(your_column);
不过索引并不是越多越好。索引会带来额外的维护成本,尤其是写操作频繁的字段,加得过多会拖慢插入和更新。因此索引策略的重点不是“多”,而是“命中高频查询条件”。
先改 SQL 写法,再看 PHP 访问方式
避免滥用 SELECT *
SELECT * 是最常见、也最容易被忽略的性能问题之一。它会把整行所有字段都取出来;一旦表字段很多,网络传输、内存占用和 MySQL 读取成本都会一起上升。

更合理的做法是只取业务真正要用到的列:
SELECT your_column1, your_column2 FROM your_table WHERE your_column = 'value';
如果一张表有几十个字段,这种改法带来的收益通常非常直接,尤其适合接口列表页、后台管理页这类高频查询场景。
PHP 中尽量使用预处理语句
在 PHP 侧,预处理语句不仅是安全实践,也有性能收益。MySQL 可以重复利用执行计划,减少 SQL 反复解析带来的开销。
$stmt = $pdo->prepare('SELECT * FROM your_table WHERE your_column = :value');
$stmt->execute([':value' => 'your_value']);
$results = $stmt->fetchAll();
对于同类查询被反复执行的业务接口,这种写法通常比字符串拼接更稳定,也更方便后续排查参数与执行行为。
高并发下要重视连接复用
如果每个请求都新建数据库连接、执行查询、再立即断开,连接建立和释放本身就会形成不小的开销。高并发场景下,这部分损耗会被进一步放大。
可选方案包括使用 PHP 的 pdo_mysql 配合持久连接,或采用更专业的方案,例如 php-pm、Swoole。核心目标都是复用连接、降低重复握手成本,从而减少请求延迟。
缓存、维护与监控要一起做
把热点数据挡在数据库前面
对变更不频繁的数据,例如用户配置、分类列表、基础字典信息,可以考虑放进 Redis 或 Memcached。这样同一份数据可以“一次查询,多次使用”,数据库压力会明显下降。
这类优化尤其适合读多写少的业务。真正需要数据库实时计算的请求变少后,慢查询的暴露概率通常也会随之降低。
定期整理数据表碎片
数据库长时间运行后,表碎片增加、索引效率下降并不罕见。对合适的表定期执行 OPTIMIZE TABLE,可以帮助整理碎片、重建索引:
OPTIMIZE TABLE your_table;
这不是所有性能问题的解药,但在数据频繁变动、删除较多的场景里,作为日常维护动作是有意义的。
日志分析要持续进行
数据库优化不是一次性动作。慢查询会随着业务变化不断出现,因此日志分析需要持续做。常见工具包括 pt-query-digest 和 mysqldumpslow,可以帮助你定期汇总最慢、最频繁、最值得优先处理的 SQL。
这一步的关键不只是“发现慢 SQL”,更是建立持续观察机制,避免旧问题解决后又被新问题顶上来。
单表太大时,再考虑分区或分表
当单表数据量达到千万级甚至亿级时,即使已有索引,查询也可能继续变慢。这时问题不再只是“某条 SQL 是否写得好”,而是数据规模本身已经逼近单表处理上限。
可考虑的方向主要有两类:
- 分区:把一张表物理上拆成多个文件,让查询命中更小的数据范围。
- 分表:按业务逻辑或数据分布拆成多张表,减少单次扫描规模。
这类方案改动面更大,通常适合在日志、执行计划、索引和缓存都做过后,仍然无法满足响应目标时再推进。
实战示例:从一条慢查询走到索引生效
假设你在 PHP 日志里发现下面这条查询执行很慢:

SELECT * FROM users WHERE email = 'example@example.com';
第一步,先用 EXPLAIN 查看执行计划:
EXPLAIN SELECT * FROM users WHERE email = 'example@example.com';
如果看到 possible_keys 是 NULL,说明当前没有可用索引。这时就可以为 email 字段建立索引:
CREATE INDEX idx_email ON users(email);
然后再次执行 EXPLAIN,确认索引已经命中。原文给出的判断结果是:rows 从全表扫描变成了 1 行,这通常意味着查询成本已经显著下降。
这个例子也说明了一点:排查慢查询时,最有效的方式往往不是一口气做很多优化,而是遵循“日志发现问题,执行计划定位问题,索引或 SQL 改写验证结果”这条最短链路。







