2009-12-01 2 views
45

Я прочитал несколько альтернатив функции MySQL ORDER BY RAND(), но большинство альтернатив применимо только к тому, где требуется один случайный результат.MySQL: Альтернативы ORDER BY RAND()

Кто-нибудь есть какие-либо идеи, как оптимизировать запрос, который возвращает несколько случайных результатов, таких, как это:

SELECT u.id, 
      p.photo 
    FROM users u, profiles p 
    WHERE p.memberid = u.id 
     AND p.photo != '' 
     AND (u.ownership=1 OR u.stamp=1) 
ORDER BY RAND() 
    LIMIT 18 
+0

Я не понимаю, что вы ищете. Почему '' ORDER BY RAND() 'подходит? Вы в основном озабочены эффективностью? – outis

+0

Да, это так. Я не дошел даже до масштаба, представленного на вашем графике, и я уже принимал удар. – Tony

+1

@outis: поскольку он не масштабируется - см .: http://www.dasprids.de/blog/2008/06/07/fetching-random-rows-of-mysql-efficiently –

ответ

18

Вот альтернатива, но она по-прежнему базируется на использовании RAND():

SELECT u.id, 
     p.photo, 
     ROUND(RAND() * x.m_id) 'rand_ind' 
    FROM users u, 
     profiles p, 
     (SELECT MAX(t.id) 'm_id' 
      FROM USERS t) x 
    WHERE p.memberid = u.id 
    AND p.photo != '' 
    AND (u.ownership=1 OR u.stamp=1) 
ORDER BY rand_ind 
    LIMIT 18 

Это немного сложнее, но дал лучшее распределение random_ind значений:

SELECT u.id, 
     p.photo, 
     FLOOR(1 + RAND() * x.m_id) 'rand_ind' 
    FROM users u, 
     profiles p, 
     (SELECT MAX(t.id) - 1 'm_id' 
      FROM USERS t) x 
    WHERE p.memberid = u.id 
    AND p.photo != '' 
    AND (u.ownership=1 OR u.stamp=1) 
ORDER BY rand_ind 
    LIMIT 18 
+0

Отличный OMG. Огромное спасибо. Хотя мой db не огромен, я уже заметил небольшое увеличение производительности и чувствую себя намного более комфортно, двигаясь вперед. – Tony

+0

Этот ответ мне очень помог, так как большинство других «решений», которые я нашел, исключают сложные предложения WHERE. Благодаря! – MattBianco

+0

@OMG вы можете объяснить свой код? – Michelle

0

Я столкнулся с этим сегодня и пытался использовать «DISTINCT» вместе с JOINs, но получал дубликаты, которые я предполагаю, потому что RAND делал каждую строку JOINed отличной. Я запутался вокруг немного и нашел решение, которое работает, как это:

SELECT DISTINCT t.id, 
       t.photo 
     FROM (SELECT u.id, 
        p.photo, 
        RAND() as rand 
       FROM users u, profiles p 
       WHERE p.memberid = u.id 
        AND p.photo != '' 
        AND (u.ownership=1 OR u.stamp=1) 
       ORDER BY rand) t 
     LIMIT 18 
+0

Это похоже на то же самое, что и MySql, когда вы используете' ORDER BY RAND() '. – rcdmk

+0

Я тестировал его, и если у вас есть значение rand в вашем результирующем наборе (как это сделано в решениях OMG Ponies), DISTINCT становится отрицательным. Так вот как я об этом узнал. –

0

Решение Я использую также размещены в ссылке ниже: How can i optimize MySQL's ORDER BY RAND() function?

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

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

сначала сделать выбор:

SELECT * 
FROM users 
WHERE users.ownership = 1 OR users.stamp = 1 

Затем из этого пула, выбрать случайные строки через вычисленной вероятности. Если ваша таблица имеет M строк и вы хотите выбрать N случайных строк, вероятность случайного выбора должна быть N/M. Следовательно:

SELECT * 
FROM 
(
    SELECT * 
    FROM users 
    WHERE users.ownership = 1 OR users.stamp = 1 
) as U 
WHERE 
    rand() <= $limitCount/(SELECT count(*) FROM users WHERE users.ownership = 1 OR users.stamp = 1) 

Где N является пределом $ limitCount и M является подзапросом, который вычисляет счетчик строк таблицы. Однако, поскольку мы работаем над вероятностью, возможно получить МЕНЬШЕ, чем $ limitCount возвращаемых строк. Поэтому мы должны умножить N на коэффициент увеличения случайного пула.

т.е.:

