Оптимизация рандовых запросов с помощью соединения - PullRequest
0 голосов
/ 17 марта 2020

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

SELECT u.id 
     , u.is_instagram_connected 
     , u.tokens 
     , u.username 
     , u.name 
     , u.photo 
     , u.bio 
     , u.voice 
     , u.mobile_update 
     , 1584450999 - l.time idleTime
  FROM mobile_login_list l
  JOIN users u
    ON l.username = u.username
  JOIN mobile_token_list t
    ON t.username = l.username 
 WHERE l.time > 1584393399
   AND l.username NOT IN ('enesdoo')
   AND u.username NOT IN (
                   SELECT blocked_username
                     FROM hided_mobile_users_from_shuffle
                    WHERE username = 'enesdoo'
                         )
   AND u.ban_status = 0
   AND u.perma_ban = 0
   AND u.mobile_online_status = 1
   AND u.lock_status = 0
 GROUP 
    BY l.username
 ORDER 
    BY RAND( )
 LIMIT 27

Если я удаляю строку order by rand, она выполняется очень быстро, как в 100 раз быстрее.

Как я могу ускорить этот запрос?

mobile_login_list содержит> 50 тыс. строк

у пользователей> 1 млн. строк

Редактировать:

Объясните:

enter image description here

Мой стол:

