【问题标题】:Can you optimize this PHP loop query code?你能优化这个 PHP 循环查询代码吗?
【发布时间】:2019-11-05 07:08:44
【问题描述】:

我的 Laravel 网站出现了一些性能问题。 我能够发现原因是我的代码的一部分。 我在一个循环中进行查询,正如我刚刚发现的那样,不应该这样做。 我不确定如何以最佳方式优化此代码(无需更改数据库,因为在项目的这个阶段这并不容易)。

我正在考虑使用 foreach $transactionInfos 至少有点像这样改进部分,但我现在不确定 Laravel 语法:

$transactionInfos = ToolTransactionInfo::where('tool_id', $tool->id)
                    ->where('date', $date)
                    ->only(['users'])
                    //->get();
                    ->all();

if(!empty($transactionInfos)) {
                    $users = array_sum($transactionInfos);
                }
                else{
                    $users = 0;
                }

实际代码:

        foreach ($data['tools'] as $tool) {
            // 30 day user graph data
            $count = 30;
            $tool_users_30d[$tool->id] = [];
            while ($count > 0) {
                $date = Carbon::now()->subDays($count)->format('Y-m-d');

                $transactionInfos = ToolTransactionInfo::where('tool_id', $tool->id)
                    ->where('date', $date)
                    ->get();

                $users = 0;
                foreach ($transactionInfos as $transactionInfo) {
                    $users += $transactionInfo->users;
                } 

                array_push($tool_users_30d[$tool->id], $users);
                $count--;
            }
        }
        $data['tool_users_30d'] = $tool_users_30d;

我希望得到一个包含所有工具列表的数组,其中包含每个 30 天用户数据的数组,例如:

  • [13][0] = 20
  • [13][1] = 6
  • [13][2] = 24
  • ..
  • [13][29] = 10

  • ..

  • [18][0] = 50

  • [18][1] = 11
  • [18][2] = 55
  • ..
  • [18][29] = 6

https://i.imgur.com/VDyZ9uN.png (示例图像显示 0 个用户,因为本地数据库为空,但它应该是这样的)。

【问题讨论】:

  • 向我们展示生成的 SQL,以便我们从那方面解决问题。
  • 我在创建统计数据时遇到了类似的问题。我通过缓存结合生成的命令和调度来解决它。你知道过去的数据不会改变。如果当前日期发生变化,请在 multidim 数组中的特定位置调整缓存数据并重新保存缓存。这可以通过结合队列创建事件来完成。在午夜运行 30 天计划。如果缓存(d数组)太大,你可以将它保存为文件。

标签: php mysql laravel optimization query-optimization


【解决方案1】:

您可以将整个算法简化为单个 sql 查询,像这样(没有运行它,它至少需要一些引号......),您只需要一次通过结果来构造目标多维数组.

SELECT tool_id, 30 - datediff(now(), date) as "offset", sum(users)
FROM transaction_info
WHERE date >= (date(now()) - interval 30 days) AND date < date(now())
GROUP BY tool_id, 30 - datediff(now(), date)

【讨论】:

  • 将了解如何在 laravel 中进行该查询,并在可行时提供反馈。
  • SQL 查询有效并且似乎是正确的方法,我会在得到它时编写 laravel 代码。
  • 好的,顺便说一句,带有“30 - datediff(now(), date)”的部分,只是为了完美地适应你的偏移量,但我认为如果你有这个可能会更好“按日期选择日期组”,并计算php端的偏移量(今天前几天)。所以它可能会更好地提高性能,毕竟它可能更容易适应 laravel 包装器......
  • 是的,我认为我使用的只是日期。查询后只需检查如何根据需要制作数组,将很快发布以供审核。
【解决方案2】:

您可以采取一些措施来帮助加快此过程以提高性能,并减少对数据库/函数调用的命中。

首先,您可以在循环之前一次提取所有ToolTransactionInfo。这将帮助您只进行一次数据库拉取。所以,在你的循环之上:

$transactionInfosTotal = ToolTransactionInfo::get();

这将为您提供所需的所有信息。然后,在您的循环中,您可以执行相同的查询,但在内存中已经存在的集合上执行此操作(即不要返回数据库)。

 $transactionInfos = $transactionInfosTotal->where('tool_id', $tool->id)  
                ->where('date', $date)
                ->get();

有了这个,您可以在循环中获得相同的集合,用于正确的日期和工具 - 然后您可以执行您的计算或根据需要移动到一个数组。这只是节省了数据库命中/使其更高效、更快。

