Как избежать экспоненциального замедления в PHP / MYSQL?

#php #mysql #optimization

#php #mysql #оптимизация

Вопрос:

Я владелец онлайн-браузерной игры, в которой зарегистрировано около 300 игроков. Я написал скрипт для обнаружения читеров, но проблема в том, что количество запросов в указанном скрипте будет расти экспоненциально.

Это работает следующим образом:

  1. Отправьте запрос, который получит информацию об игроке.
  2. Внутри запроса запустите другой запрос, который получит информацию о каждом игроке.

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

Проблема в том, что, поскольку у меня 300 игроков, я должен запускать 300 запросов на каждого игрока. Это 90 000 запросов. Если я достигну 1000 игроков, это будет 1 000 000 запросов. Должен быть лучший способ сделать это.

Мой код:

  <?php
    require '../connect.php';

    $rulerinfo = $conn->query("SELECT id, rulername, nationname, alliance, email, dateregister, user_agent, lastseen, password FROM players");
        while ($rulerinfo2 = $rulerinfo->fetch_assoc()) {
            $id = $rulerinfo2['id'];
            $rulername = $rulerinfo2['rulername'];
            $nationname = $rulerinfo2['nationname'];
            $alliance = $rulerinfo2['alliance'];
            $email = $rulerinfo2['email'];
            $dateregister = $rulerinfo2['dateregister'];
            $useragent = $rulerinfo2['user_agent'];
            $lastseen = $rulerinfo2['lastseen'];
            $password = $rulerinfo2['password'];

    $playerinfo = $conn->query("SELECT id, rulername, nationname, alliance, email, dateregister, user_agent, lastseen, password  FROM players WHERE id != '$id'");
        while ($playerinfo2 = $playerinfo->fetch_assoc()) {
            $id2 = $playerinfo2['id'];
            $rulername2 = $playerinfo2['rulername'];
            $nationname2 = $playerinfo2['nationname'];
            $alliance2 = $playerinfo2['alliance'];
            $email2 = $playerinfo2['email'];
            $dateregister2 = $playerinfo2['dateregister'];
            $useragent2 = $playerinfo2['user_agent'];
            $lastseen2 = $playerinfo2['lastseen'];
            $password2 = $playerinfo2['password'];

            $rulerdistance = levenshtein($rulername, $rulername2);
            $nationdistance = levenshtein($nationname, $nationname2);
            $emaildistance = levenshtein($email, $email2);
            $agentdistance = levenshtein($useragent, $useragent2) / 2;

            $totaldistance = $rulerdistance   $nationdistance   $emaildistance   $agentdistance;

            if ($password == $password2) {
                $totaldistance = $totaldistance - 20;
            }

            if ($totaldistance < 0) {
                $totaldistance = 0;
            }


        }

        }
?>
  

Комментарии:

1. Показать структуры таблиц и индексы, а также результат ПОЯСНЕНИЯ к запросу

2. @MarkBaker Можете ли вы объяснить, что такое EXPLAIN?

3. ОБЪЯСНИТЕ SELECT * FROM ….. это дает много информации о таблицах, ключах и о том, как запрос объединяет (не mysql join) вещи вместе. Поэтому в основном просто ставьте «EXPLAIN» перед вашим запросом.

4. Хорошо, я попробовал, но ничего не появляется. @brechmos

5. Похоже, что вы получаете все строки players в памяти, затем снова выполняете запрос одну за другой. Это 1 300 запросов, так что не совсем те 300 ^ 2, о которых вы упомянули (если только я чего-то не вижу). Почему бы не загрузить их все в память, и тогда вы сможете сравнивать все со всеми намного быстрее? Это один запрос. Более того, вы можете сделать это в автономном режиме на cron, так что вы можете увеличить лимиты памяти, необходимые для большего числа пользователей.

Ответ №1:

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

 $res = $conn->query("SELECT id, rulername, nationname, alliance, email, dateregister, user_agent, lastseen, password FROM players");

$array=array();
while ($row = $res->fetch_assoc()) {
   $array[] = $row;
}

for($i=0; $i<count($array);$i  ) {
   for($j=0; $j<count($array); $j  ) {
      if ($i != $j) {
         // Call your functions
         $rulerdistance = levenshtein($array[$i]['rulername'], $array[$j]['rulername']);
         ...
      }
   }
}
  

Комментарии:

1. Спасибо! Это должно очень помочь. Я протестирую это и посмотрю, смогу ли я заставить это работать.

2. Это сработало как по волшебству. Я потратил часы на поиск того, как использовать массивы в запросах, подобных этому, но безуспешно. Ты лучший, @Johnny Drew

3. $rulerinfo и $rulerinfo2 не являются идеальными именами. Один из них является ресурсом результата, а другой — строкой, поэтому $result и $row могут быть лучше (и обычно используются). В любом случае 1.

4. @halfer действительно, я просто сохранил текущий код, но внесу изменения.