CREATE TABLE IF NOT EXISTS `mobile_login_list` (
  `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `username` varchar(30) COLLATE utf8_bin NOT NULL,
  `key` varchar(32) COLLATE utf8_bin NOT NULL,
  `time` int(11) NOT NULL,
  `ip` int(11) NOT NULL,
  `version` smallint(4) NOT NULL,
  `messaged` int(11) NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  KEY `kontrol` (`username`,`key`),
  KEY `username` (`username`),
  KEY `time` (`time`),
  KEY `username_2` (`username`,`time`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=3351637 ;

Ответы [ 2 ]

0 голосов
/ 17 марта 2020

В строке go случайного поиска это называется операцией сделка (раздайте 27 разных карт из перетасованной колоды 4k или около того. Другая случайная операция называется бросок : разрешает дублирование.)

Вы используете SELECT mess-of-columns FROM mess-of-joins WHERE mess-of-criteria ORDER BY RAND() LIMIT small-number для выполнения операций перемешивания и сдачи. Это пресловутое представление антипаттерна. Это приводит к некоторой дополнительной работе для сервера, поскольку он должен упорядочить довольно большой набор результатов, а затем отбросить почти все (с помощью LIMIT).

Чтобы избавиться от некоторых проблем, нужно отложить присоединения к подробности. Перемешать только идентификаторы. Затем возьмите небольшое количество результатов и получите необходимые данные. Примерно так.

          SELECT u.id    /* just the id values */
            FROM mobile_login_list l
            JOIN users u
              ON l.username = u.username
            JOIN mobile_token_list t
              ON t.username = l.username 
           WHERE l.time > 1584393399
             AND l.username NOT IN ('enesdoo')
             AND u.username NOT IN (
                             SELECT blocked_username
                               FROM hided_mobile_users_from_shuffle
                              WHERE username = 'enesdoo'
                                   )
             AND u.ban_status = 0
             AND u.perma_ban = 0
             AND u.mobile_online_status = 1
             AND u.lock_status = 0
           ORDER 
              BY RAND( )
           LIMIT 27

Вы можете отлаживать, запускать EXPLAIN и оптимизировать этот подзапрос, изменяя индексы и, возможно, ужесточая критерии выбора. Это тот, кто делает всю тяжелую работу, тасуя и действуя.

Затем присоедините этот набор результатов к таблицам подробностей, чтобы выбрать нужные данные. Этот внешний запрос должен обработать только 27 строк. Обязательно снова перемешайте.

SELECT u.id 
     , u.is_instagram_connected 
     , u.tokens 
     , u.username 
     , u.name 
     , u.photo 
     , u.bio 
     , u.voice 
     , u.mobile_update 
     , 1584450999 - l.time idleTime
  FROM mobile_login_list l
  JOIN users u
    ON l.username = u.username
  JOIN (
           /* the subquery from above */
       ) selected ON u.id = selected.id
  ORDER BY RAND()

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

SELECT u.id 
   , u.is_instagram_connected 
   , u.tokens 
   , u.username 
   , u.name 
   , u.photo 
   , u.bio 
   , u.voice 
   , u.mobile_update 
   , 1584450999 - l.time idleTime
FROM mobile_login_list l
JOIN users u
  ON l.username = u.username
JOIN (
            SELECT u.id 
              FROM mobile_login_list l
              JOIN users u
                ON l.username = u.username
              JOIN mobile_token_list t
                ON t.username = l.username 
             WHERE l.time > 1584393399
               AND l.username NOT IN ('enesdoo')
               AND u.username NOT IN (
                               SELECT blocked_username
                                 FROM hided_mobile_users_from_shuffle
                                WHERE username = 'enesdoo'
                                     )
               AND u.ban_status = 0
               AND u.perma_ban = 0
               AND u.mobile_online_status = 1
               AND u.lock_status = 0
             ORDER 
                BY RAND( )
             LIMIT 27
     ) selected ON u.id = selected.id
 ORDER BY RAND()

Более эффективный способ работы с записями заключается в следующем, если вы много разыгрываете.

  1. Добавьте FLOAT столбец таблицы, с которой вы имеете дело, назовем это deal. Поместите в него индекс.

  2. Каждые несколько часов, или, возможно, в одночасье или даже раз в неделю, перетасуйте таблицу, выполнив этот запрос UPDATE users SET deal = RAND(); Это займет некоторое время; ему нужно изменить значение deal в каждой строке.

  3. Когда вам нужно совершить сделку, выполните ...WHERE deal >= RAND() * 0.9 ... ORDER BY deal LIMIT n. Умножение на 0,9 помогает гарантировать, что вы не дойдете до конца таблицы, выбрав случайное число, слишком близкое к 1.

Это эквивалентно, с точки зрения карточного акула, перетасовыванию колоды каждые несколько часов, а затем просто сокращая за каждую сделку. Именно так в Википедии реализована функция «показать случайную статью».

0 голосов
/ 17 марта 2020

Можем ли мы вместо этого увидеть ОБЪЯСНЕНИЕ для этого ...?

SELECT DISTINCT u.id 
              , u.is_instagram_connected 
              , u.tokens 
              , u.username 
              , u.name 
              , u.photo 
              , u.bio 
              , u.voice 
              , u.mobile_update 
              , 1584450999 - l.time idleTime
           FROM mobile_login_list l
           JOIN users u
             ON l.username = u.username
           JOIN mobile_token_list t
             ON t.username = l.username          
           LEFT
           JOIN hided_mobile_users_from_shuffle x         
             ON x.blocked_username = u.username
            AND x.username = 'enesdoo'         
          WHERE l.time > 1584393399
            AND l.username NOT IN ('enesdoo')
            AND x.blocked_username IS NULL
            AND u.ban_status = 0
            AND u.perma_ban = 0
            AND u.mobile_online_status = 1
            AND u.lock_status = 0
          ORDER 
             BY RAND( )
          LIMIT 27

Учитывая мои ограниченные знания по оптимизации запросов, я бы просто определил таблицу следующим образом, но, возможно, кто-то другой может предложить дальнейшие улучшения:

CREATE TABLE IF NOT EXISTS mobile_login_list 
(id SERIAL PRIMARY KEY
,username varchar(30) COLLATE utf8_bin NOT NULL
,`key` varchar(32) COLLATE utf8_bin NOT NULL
,time int NOT NULL
,ip int NOT NULL
,version smallint NOT NULL
,messaged int NOT NULL DEFAULT 0
,KEY username_2 (username,time) -- or (time,username)
);

Обратите внимание, что key - зарезервированное слово (а time - "ключевое слово"), что делает его плохим выбором для идентификатора таблицы / столбца

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...