2009-10-20 2 views
9

Итак, у меня есть таблица с более чем 80 000 записей, эта называется системой. У меня также есть следующая таблица, которая называется.Оптимизация моего оператора mysql! - RAND() TOO SLOW

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

Так вот, что у меня есть:

SELECT system.id, 
      system.username, 
      system.password, 
      system.followed, 
      system.isvalid, 
      follows.userid, 
      follows.systemid 
     FROM system 
    LEFT JOIN follows ON system.id = follows.systemid 
        AND follows.userid = 2 
     WHERE system.followed = 0 
     AND system.isvalid = 1 
     AND follows.systemid IS NULL 
    ORDER BY RAND() 
     LIMIT 200 

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

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

Спасибо!

+1

Какие индексы у вас есть в ваших JOIN-полях? Это может быть большая бутылочная шея. – dnagirl

+0

Я не слишком уверен, что вы имеете в виду ... – Brandon

+0

@Brandon Я знаю, что это немного поздно для этого, но если вы хотите получить упрощенную форму, вы можете просто положить его в подзапрос. подробнее http://stackoverflow.com/questions/25361158/mysql-select-random-on-large-table-order-by-score/25364339?noredirect=1#comment39644652_25364339 –

ответ

7

Я не уверен, что есть простое решение для замены вашего запроса, вот статья об исправлении этого типа проблемы.

http://www.titov.net/2005/09/21/do-not-use-order-by-rand-or-how-to-get-random-rows-from-table/

+0

Спасибо, но это не жизнеспособный вариант для пути этот запрос работает. – Brandon

+0

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

2

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

ORDER BY 37*(UNIX_TIMESTAMP()^system.id) & 0xffff 

смешает укусы от ид, а затем будет принимать только самый низкий 16.

+0

Кажется, так же медленно ... – Brandon

2

Есть две основные причины медлительности:

  • SQL должны первыми выдает случайное число для каждой из строк
  • Строки затем должны быть заказаны на основании этого номера, чтобы выбрать 200 лучших из них

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

==> Ввести дополнительный столбец с «случайной категорией» значением отфильтровать большинство строк

Идея состоит в том, чтобы иметь целочисленный столбец со значениями рандомизации, один раз во время подготовительного, с значение между точками 0 и 9 (или 1 и 25 ... независимо). Затем этот столбец необходимо добавить к индексу, используемому в запросе. Окончательно, изменив запрос на включение фильтра в этот столбец = конкретное значение (скажем, 3), количество строк, которые SQL должен обрабатывать, затем уменьшается на 10 (или 25, в зависимости от количества различных значений, которые мы имеем в «случайная категория».

Предполагая, что этот новый столбец называется RandPreFilter, мы могли бы введен индекс, как

CREATE [UNIQUE ?] INDEX 
ON system (id, RandPreFilter) 

И изменить запрос следующим образом

SELECT system.id 
    , system.username 
    , system.password 
    , system.followed 
    , system.isvalid 
    , follows.userid 
    , follows.systemid 
FROM system 
LEFT JOIN follows ON system.id = follows.systemid 
    AND follows.userid = 2 
WHERE system.followed=0 AND system.isvalid=1 
    AND follows.systemid IS NULL 

    AND RandPreFilter = 1 -- or other numbers, or possibly 
     -- FLOOR(1 + RAND() * 25) 
ORDER BY RAND() 
LIMIT 200 
5

причину запрос является медленным заключается в том, что база данных должна содержать представление обо всех генерируемые случайные значения и их соответствующие данные, прежде чем он сможет вернуть даже одну строку из базы данных.То, что вы можете сделать, это ограничить количество строк-кандидатов, которые следует учитывать сначала, используя WHERE RAND() < x, где вы выбираете x для числа, которое, скорее всего, вернет хотя бы количество необходимых вам образцов. Чтобы получить истинную случайную выборку, вам нужно будет снова заказать RAND или выполнить выборку на возвращаемом наборе данных.

Использование этого подхода позволяет базе данных обрабатывать запрос потоковым способом, не создавая большого промежуточного представления всех данных. Недостаток заключается в том, что вы никогда не сможете быть на 100% уверены, что получаете количество образцов, которые вам нужны, поэтому вам может потребоваться выполнить запрос еще до того, как вы это сделаете, жить с меньшим набором проб или постепенно добавлять образцы (чтобы избежать дублирования), пока у вас не будет количества образцов, которые вам нужны.

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

1

В зависимости от того, насколько случайны ваши данные, возможно, стоит заказать данные и добавить дополнительный «последний использованный» столбец datetime и обновить это после использования данных. Затем выберите порядок верхних n строк по последнему использованному полю.

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

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

0

Может быть, немного поздно, но по крайней мере здесь, является дополнительным решением для дальнейшего рассмотрения:

SELECT minSystem.id, 
    minSystem.username, 
    minSystem.password, 
    minSystem.followed, 
    minSystem.isvalid, 
    randFollows.userid, 
    randFollows.systemid 
FROM 
(
    SELECT * 
    FROM system 
    WHERE system.followed = 0 AND system.isvalid = 1 
) as minSystem 
LEFT JOIN 
(
    SELECT * 
    FROM (
     SELECT * 
     FROM follows 
     WHERE follows.systemid IS NULL 
    ) as minFollows 
    WHERE rand() <= 200 * 1.5/(SELECT count(*) FROM follows WHERE systemid IS NULL) 
) as randFollows 
ON minSystem.id = randFollows.systemid 
LIMIT 200 

Во-первых, мы проводим отбор на системной таблице, чтобы срубить minSystem и minFollow размер таблицы температуры. Затем мы выбираем случайные строки из таблицы minFollows через вычисленную вероятность. К настоящему времени у нас будет довольно случайная таблица randFollows для LEFT JOIN с помощью minSystem. Наконец, мы делаем LIMIT 200.

Если вы используете MyISam, вы можете просто получить размер таблицы. Это устраняет дополнительный подзапрос, чтобы вычислить размер таблицы follows. Кроме того, вы можете также жестко кодировать знаменатель, если размер вашей таблицы не растет слишком быстро (для этого требуется больше ручного обслуживания).

Для более тщательных объяснений, пожалуйста, проверка решения, которое я отправил на: MySQL: Alternatives to ORDER BY RAND()

Надеется, что это помогает (или, по крайней мере, я надеюсь, что вы найдете это интересно)!