Расчет поля в сущности, порядок по доктрине - PullRequest
0 голосов
/ 12 мая 2019

У меня есть пользовательский объект, у которого есть вычисляемое поле totalPoints.Теперь я хочу упорядочить пользователей в общем зачете.У меня уже есть рабочее решение, но оно действительно медленное только для 150 записей.Я беспокоюсь, если у меня будет больше записей, рано или поздно.

В моей пользовательской сущности у меня есть эта функция для подсчета очков

public function getTotalPoints()
{
        $totalPoints = 0;
        $totalPoints += $this->totalLevel;
        $totalPoints += $this->getWeeksJoined() * $this->staticsRepository->findOnByName("Points Weeks Joined")->getValue();
        $totalPoints += $this->forumBumps * $this->staticsRepository->findOnByName("Points Forum Bumps")->getValue();
        $totalPoints += $this->lootSplit * $this->staticsRepository->findOnByName("Points Loot Split")->getValue();
        $totalPoints += $this->isVeteran * $this->staticsRepository->findOnByName("Points Veteran Rank")->getValue();
        $totalPoints += $this->getSotwPlaces(1)->count() * $this->staticsRepository->findOnByName("Points SOTW Wins")->getValue();
        $totalPoints += $this->getSotwPlaces(2)->count() * $this->staticsRepository->findOnByName("Points SOTW Second Place")->getValue();
        $totalPoints += $this->getSotwPlaces(3)->count() * $this->staticsRepository->findOnByName("Points SOTW 3rd Place")->getValue();
        $totalPoints += $this->osrsUserRefs->count() * $this->staticsRepository->findOnByName("Points Referals")->getValue();
        $totalPoints += $this->eventsAttended->count() * $this->staticsRepository->findOnByName("Points Events Attended")->getValue();
        $totalPoints += $this->eventsHosted->count() * $this->staticsRepository->findOnByName("Points Events Hosted")->getValue();

        if($this->discordUser != null)
            $totalPoints += $this->discordUser->getLevel() * $this->staticsRepository->findOnByName("Points Discord Level")->getValue();

        return $totalPoints;
    }

Теперь, чтобы получить их, упорядоченные по итоговым точкам, нужно usortмассив пользователей, и это медленно.

/**
* @Route("/", name="highscores_index")
*/
    public function index(OsrsUserRepository $osrsUserRepository)
    {
        $osrsUsers = $osrsUserRepository->findAllHighscores();

        usort($osrsUsers, function($a, $b)
        {
            return $b->getTotalPoints() > $a->getTotalPoints();
        });

        return $this->render('highscores/index.html.twig', [
            'users' => $osrsUsers,
        ]);
    }

Есть ли способ сделать это внутри конструктора запросов доктрины?Я не нашел никаких документов по этому виду сортировки.

1 Ответ

1 голос
/ 12 мая 2019

Проблема в вашем случае, по сути, такова:

Вы выполняете полный расчет каждый раз, когда звоните getTotalPoints, ... и каждый вызов включает в себя 10 вызовов staticsRepository (fuckup 1: почему, черт возьми, у вас есть хранилище в сущности? - это запах кода!) и подсчитывает дочерние объекты (+1 запрос для каждой коллекции).

Я действительно очень надеюсь, что ваш staticsRepository не зависит от пользователя , потому что в этом случае запросы даже не кэшируются. по вашему мнению, они, вероятно, нет.

Итак, по сути, у вас есть проблема n+1 (см. № 5) .

Вы , вероятно, можете написать специализированный запрос , который вычисляет totalScore s для всех ваших пользователей в базе данных, но, честно говоря, я здесь не для того, чтобы обучать вас DQL или SQL. Тем не менее, сделайте исследование, или вы тратите так много потенциала для улучшения ...

Теперь есть несколько вариантов (неполный список):

  1. делать все это в базе данных , каждый раз, все время. В этом случае вы добавляете функцию к вашему UserRepository, которая выполняет запрос либо для одного User, либо возвращает всех пользователей, включая их общие баллы. Создание представления базы данных, включающего этот запрос в качестве поля, может сделать это даже прозрачным для вашего кода. но это также может быть сделано в построителе запросов, то есть DQL, ... или SQL. Может быть, вам понадобятся расширения доктрины.

    это решение является чрезвычайно эффективным и всегда дает достаточно недавние результаты

  2. реализует логику в php, сохраняет результат в базе данных (добавляя новое поле БД для totalScore), пересчитывает в удобный момент времени. Это решение очень эффективно, если все сделано правильно, но может привести к слегка устаревшим результатам. (удобный момент времени - как ... ежедневно в полночь или один раз в час, или каждые 5 минут - если ваш сервер может остановить это, добавьте некоторые средства, чтобы выяснить, изменился ли счет: отметка времени всего, что меняет счет, сохранить метка времени ...)

    это решение несколько производительно, обновление (которое, вероятно, будет выполнено с помощью заданий cron), и оно всегда дает правильные и свежие результаты. - недавний == ваш интервал. по сути, это упрощает ваш текущий подход ... в сочетании с 1 для очень хороших результатов.

  3. использовать совокупные поля , что существенно обновляет счет в базе данных - почти как в 2. но - всякий раз, когда он изменяется. Посмотрите на агрегатные поля и выясните, как адаптировать это к вашему варианту использования. Пожалуйста, обратите внимание на условия гонки и как их избежать. В зависимости от вашего конкретного подхода это может снизить производительность для многих действий, но значительно повысит производительность при запросе высоких баллов ...

    это ОЧЕНЬ высокоэффективно с высокими показателями и всегда дает достаточно недавние результаты, НО вам приходится иметь дело с условиями гонки и всеми обновлениями очков, которые могут быть чрезвычайно раздражающими и подверженными ошибкам!

Мой выбор был бы 1. Это требует, чтобы вычисление было выражено в sql или dql, что, вероятно, можно сделать.

...