youmixx
@youmixx
PHP Developer

Как оптимизировать эти запросы на Laravel?

Запросы занимают слишком много времени и пямяти, т.к. в БД очень много данных. Мне нужно как-то ускорить. Уже много раз менял код и пока не знаю как сделать еще быстрее.

У меня есть три графика на главной странице. Там мы выбираем с какой даты по какую показать и я получаю данные под каждый день.

Сначала перевожу дату в нужный формат:
if ($from) {
     $dateFrom = Date::createFromFormat('d.m.Y G:i', $from);
 }
if ($to) {
     $dateTo = Date::createFromFormat('d.m.Y G:i', $to);
}

Создаю массив, в который буду складывать значения:
$tableDate = [
    [
        'count_sales' => [],
    ],
    [
        'new_users' => [],
    ],
    [
        'success_payments' => [],
    ],
    'days' => [],
];

Потом получаю кол-во дней между этими датами.
// Все дни с первого по последнего.
$count_days = floor(abs(strtotime($dateFrom) - strtotime($dateTo)) / 86400) + 1;

После этого я делаю запросы через конструктор. Специально группирую их по датам, дабы потом распарсить.
$count_sales = ActiveService::orderBy('created_at')
    ->whereBetween('updated_at', [$dateFrom, $dateTo])
    ->get('created_at')
    ->groupBy(function($events) {
        return Carbon::parse($events->created_at)->format('d.m.Y');
});

$new_users = User::orderBy('created_at')
    ->whereBetween('created_at', [$dateFrom, $dateTo])
    ->get('created_at')
    ->groupBy(function($events) {
        return Carbon::parse($events->created_at)->format('d.m.Y')
});

$success_payments = HistoryPayment::orderBy('created_at') 
    ->whereBetween('created_at', [$dateFrom, $dateTo])
    ->where('operation', 'top_up')
    ->where('status_transaction', 'success')
    ->where('payment_id', '!=', '0')
    ->select(['amount', 'created_at'])
    ->get()
    ->groupBy(function($events) {
        return Carbon::parse($events->created_at)->format('d.m.Y');
});


Вот что я получаю:
(на примере $count_sales)
61d1dd1cb9d01824793669.png

Теперь делаю следующее (ниже поясню)
for ($i=0; $i < $count_days; $i++) {
    $day = date('d.m.Y', strtotime($dateFrom) + 86400 * $i);
    array_push($tableDate['days'],  $day);

    $tableDate[0]['count_sales'][$i] = 0;
    $tableDate[1]['new_users'][$i] = 0;
    $tableDate[2]['success_payments'][$i] = 0;

    // Кол-во успешных активаций (продаж) по дням.
    if($count_sales->get($day) !== null) {
        $count = $count_sales->get($day)->count();
        $tableDate[0]['count_sales'][$i] = $count;
    }

    // Кол-во регистраций по дням
    if($new_users->get($day) !== null) {
        $count = $new_users->get($day)->count();
        $tableDate[1]['new_users'][$i] = $count;
    }

    // Сумма успешных оплат по дням
    if($success_payments->get($day) !== null) {
        $amounts = $success_payments->get($day);
        foreach ($amounts as $value) {
            $tableDate[2]['success_payments'][$i] += $value->amount;
        }    
    }
}


Я создаю цикл, в которым перебираю каждый день.
Т.е. переменная $day - это определенный день в формате d.m.Y.
Как видите, у меня есть проверки типа:
if($count_sales->get($day) !== null)
Просто БД запросы что показывал выше (скриншот выше) возвращают только дни, в которых есть нужные нам записи. А спокойно может быть такое, что будет какая-то дата, например 22.12.2021 в которую ничего не будет куплено/пополнено и т.д. Поэтому я сначала проверяю дату, которую мы парсим сейчас на то, есть ли у нас данные для нее. Ну и если есть, я их вписываю.

В конце я просто все возвращаю:
return new Collection([
    'tableDate' => $tableDate,
]);


Ну так вот, запросы занимают много времени и памяти. Спокойно может выйти ошибка 500 ну или просто грузится 30 секунд - пару минут.

Мне бы хотя-бы совет или указать на то, что я сделал не так и как это можно сделать лучше.
  • Вопрос задан
  • 703 просмотра
Решения вопроса 1
AmdY
@AmdY
PHP и прочие вебштучки
Насколько я помню, ->get() делает запрос к базе и возвращает коллекцию. А это куча данных и ваш проект будет умирать, пользуйтесь groupBy как часть запроса и расчитывайте на стороне БД до вызова get(), а не после, когда вы уже работаете не с квери билдером, а с огромной коллекцией.
Ответ написан
Пригласить эксперта
Ответы на вопрос 3
@vism
Это задание, а не вопрос.
Ну, а если вопрос, то переписать код, чтоб формировался 1 запрос к базе и уже из него доставать данные.
Никаких проблем, много раз так делал.
Ну а если задание, то $200/h и за несколько часов составим нужный запрос:)
Ответ написан
Alex_Wells
@Alex_Wells
PHP/Kotlin
Напиши оптимизированные запросы в базу. Группирование обязательно на стороне запроса. 99% что это решит твою проблему, ибо данных у тебя точно не много. А если вдруг, то експлейн и индексы в помощь.
Ответ написан
@KingstonKMS
1. Расставить корректные индексы в таблицах.
2. Построить запросы в mysql клиенте, отладить их и убедится, что выполняются с минимальным временем.
3. Построить запросы через инструменты фреймворка.
* Выбирать из базы только те столбцы, которые необходимы для дальнейшего использования.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы