慢查询非 FrankenPHP 问题,而是其常驻进程暴露了本被重启掩盖的低效 SQL;需用 EXPLAIN 和 slow_query_log 定位并优化,隔离慢端点,规避 ORM 滥用,强化连接与资源管理。

慢查询不是 FrankenPHP 的问题,而是它暴露得更早、更真实——常驻进程让反复执行的低效 SQL 无法被“重启”掩盖。要避免它拖垮吞吐量,核心是把数据库瓶颈从“等它崩”变成“提前掐断”。
用 EXPLAIN + slow_query_log 定位真实慢查询
FrankenPHP 不会隐藏慢查询,反而会让它们在高并发下更快挤爆连接池。别只看 Laravel 的 DB::listen 或 Symfony 的 profiler,那些只是应用层日志;必须打开 MySQL 的 slow_query_log 并设低阈值(比如 long_query_time = 0.2),再配合 EXPLAIN 看执行计划。
-
type是ALL或index?说明没走有效索引 -
rows明显大于实际返回数?可能是索引失效或统计信息过期 - 出现
Using temporary或Using filesort?ORDER BY 或 GROUP BY 没命中索引 - WHERE 条件里写了
YEAR(created_at) = 2023?函数包裹字段直接让索引失效
在 FrankenPHP 中隔离慢端点,防止线程阻塞
FrankenPHP 默认按 CPU 核心数 × 2 启动 PHP 线程(8 核 → 16 线程),但一个慢查询可能卡住整个线程,导致后续请求排队。不能靠“加线程”硬扛,得做流量分层。
- 把已知耗时的操作(如报表导出、搜索聚合)单独路由到
worker.php,用worker { num 2 }隔离线程池,避免污染主请求链路 - Caddyfile 中用
handle /api/slow/*匹配路径,配合reverse_proxy转发到专用 worker 实例,和主服务内存/线程资源解耦 - 对未识别的慢端点,启用
max_threads作为安全阀:设为当前num_threads的 1.5 倍,让突发流量有缓冲,但必须配监控观察扩容频率——频繁触发说明慢查询已成常态
PHP 层主动规避,而不是等数据库扛不住
FrankenPHP 的常驻特性放大了 ORM 使用不当的代价。N+1、SELECT *、循环查库这些在 PHP-FPM 下可能“凑合能跑”,在 FrankenPHP 下会快速累积内存与连接压力。
立即学习“PHP免费学习笔记(深入)”;
- 禁用 Eloquent 的懒加载(
App::set('config.database.disable_lazy_loading', true)),强制开发者显式写with() - 所有列表接口必须带分页,
paginate(20)而非get();大表关联用cursorPaginate()避免OFFSET性能坍塌 - 高频读场景(如配置、字典)加 Redis 缓存,用
Cache::remember('config:site', 3600, fn() => Config::get('site')),缓存键带版本号便于批量失效 - 连接层启用持久化(
PDO::ATTR_PERSISTENT => true),但需确认 MySQL 服务端wait_timeout≥ 应用空闲时间,否则会报MySQL server has gone away
最易被忽略的一点:FrankenPHP 的线程复用会让慢查询的副作用持续存在——比如一个未关闭的事务锁住行,或一个没释放的大对象长期占内存。不要只盯着 SQL 执行时间,还要检查事务边界是否清晰、资源是否及时释放。线程不重启,bug 就不重置。



















