滚动新闻 →
AI 集群中的 Kubernetes 节点应该怎样规划,计算节点、管理节点和存储节点如何分工 Google Cloud FinOps 应该怎么做,开发团队如何参与云成本管理 西班牙高铁相撞致68人遇难 调度中心指令引事故 联准会维持利率不变 通胀高企背景下暂不加息 最高法院裁定拜登移民政策失效 川普称法国骚乱根源在移民 一个 SQL 查询越来越慢,PHP SaaS 开发者应该从哪些地方开始排查 U盘频繁掉线,接口接触不良和存储芯片故障如何区分 中共退休高官背后的隐形账单 巴西前总统之子赴美寻求国际支持,称将领导海外反对派 23岁网红奇迹夏夏病逝 临终遗言引关注 古巴外交官在联合国失态引发驱逐风波 一党专制为何害怕公开质问 巴方临时解除台湾航班领空禁令 涉事航司恢复跨亚欧航线 Windows 11 锁屏密码和账户密码有什么区别?一次理解登录机制 MySQL 导入大型 SQL 文件时速度太慢,可以从哪些方面解决 拜登政府为何放宽移民案件搁置规则 川普政府收紧遣返程序触及美国边境治理之争 美国最高法院驳回移民法官暂停驱逐案请求 美国防长提议与东盟共建防务合作框架 强化印太战略威慑 高校联署促废除终身制引权斗 17校公开信遭删除 中国古典文学为什么如此重视自然 岭南地区的传统养生饮食文化 安徽女子赴泰旅游后失踪 账户50万元遭电诈集团转走 以色列大选进入白热化 内塔尼亚胡称将与川普联手对抗伊朗威胁 以色列大选进入白热化 两党竞争激烈 投票率创历史纪录 巴西博索纳罗拒交护照赴美 川普表态支持 文房四宝为什么成为中国文化符号 孩子犯错后总找借口怎么办 塞尔维亚首都以色列大使馆遭袭击 枪手被警方击毙 相机视频文件应该如何分类 清朝前期为什么能够迅速扩张 姆巴佩代言饮料头像遭遮盖 二手平台炒至120元/瓶 家具摆放应该优先考虑美观还是使用方便 北方火锅为什么重视肉类和汤底 沃尔玛节庆餐篮人均成本降至3.5美元,商品清单含26种食材 夜幕降临以后,加拿大蒙特朗布朗的山村开始亮起温暖灯火 汽车氧传感器故障有哪些表现 浙江一公园景观墙钢板脱落 1岁幼童被砸中身亡 上海小区幼童裸体跑动引热议,网友联想到云南‘野孩子’事件 刘延东去世与胡春华缺席引发猜测 中共团派政治影响力再受关注 什么是现代汽车中360度全景影像 加拿大独立屋和公寓的长期持有成本有什么区别

一个 SQL 查询越来越慢,PHP SaaS 开发者应该从哪些地方开始排查

发布时间: 2026-10-28 21:00:01    最后更新: 2026-10-28 22:29:39    阅读:6  约12 分钟阅读     

一个 SQL 查询越来越慢,PHP SaaS 开发者应该从哪些地方开始排查
图片说明:示意图   图片来源:Public Domain(公有领域)
PHP SaaS系统运行几年以后,经常会出现一种很典型的问题:原来几十毫秒完成的SQL查询,现在突然变成几百毫秒,甚至几秒。很多开发者第一反应是“加一个索引”,但SQL变慢并不一定是索引消失了。数据量增长、执行计划变化、统计信息过期、锁等待、磁盘I/O、返回数据量增加,甚至PHP应用本身重复执行SQL,都可能成为原因。

因此,排查SQL性能最重要的原则不是立即修改数据库,而是先回答一个问题:到底是什么发生了变化。

第一步应该保存当前SQL、参数、执行时间以及执行计划,然后与过去正常运行时进行比较。MySQL的EXPLAIN可以查看优化器选择的执行计划,而MySQL 8.4还支持EXPLAIN ANALYZE,可以实际执行查询并提供各个迭代器的执行时间、实际返回行数和循环次数。这样可以判断优化器估计的数据量与真实数据量之间是否出现明显偏差。

例如,一个SaaS订单查询原来只扫描几十行,现在EXPLAIN ANALYZE发现某个JOIN实际扫描了数百万行,那么真正的问题可能是索引、统计信息或者查询条件,而不是PHP代码本身。

接下来检查索引,但不要简单地把“有没有索引”作为判断标准。真正需要分析的是索引是否匹配查询模式。

例如查询经常是:

SELECT id, status, created_at
FROM orders
WHERE tenant_id = ?
AND status = ?
ORDER BY created_at DESC
LIMIT 50;