您可以做的另一件事来加快一点速度是调用时间(碳)一次。不是很大的节省,但它可能会更快一点:。在循环之前,将 'now' 设置为变量:

$now = Carbon(now);

然后使用存储在循环内变量中的值来计算时间,因为它不必返回到现在获取的方法,它已经在内存中:

$date = $now->subDays($count)->format('Y-m-d');

HTH

【讨论】:

  • 感谢您的建议。不幸的是,此时我收到以下错误 "where('tool_id', $tool->id)" 不应静态调用非静态方法 Illuminate\Support\Collection::where()。
  • 哎呀 - 抱歉 - 让它成为非静态的。 $transactionInfosTotal-&gt;where('tool_id', $tool-&gt;id)。我将编辑我的答案以修复。
  • 它不需要 ->get();在 $transactionInfos。然后 $now = Carbon(now);不起作用,因为它会通过执行 $now->subDays($count) 来替换现在,所以我一直在替换替换,而不是再次从碳中获取现在来重置它。
  • 不幸的是,这使它变得更慢,因为它必须在一个查询中加载 150k 数据。另一个是 50 个数据库查询,但每个只选择 30 个数据。 30 秒对 5 秒。
  • 是的,我明白你的意思了。有了这么多数据,您将需要调整查询以获取切片。仍然建议您不要在循环中执行此操作,而是进行预查询并提前进行某种程度的过滤。 30 秒对于查询来说太长了——即使是那个量。使用 $now 时,使用第二个临时变量 - 这仍然是正确的方法,而不是每次都调用。
【解决方案3】:

slepic 的查询是要走的路。它将总页面加载时间从 3.8 秒提高到 2.5 秒(大量数据页面,但仍有一些改进要做),但这是我可以改进的 2 个最重的功能之一。谢谢 slepic 和所有其他建议。

这是我的解决方案:

        // 30 day user graph data
        // Prepare array list
        $loaded_tool_ids = [];
        foreach ($data['tools'] as $tool) {
            array_push($loaded_tool_ids, $tool->id);
        }
        $count = 30;
        $now = Carbon::now()->format('Y-m-d');
        $oldest_date = Carbon::now()->subDays($count)->format('Y-m-d');
        // Query tool history data
        $transactionInfos = ToolTransactionInfo::select('tool_id','date',DB::raw("SUM(users) as users"))
            ->whereIn('tool_id', $loaded_tool_ids)
            ->where('date', '>=', $oldest_date)
            ->where('date', '<', $now)
            ->groupBy('tool_id')
            ->groupBy('date')
            ->orderBy('tool_id', 'asc')
            ->orderBy('date', 'asc')
            ->get()
            ->toArray();

        // Fill output array with query data
        foreach($transactionInfos as $transactionInfo) {
            // Get date number
            $date_number = $count - Carbon::parse($transactionInfo['date'])->diffInDays($now);
            // Fill array
            if(!empty($tool_users_30d[$transactionInfo['tool_id']])){
                // Fill previous dates with 0 data
                $tool_users_30d[$transactionInfo['tool_id']][$date_number] = (int) $transactionInfo['users'];
            }
            else{
                // Create array with the tool id
                $tool_users_30d[$transactionInfo['tool_id']] = [];
                // Fill date
                $tool_users_30d[$transactionInfo['tool_id']][$date_number] = (int) $transactionInfo['users'];
            }
        }

        // Fill output array with loaded tools that have empty days
        foreach($loaded_tool_ids as $loaded_tool_id){
            if(empty($tool_users_30d[$loaded_tool_id])){
                // Create array with the tool id
                $tool_users_30d[$loaded_tool_id] = [];
            }
        }

        // Fill days with 0 data
        foreach($tool_users_30d as $key => $tool_users){
            while($count > 0){
                if(empty($tool_users[$count-1])){
                    $tool_users_30d[$key][$count-1] = 0;
                }
                $count--;
            }
            $count = 30;
            ksort($tool_users_30d[$key]);
        }
        $data['tool_users_30d'] = $tool_users_30d;

【讨论】:

  • 顺便说一句,您可能想在 (tool_id, date) 上添加复合索引,看看它是否有任何区别。好吧,我想 tool_id 已经有一个索引。在这种情况下,将日期列合并到此索引中,而不是创建另一个。还可以在日期列上尝试单独的索引。看看哪个更好。索引的需求不是静态的,它取决于表的大小和查询利用索引的能力。所以它需要随着时间的推移进行调整......
猜你喜欢
  • 1970-01-01
  • 2018-03-02
  • 1970-01-01
  • 1970-01-01
  • 2011-11-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-12
相关资源
最近更新 更多