You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

PHP脚本响应过慢排查:显示Waiting for Cache耗时超30分钟求优化

问题根源与优化方案分析

首先可以明确:你的脚本性能瓶颈主要源于MySQL的低效查询设计,PHP层面也有可优化的点,但核心问题出在数据库的嵌套查询逻辑和缺少关键索引上。下面一步步拆解问题并给出落地优化方案:

一、为什么说是MySQL的主因?

你的代码陷入了典型的嵌套循环查询陷阱,加上缺失必要的索引,直接导致数据库查询次数呈几何级增长,进而触发"Waiting for Cache"状态——这通常是MySQL在反复执行相同/类似查询时,缓存压力过载,不得不频繁硬解析查询、等待表锁或缓存写入,最终拖垮整个响应时间。

具体来看代码里的核心问题:

  1. 多层嵌套的N+1+M查询模式

    • 外层循环遍历profile表的结果(假设返回N条记录)
    • 每个profile都单独执行一次SELECT * FROM counter WHERE ...(共N次查询)
    • 每条counter记录又单独执行一次SELECT ... FROM horraire WHERE ... GROUP BY ...(共M次查询,M是所有counter记录的总和)
      如果N=100、M=10000,总查询次数会达到1+100+10000=10101次!这种模式完全抛弃了SQL的批量处理能力,数据库根本扛不住。
  2. 缺失关键联合索引

    • profile表的查询条件date_f_contract >= '$now' AND groupe > 0没有索引,会触发全表扫描
    • counter表的查询条件from_interval >= '$sDateHour' AND to_interval <= '$eDateHour' AND id_profile='$pr_id'缺少联合索引,每次查询都要遍历整个counter表
    • horraire表的查询+分组逻辑没有覆盖索引,导致频繁回表查询和排序,进一步拖慢速度

二、PHP层面的辅助问题

虽然不是核心,但这些细节也会加剧性能恶化:

  • 重复实例化Func类:代码里创建了3个Func对象($fn, $sel, $getHrName),其实完全可以复用一个,减少连接和资源开销
  • 无结果缓存:同一时间范围的horraire查询没有缓存,重复执行相同查询
  • 内存管理:如果结果集过大,没有分批处理可能导致内存占用过高,但这是次要问题

三、具体优化方案

1. MySQL核心优化(必须优先做)

(1)添加必要的联合索引

执行以下SQL创建索引,直接降低查询的IO开销:

-- profile表:优化date_f_contract和groupe的组合查询
CREATE INDEX idx_profile_date_groupe ON profile(date_f_contract, groupe);

-- counter表:优化按profile+时间范围的查询
CREATE INDEX idx_counter_profile_interval ON counter(id_profile, from_interval, to_interval);

-- horraire表:优化按profile+时间范围的查询,同时覆盖libelle字段避免回表
CREATE INDEX idx_horraire_profile_datetime_libelle ON horraire(id_profile, date_heure_deb, date_heure_fin, libelle);

(2)重构查询逻辑,用批量JOIN替代嵌套循环

把多层嵌套的查询改成3次批量查询,然后在PHP里组装结果,彻底减少查询次数:

  • 第一步:批量获取符合条件的profile
    SELECT id_profile, groupe FROM profile WHERE date_f_contract >= ? AND groupe > 0
    
  • 第二步:批量获取所有对应的counter记录
    SELECT * FROM counter 
    WHERE id_profile IN (?, ?, ...) -- 填入第一步获取的所有id_profile
      AND from_interval >= ? 
      AND to_interval <= ?
    
  • 第三步:批量获取horraire的统计数据
    SELECT 
      h.id_profile,
      c.from_interval,
      c.to_interval,
      h.libelle AS lbl,
      COUNT(h.libelle) AS totalHr
    FROM horraire h
    JOIN counter c ON h.id_profile = c.id_profile 
      AND h.date_heure_deb >= c.from_interval 
      AND h.date_heure_fin <= c.to_interval
    WHERE c.id_profile IN (?, ?, ...) -- 同样填入第一步的id_profile列表
      AND c.from_interval >= ? 
      AND c.to_interval <= ?
    GROUP BY h.id_profile, c.from_interval, c.to_interval, h.libelle
    ORDER BY totalHr DESC
    

这样把上万次查询压缩到3次,性能会有质的飞跃。

2. PHP辅助优化

  • 复用数据库连接:只实例化一个Func对象,减少重复创建连接的开销:
    $db = new Func;
    // 所有查询都通过$db->select()执行
    
  • 添加缓存机制:如果查询的时间范围(sDateHour/eDateHour)不频繁变化,可以把最终的JSON结果缓存到文件或Redis,避免重复查询:
    $cacheKey = 'full_view_tasks_' . $sDateHour . '_' . $eDateHour;
    $cachedResult = file_get_contents("/tmp/{$cacheKey}.json");
    if ($cachedResult) {
        echo $cachedResult;
        exit;
    }
    // 执行查询逻辑...
    // 保存缓存
    file_put_contents("/tmp/{$cacheKey}.json", json_encode($temp, JSON_PRETTY_PRINT));
    
  • 添加错误处理:捕获数据库查询错误,避免静默失败导致的无限等待:
    $result = $db->select(...);
    if (!$result) {
        error_log(mysqli_error($db->conn)); // 假设Func类暴露了数据库连接属性conn
        echo json_encode(['error' => 'Database query failed']);
        exit;
    }
    

四、优化效果验证

优化后,先查看MySQL的慢查询日志,确认单条查询时间是否大幅降低;然后测试接口响应,正常情况下应该从30分钟降到几秒甚至毫秒级。

内容的提问来源于stack exchange,提问作者DuliNini

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 08:52:39