那么索引设计就应该围绕实际的过滤条件和排序方式考虑,而不是机械规定“tenant_id必须第一”或者“时间字段必须第一”。如果另一类查询主要按照时间范围扫描所有租户,那么索引顺序又可能完全不同。

JOIN同样如此。需要确认JOIN两侧参与连接的字段是否有合适索引,并观察实际执行计划中的访问方式。不能因为看到JOIN就认为一定需要改成子查询,也不能认为所有子查询都应该改写成JOIN。MySQL优化器本身会对很多查询进行转换,最终还是应该通过执行计划和实际测试判断。

如果查询运行一段时间以后突然变慢,还应该考虑统计信息。数据分布发生变化以后,优化器对基数的估计可能不再准确。MySQL官方也建议在怀疑索引选择异常时检查并更新表统计信息,例如通过ANALYZE TABLE更新相关统计数据。

第二个经常被忽视的问题是数据量本身。

一条SQL在100万行的表上运行正常,并不意味着它在5000万行甚至数亿行的数据上仍然会保持相同速度。尤其是SaaS系统,一个租户可能只有几千条数据,但整个表已经积累了数千万甚至数亿条记录。

这时候应该区分“全表数据量”和“单次查询需要处理的数据量”。如果每次查询都能够通过tenant_id、状态、时间范围等条件快速缩小扫描范围,那么大表本身未必意味着查询一定慢。如果查询需要扫描大量历史数据,再好的PHP代码也无法消除数据库需要处理的数据量。

分页也是SaaS系统中非常常见的性能陷阱。LIMIT 50 OFFSET 500000意味着数据库可能需要先找到并跳过大量记录,然后才能返回最后50行。对于大型数据集,可以根据实际排序规则考虑keyset pagination,例如利用created_at和主键组成稳定的分页条件。

第三个问题是锁和事务。

有些SQL看起来只执行100毫秒,但用户仍然感觉系统卡顿,因为真正等待的时间发生在锁上。特别是在订单、库存、余额、任务队列等高并发业务中,应该区分SQL执行时间和锁等待时间。

SELECT ... FOR UPDATE并不是应该一律避免的技术。它在需要锁定即将修改的数据时非常有用。真正需要排查的是事务范围是否过大、事务持续时间是否过长、多个业务流程是否以不同顺序获取锁,以及是否存在死锁和锁等待。

例如PHP代码可能先查询订单,再调用外部API,最后才提交数据库事务。如果外部API响应需要两秒,那么数据库锁可能也被保持两秒。此时即使SQL本身只执行几十毫秒,整个系统仍然会出现明显的并发等待。

因此,排查数据库慢查询时应该同时观察事务持续时间、锁等待和死锁,而不是只看SQL执行时间。

第四个问题是PHP应用本身。

PHP SaaS中很常见的一类问题是N+1查询。例如页面需要显示100条订单,代码先查询100条订单,然后循环执行100次用户查询、100次商品查询,最终一个页面可能产生数百甚至上千次SQL。

这时候给每一条SQL分别增加索引,并不能解决根本问题。应该首先确认一个HTTP请求到底执行了多少条SQL,每条SQL分别花费多少时间,以及是否存在重复查询。

PHP的PDO和mysqli都支持持久连接,但持久连接不等同于一个可以随意配置大小的传统应用连接池。PHP的连接复用还受到PHP进程、Web服务器部署方式以及连接参数的影响。PDO官方文档也特别提醒,持久连接可能保留事务、锁和临时表等连接状态,因此使用时必须做好连接状态管理。

所以不要简单把max_connections当成“连接池配置”。max_connections首先是MySQL服务器允许的最大并发连接数。如果把它盲目调得非常高,反而可能因为内存和CPU竞争让数据库整体变慢。

第五个问题是返回的数据。

很多慢查询并不是WHERE条件没有索引,而是一次返回了大量无用数据。例如后台列表只需要十几个字段,却使用SELECT *读取一个包含TEXT、JSON甚至BLOB字段的大表。

对于SaaS列表页面,应该尽量只读取页面真正需要的字段。大字段如果只是详情页面才需要,可以在用户打开详情时再查询。

这并不意味着所有TEXT、BLOB都应该搬到另一张表。是否拆分,应该根据访问频率、数据大小、I/O压力和业务模型判断。真正需要解决的是查询是否每次都把大量不必要的数据从磁盘读取并传输到PHP。

第六个问题是数据库服务器资源。

如果EXPLAIN显示执行计划没有明显问题,但查询仍然越来越慢,就需要观察CPU、内存、Buffer Pool、磁盘I/O、临时表以及锁等待。

例如CPU长期接近满载,可能意味着查询计算、排序、聚合或者并发过高;磁盘I/O持续很高,则可能是大量数据扫描、Buffer Pool不足或者存储性能达到瓶颈。

