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

获取各区域含当前活动城市数量时脚本崩溃求助

问题诊断与解决方案

首先,你的脚本崩溃主要源于两个核心问题:逻辑错误导致的无效计算,叠加N+1查询引发的性能爆炸,最终触发了"Request too heavy"的告警。

1. 先修正核心逻辑错误

你原本的需求是统计「每个区域中存在当前活动的城市数量」,但现有代码的第二个循环里,每次遍历城市都会覆盖$regionCitiesNumber[$index]的值,最后这个变量只会保留该区域最后一个城市的活动数量,完全不符合你的需求。正确逻辑应该是:遍历每个城市,判断它是否有当前活动(活动数>0),如果有就给区域计数+1。

2. 解决性能爆炸问题(N+1查询)

你的代码在第一个循环加载所有区域的城市后,第二个循环里每个城市调用getCurrentEvents()时,Doctrine会懒加载每个城市的events关联,再用matching()过滤——这意味着每个城市都会发起至少一次额外的数据库查询。如果区域和城市数量较多,几百上千次查询会瞬间拉满请求负载,导致脚本崩溃。

优化方案一:预加载符合条件的关联数据,减少查询次数

可以在获取城市的时候,直接通过Doctrine的QueryBuilder预加载仅当前活动的events,避免后续的懒加载查询。修改CityRepository中的方法:

// 在CityRepository中
public function getCitiesByRegionWithCurrentEvents(Region $region): array
{
    $currentDate = new DateTime('00:00:00');
    return $this->createQueryBuilder('c')
        ->select('c, e') // 预加载符合条件的events
        ->leftJoin('c.events', 'e', 'WITH', 'e.endDate >= :currentDate')
        ->where('c.region = :region')
        ->setParameters([
            'region' => $region,
            'currentDate' => $currentDate,
        ])
        ->getQuery()
        ->getResult();
}

然后修改业务代码:

$regionCities = [];
foreach ($regions as $region) {
    // 使用新的预加载方法
    $regionCities[$region->getId()] = $cityRepository->getCitiesByRegionWithCurrentEvents($region);
}

$regionCitiesNumber = [];
foreach ($regionCities as $regionId => $cities) {
    $count = 0;
    foreach ($cities as $city) {
        // 直接用已预加载的集合判断,不会触发额外查询
        if (!$city->getCurrentEvents()->isEmpty()) {
            $count++;
        }
    }
    $regionCitiesNumber[$regionId] = $count;
}

优化方案二:直接在数据库层面完成统计(最优解)

比起加载所有实体再计算,直接让数据库统计数量效率高得多,完全避免了加载大量实体带来的内存和性能开销。可以在RegionRepository中写一个专门的统计方法:

// 在RegionRepository中
public function getRegionActiveCityCount(): array
{
    $currentDate = new DateTime('00:00:00');
    $result = $this->createQueryBuilder('r')
        ->select('r.id as regionId, COUNT(DISTINCT c.id) as activeCityCount')
        ->leftJoin('r.cities', 'c')
        ->leftJoin('c.events', 'e', 'WITH', 'e.endDate >= :currentDate')
        ->where('e.id IS NOT NULL') // 只统计有当前活动的城市
        ->groupBy('r.id')
        ->setParameter('currentDate', $currentDate)
        ->getQuery()
        ->getResult();

    // 转换成键值对格式方便使用
    $counts = [];
    foreach ($result as $item) {
        $counts[$item['regionId']] = $item['activeCityCount'];
    }
    // 给没有活动城市的区域补0
    foreach ($this->findAll() as $region) {
        if (!isset($counts[$region->getId()])) {
            $counts[$region->getId()] = 0;
        }
    }
    return $counts;
}

使用时直接调用即可,一行代码搞定:

$regionCitiesNumber = $regionRepository->getRegionActiveCityCount();

这个方案只需要一次数据库查询,性能最优,完全不会出现"Request too heavy"的问题。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.08 16:42:55