SELECT* 
FROM 
(
    SELECT * 
    FROM users 
    WHERE users.ownership = 1 OR users.stamp = 1 
) as U 
WHERE 
    rand() <= $limitCount * $factor/(SELECT count(*) FROM users WHERE users.ownership = 1 OR users.stamp = 1) 

Я обычно набор $ = коэффициент 2. Можно установить коэффициент на более низкое значение, чтобы дополнительно уменьшить размер пула случайных (например, 1,5).

На данный момент мы бы уже ограничили таблицу размеров M примерно до 2N. Отсюда мы можем сделать JOIN, а затем LIMIT.

SELECT * 
FROM 
(
     SELECT * 
     FROM 
     (
      SELECT * 
      FROM users 
      WHERE users.ownership = 1 OR users.stamp = 1 
     ) as U 
     WHERE 
      rand() <= $limitCount * $factor/(SELECT count(*) FROM users WHERE users.ownership = 1 OR users.stamp = 1) 
) as randUser 
JOIN profiles 
ON randUser.id = profiles.memberid AND profiles.photo != '' 
LIMIT $limitCount 

На большой таблице этот запрос будет превосходить обычный запрос ORDER по запросу RAND().

Надеюсь, это поможет!

1

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

20

ОБНОВЛЕНИЕ 2016

Это решение лучше всего работает с использованием индексированный столбец.

Вот простой пример и оптимизированный столбец запросов, отмеченный 100 000 строк.

ОПТИМИЗИРОВАННЫЙ: 300мс

SELECT 
    g.* 
FROM 
    table g 
     JOIN 
    (SELECT 
     id 
    FROM 
     table 
    WHERE 
     RAND() < (SELECT 
       ((4/COUNT(*)) * 10) 
      FROM 
       table) 
    ORDER BY RAND() 
    LIMIT 4) AS z ON z.id= g.id 

примечания о предельной сумме: предел 4 и 4/COUNT (*). 4s должны быть одинаковыми. Изменение того, сколько вы вернетесь, не так сильно влияет на скорость. Контрольный показатель в пределе 4 и предел 1000 совпадают. Предел 10000 взял его до 600 мс

примечание о присоединении: Рандомизация только идентификатора быстрее, чем рандомизация целой строки. Так как он должен скопировать всю строку в память, а затем рандомизировать ее. Соединение может представлять собой любую таблицу, связанную с подзапросом «С», чтобы предотвратить показ таблиц.

примечание, где пункт: где count ограничивает количество результатов рандомизации. Он принимает процент от результатов и сортирует их, а не всю таблицу.

примечание sub query: Если выполняются условия присоединения и дополнительные условия, в которых необходимо разместить их как в подзапросе, так и в подзапросе. Точное подсчет и отведение правильных данных.

неоптимизированному: 1200ms

SELECT 
    g.* 
FROM 
    table g 
ORDER BY RAND() 
LIMIT 4 

ПРОФИ

4 раза быстрее, чем order by rand(). Это решение может работать с любой таблицей с индексированным столбцом.

СВОД

Это немного сложно со сложными запросами. Необходимо поддерживать 2 базы кода в подзапросов

+2

Очень приятно. Я обязательно буду использовать это. –

+2

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

-1

Order by rand() очень медленно на больших столах,

Я нашел следующий обходной путь в PHP скрипт:

Select min(id) as min, max(id) as max from table; 

Затем сделать случайный PHP

$rand = rand($min, $max); 

Тогда

'Select * from table where id>'.$rand.' limit 1'; 

Кажется, довольно быстро ....

2

Это не самый быстрый, но быстрее, чем общий ORDER BY RAND() образом:

ORDER BY RAND() не так медленно, когда вы используете его, чтобы найти только индексированный столбец. Вы можете взять все ваши идентификаторы в одном запросе так:

SELECT id 
FROM testTable 
ORDER BY RAND(); 

, чтобы получить последовательность случайных идентификаторов, и JOIN результат в другой запрос с другими ВЫБРАТЬ или WHERE параметры:

SELECT t.* 
FROM testTable 
JOIN 
    (SELECT id 
    FROM `testTable` 
    ORDER BY RAND()) AS z ON z.id= t.id 
WHERE isVisible = 1 
LIMIT 100; 

в вашем случае было бы:

SELECT u.id, p.photo 
FROM users u, profiles p 
JOIN 
    (SELECT id 
    FROM users 
    ORDER BY RAND()) AS z ON z.id= u.id 
WHERE p.memberid = u.id 
    AND p.photo != '' 
    AND (u.ownership=1 OR u.stamp=1) 
LIMIT 18 

это очень тупой способ, и это может быть не собственно с очень большими столами, но все-таки быстрее, чем обычные RAND(). Я получил в 20 раз быстрее время выполнения поиска 3000 случайных строк почти в 400000.