如果数据库等待磁盘,而PHP应用又有大量并发请求,那么单纯增加PHP服务器数量可能适得其反,因为更多PHP进程只会同时向数据库发送更多查询。

第七个问题是慢查询日志和Performance Schema。

慢查询日志适合寻找真正运行缓慢或者频繁出现的SQL,而Performance Schema则能够从数据库服务器内部观察语句执行、等待、I/O和锁等运行事件。MySQL官方明确指出,Performance Schema用于运行时性能监控,而Information Schema主要用于元数据检查,两者用途并不相同。sys schema则提供了基于Performance Schema等数据的更方便查询方式。

因此,“分析information_schema”不应该作为数据库性能诊断的主要表述。更准确的组合应该是慢查询日志、Performance Schema、sys schema、EXPLAIN/EXPLAIN ANALYZE以及操作系统和云平台监控。

Percona Toolkit等工具也可以作为辅助工具,但工具本身不是解决方案。先找到慢在哪里,再决定使用什么工具。

第八个问题是缓存。

如果某个查询被大量重复执行,而且数据变化频率很低,那么Redis等缓存层可能有效。例如SaaS系统中的配置、权限、字典数据和部分热点统计信息,都可能适合缓存。

但缓存不是SQL优化的替代品。如果一个查询本身有严重的全表扫描问题,直接加缓存可能只是把问题暂时隐藏起来。缓存还会引入失效、一致性、热Key和内存管理等新的问题。

第九个问题才是架构调整。

当单个数据库实例经过SQL优化、索引优化、事务优化和硬件调整以后仍然无法满足需求,才应该考虑读写分离、专用数据库、数据归档、汇总表或者分片。

分区表也不能简单理解成“大表解决方案”。MySQL分区适合特定的数据访问和维护模式,例如按照时间组织的数据,并能够在合适的查询条件下利用partition pruning。它并不会自动让所有查询变快。

同样,MySQL没有传统意义上的自动物化视图机制。如果报表查询需要反复扫描数亿条交易记录,更现实的办法可能是建立汇总表,并通过定时任务、增量计算或数据管道维护这些统计数据。

对于历史数据,则应该首先考虑业务上是否真的需要在线查询全部历史记录。把几年前的数据全部留在交易主表里,然后每个列表查询都从头扫描,往往比单纯增加服务器配置更加值得解决。

对于SaaS系统,还必须把租户隔离放在性能问题之前。

正常的租户业务查询通常应该明确限定租户范围,例如:

SELECT ...
FROM orders
WHERE tenant_id = ?
AND ...

但不能把“所有SQL必须包含tenant_id”理解成绝对规则,因为平台管理员、跨租户报表、后台运维任务等场景可能本身就是跨租户查询。真正重要的是访问边界必须明确,并且性能设计要与授权模型一致。

同时,大租户和小租户的数据量可能完全不同。一个超级租户可能拥有整个系统大部分数据,因此同一条SQL在普通租户和超级租户上的执行时间可能完全不同。这种数据倾斜在SaaS系统中非常常见,也应该纳入性能测试。

因此,一个越来越慢的SQL,比较合理的排查顺序是:先确认实际变慢了多少,再比较执行计划;然后检查实际扫描行数、索引和统计信息;接着排查锁等待、事务、PHP重复查询和返回数据量;随后观察CPU、内存、Buffer Pool和磁盘I/O;最后才考虑缓存、读写分离、汇总表、归档或者分片。

最重要的是建立性能基线。每次数据库版本、表结构、索引、SQL、PHP代码或者数据规模发生重大变化以后,都应该重新观察P95/P99延迟、执行次数、扫描行数和资源消耗。MySQL官方也强调,性能优化必须结合实际工作负载进行测试,因为某项优化在一个环境中有效,并不意味着在另一个环境中同样有效。

对于PHP SaaS开发者来说,真正成熟的SQL优化并不是“发现慢了就加索引”,而是建立一条完整的诊断链:PHP请求到底执行了什么SQL,SQL实际扫描了多少数据,优化器为什么选择这个执行计划,数据库究竟在计算、等待锁还是等待磁盘,最终再判断应该修改SQL、索引、事务、代码还是整个架构。只有把这条链跑通,数据库性能问题才会从“感觉越来越慢”变成可以测量、可以定位、也可以验证的工程问题。

喜欢这篇报道?

使用下面的功能,方便以后继续阅读和分享 MNewsTV

设为 Google 新闻首选来源 让 Google 新闻优先显示 MNewsTV 的最新报道 ›
★ 我的收藏 查看已经收藏的文章
关于文章收藏 收藏不需要注册帐号,收藏信息仅保存在当前浏览器中。 删除收藏请进入「我的收藏」进行管理。
分享这篇报道

捐助(Paypal): https://www.paypal.me/observeccp
订阅中国观察电报 Telegram : https://t.me/s/ObserveCCP