MySQL выбирает 10 случайных строк из 600K строк быстро

Как я могу написать запрос, который выбирает 10 строк случайным образом из всего 600к?

23 ответов


большой столб регулируя несколько случаев, от простого, к зазорам, к не-равномерному с зазорами.

http://jan.kneschke.de/projects/mysql/order-by-rand/

для большинства общих случаев, Вот как вы это делаете:

SELECT name
  FROM random AS r1 JOIN
       (SELECT CEIL(RAND() *
                     (SELECT MAX(id)
                        FROM random)) AS id)
        AS r2
 WHERE r1.id >= r2.id
 ORDER BY r1.id ASC
 LIMIT 1

это предполагает, что распределение идентификаторов равно и что в списке идентификаторов могут быть пробелы. Дополнительные примеры см. В статье


SELECT column FROM table
ORDER BY RAND()
LIMIT 10

Не эффективное решение, но работает


его очень простой и однострочный запрос.

SELECT * FROM Table_Name ORDER BY RAND() LIMIT 0,10;

Я быстрые запросы (около 0,5 секунды) с медленный процессор, выбирая 10 случайных строк в 400K, регистрирует базу данных MySQL без кэширования 2 ГБ. Смотрите здесь мой код: быстрый выбор случайных строк в MySQL

<?php
$time= microtime_float();

$sql='SELECT COUNT(*) FROM pages';
$rquery= BD_Ejecutar($sql);
list($num_records)=mysql_fetch_row($rquery);
mysql_free_result($rquery);

$sql="SELECT id FROM pages WHERE RAND()*$num_records<20
   ORDER BY RAND() LIMIT 0,10";
$rquery= BD_Ejecutar($sql);
while(list($id)=mysql_fetch_row($rquery)){
    if($id_in) $id_in.=",$id";
    else $id_in="$id";
}
mysql_free_result($rquery);

$sql="SELECT id,url FROM pages WHERE id IN($id_in)";
$rquery= BD_Ejecutar($sql);
while(list($id,$url)=mysql_fetch_row($rquery)){
    logger("$id, $url",1);
}
mysql_free_result($rquery);

$time= microtime_float()-$time;

logger("num_records=$num_records",1);
logger("$id_in",1);
logger("Time elapsed: <b>$time segundos</b>",1);
?>

из книги :

выберите случайную строку, используя смещение

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

<?php
$rand = "SELECT ROUND(RAND() * (SELECT COUNT(*) FROM Bugs))";
$offset = $pdo->query($rand)->fetch(PDO::FETCH_ASSOC);
$sql = "SELECT * FROM Bugs LIMIT 1 OFFSET :offset";
$stmt = $pdo->prepare($sql);
$stmt->execute( $offset );
$rand_bug = $stmt->fetch();

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


простой запрос, который имеет отличная производительность (работает с пробелами):

SELECT * FROM tbl WHERE id IN 
    (SELECT id FROM (SELECT id FROM tbl ORDER BY RAND() LIMIT 10) t)

используются два вложенных подзапроса, потому что MySQL еще не поддерживает ограничение в первом.

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

для взвешенной версии:https://stackoverflow.com/a/41577458/893432


Как выбрать случайные строки из таблицы:

отсюда: выберите случайные строки в MySQL

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

SELECT *
FROM random, (
        SELECT id AS sid
        FROM random
        ORDER BY RAND( )
        LIMIT 10
    ) tmp
WHERE random.id = tmp.sid;

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

таким образом, одно решение будет следующим:

SELECT * FROM table WHERE key >= FLOOR(RAND()*MAX(id)) LIMIT 1

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

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

это действительно большая проблема и нелегко решить выполнение всех требований, rand () MySQL-лучшее, что вы можете получить, если вы действительно хотите 10 случайных строк.

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

вопрос в том, насколько случайным вам это нужно быть.

можете ли вы объяснить немного больше, чтобы я мог дать вам хорошее решение.

например, у компании, с которой я работал, было решение, в котором им очень быстро требовалась абсолютная случайность. Они закончили тем, что предварительно заполнили базу данных случайными значениями, которые были выбраны по убыванию и затем снова установлены на разные случайные значения.

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


я использовал это http://jan.kneschke.de/projects/mysql/order-by-rand/ опубликовано Riedsio (я использовал случай хранимой процедуры, которая возвращает одно или несколько случайных значений):

   DROP TEMPORARY TABLE IF EXISTS rands;
      CREATE TEMPORARY TABLE rands ( rand_id INT );

    loop_me: LOOP
        IF cnt < 1 THEN
          LEAVE loop_me;
        END IF;

        INSERT INTO rands
           SELECT r1.id
             FROM random AS r1 JOIN
                  (SELECT (RAND() *
                                (SELECT MAX(id)
                                   FROM random)) AS id)
                   AS r2
            WHERE r1.id >= r2.id
            ORDER BY r1.id ASC
            LIMIT 1;

        SET cnt = cnt - 1;
      END LOOP loop_me;

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

   DROP TEMPORARY TABLE IF EXISTS rands;
      CREATE TEMPORARY TABLE rands ( rand_id INT );

    loop_me: LOOP
        IF cnt < 1 THEN
          LEAVE loop_me;
        END IF;

        SET @no_gaps_id := 0;

        INSERT INTO rands
           SELECT r1.id
             FROM (SELECT id, @no_gaps_id := @no_gaps_id + 1 AS no_gaps_id FROM random) AS r1 JOIN
                  (SELECT (RAND() *
                                (SELECT COUNT(*)
                                   FROM random)) AS id)
                   AS r2
            WHERE r1.no_gaps_id >= r2.id
            ORDER BY r1.no_gaps_id ASC
            LIMIT 1;

        SET cnt = cnt - 1;
      END LOOP loop_me;

в статье я вижу, что он пошел на многое, чтобы оптимизировать код; у меня нет ideea, если / насколько мои изменения влияют на производительность, но очень хорошо работает для меня.


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

SELECT MAX(id) FROM table_name;

затем подставьте это значение в:

SELECT * FROM table_name WHERE id > FLOOR(RAND() * max) LIMIT n;

где Max-максимальный id записи в таблице, а n-количество строк в результирующем наборе. Предположение состоит в том, что в идентификаторе записи нет пробелов, хотя я сомневаюсь, что это повлияет на результат, если бы он был (не пробовал). Я создал хранимую процедуру, чтобы быть более универсальным; передаем имя таблицы и количество строк, которые будут возвращены. Я запускаю MySQL 5.5.38 в Windows 2008, 32GB, dual 3GHz E5450, и на таблице с 17,361,264 строками он довольно последователен в ~.03 сек / ~11 сек для возврата 1,000,000 строк. (время от MySQL Workbench 6.1; вы также можете использовать CEIL вместо пола во 2-м операторе select в зависимости от ваших предпочтений)

DELIMITER $$

USE [schema name] $$

DROP PROCEDURE IF EXISTS `random_rows` $$

CREATE PROCEDURE `random_rows`(IN tab_name VARCHAR(64), IN num_rows INT)
BEGIN

SET @t = CONCAT('SET @max=(SELECT MAX(id) FROM ',tab_name,')');
PREPARE stmt FROM @t;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;

SET @t = CONCAT(
    'SELECT * FROM ',
    tab_name,
    ' WHERE id>FLOOR(RAND()*@max) LIMIT ',
    num_rows);

PREPARE stmt FROM @t;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END
$$

затем

CALL [schema name].random_rows([table name], n);

вот игра смены, которая может быть полезно для многих;

у меня есть таблица с 200k строк,С последовательным удостоверение, мне нужно, чтобы забрать N случайные строки, поэтому я предпочитаю генерировать случайные значения на основе самого большого идентификатора в таблице, я создал этот скрипт, чтобы узнать, какая из них самая быстрая операция:

logTime();
query("SELECT COUNT(id) FROM tbl");
logTime();
query("SELECT MAX(id) FROM tbl");
logTime();
query("SELECT id FROM tbl ORDER BY id DESC LIMIT 1");
logTime();

результаты:

  • Граф: 36.8418693542479 ms
  • Макс: 0.241041183472 ms
  • заказать: 0.216960906982 ms

основанный в этих результатах, заказ desc самая быстрая деятельность для того чтобы получить максимальный id,
Вот мой ответ на вопрос:--6-->

SELECT GROUP_CONCAT(n SEPARATOR ',') g FROM (
    SELECT FLOOR(RAND() * (
        SELECT id FROM tbl ORDER BY id DESC LIMIT 1
    )) n FROM tbl LIMIT 10) a

...
SELECT * FROM tbl WHERE id IN ($result);

FYI: чтобы получить 10 случайных строк из таблицы 200k, мне потребовалось 1.78 ms (включая все операции на стороне php)


все лучшие ответы уже опубликованы (в основном те, которые ссылаются на ссылку http://jan.kneschke.de/projects/mysql/order-by-rand/).

Я хочу указать еще одну возможность ускорения -кэширование. Подумайте, почему вам нужно получить случайные строки. Вероятно, вы хотите отобразить случайное сообщение или случайное объявление на веб-сайте. Если вы получаете 100 req / s, действительно ли нужно, чтобы каждый посетитель получал случайные строки? Обычно это совершенно нормально кэшировать эти X случайные строки в течение 1 секунды (или даже 10 секунд). Не имеет значения, получат ли 100 уникальных посетителей за одну и ту же секунду одинаковые случайные сообщения, потому что в следующую секунду еще 100 посетителей получат другой набор сообщений.

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


если у вас есть только один Read-Request

объедините ответ @redsio с temp-таблицей (600K не так много):

DROP TEMPORARY TABLE IF EXISTS tmp_randorder;
CREATE TABLE tmp_randorder (id int(11) not null auto_increment primary key, data_id int(11));
INSERT INTO tmp_randorder (data_id) select id from datatable;

а затем возьмите версию @redsios ответ:

SELECT dt.*
FROM
       (SELECT (RAND() *
                     (SELECT MAX(id)
                        FROM tmp_randorder)) AS id)
        AS rnd
 INNER JOIN tmp_randorder rndo on rndo.id between rnd.id - 10 and rnd.id + 10
 INNER JOIN datatable AS dt on dt.id = rndo.data_id
 ORDER BY abs(rndo.id - rnd.id)
 LIMIT 1;

если таблица большая, то вы можете просеять на первой части:

INSERT INTO tmp_randorder (data_id) select id from datatable where rand() < 0.01;

если у вас много запросов на чтение

  1. версия: вы можете сохранить таблицу tmp_randorder постоянный, назовите его datatable_idlist. Воссоздать эту таблицу в определенных интервалы (день, час), так как он также получит отверстия. Если ваш стол становится действительно большим, вы также можете пополнить отверстий

    выберите l.data_id в целом из datatable_idlist l слева присоединиться к datatable dt на dt.id = l.data_id где dt.id равен null;

  2. версия: дайте вашему набору данных столбец random_sortorder либо непосредственно в datatable, либо в постоянной дополнительной таблице datatable_sortorder. Проиндексируйте этот столбец. Сгенерируйте случайное значение в своем приложении (я назову его $rand).

    select l.*
    from datatable l 
    order by abs(random_sortorder - $rand) desc 
    limit 1;
    

это решение различает "крайние строки" с самым высоким и самым низким random_sortorder, поэтому переставьте их в интервалах (один раз в день).


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

SELECT d.* FROM (
SELECT  t.*,  @rownum := @rownum + 1 AS rank
FROM mytable AS t,
    (SELECT @rownum := 0) AS r,
    (SELECT @cnt := (SELECT RAND() * (SELECT COUNT(*) FROM mytable))) AS n
) d WHERE rank >= @cnt LIMIT 10;

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

однако, если вы не хотите последовательных случайных значений, вы можете получить большую выборку и выбрать ее случайным образом. что-то вроде ...

SELECT * FROM (
SELECT d.* FROM (
    SELECT  c.*,  @rownum := @rownum + 1 AS rank
    FROM buildbrain.`commits` AS c,
        (SELECT @rownum := 0) AS r,
        (SELECT @cnt := (SELECT RAND() * (SELECT COUNT(*) FROM buildbrain.`commits`))) AS rnd
) d 
WHERE rank >= @cnt LIMIT 10000 
) t ORDER BY RAND() LIMIT 10;

один из способов, который я нахожу довольно хорошим, если есть автогенерированный идентификатор, - использовать оператор по модулю"%". Например, если вам нужно 10 000 случайных записей из 70 000, вы можете упростить это, сказав, что вам нужен 1 из каждых 7 строк. Это можно упростить в этом запросе:

SELECT * FROM 
    table 
WHERE 
    id % 
    FLOOR(
        (SELECT count(1) FROM table) 
        / 10000
    ) = 0;

Если результат деления целевых строк на общее доступное не является целым числом, у вас будет несколько дополнительных строк, чем то, что вы просили, поэтому вы должны добавить предложение LIMIT, чтобы помочь вам обрезать результирующий набор, как это:

SELECT * FROM 
    table 
WHERE 
    id % 
    FLOOR(
        (SELECT count(1) FROM table) 
        / 10000
    ) = 0
LIMIT 10000;

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


я улучшил ответ @Riedsio. Это самый эффективный запрос, который я могу найти на большой, равномерно распределенной таблице с пробелами (проверено на получение 1000 случайных строк из таблицы, которая имеет > 2.6 B строк).

(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max := (SELECT MAX(id) FROM table)) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
(SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1)

позвольте мне пояснить, что происходит.

  1. @max := (SELECT MAX(id) FROM table)
    • я вычисляю и сохраняю максимум. Для очень больших таблиц существует небольшая накладная для вычисления MAX(id) каждый раз, когда вам понадобится row
  2. SELECT FLOOR(rand() * @max) + 1 as rand)
    • получает случайный идентификатор
  3. SELECT id FROM table INNER JOIN (...) on id > rand LIMIT 1
    • это заполняет пробелы. В основном, если вы случайно выбираете номер в промежутках, он просто выберет следующий идентификатор. Если зазоры равномерно распределены, это не должно быть проблемой.

выполнение союза поможет вам поместить все в 1 запрос, чтобы вы могли избежать выполнения нескольких запросов. Он также позволяет сохранить вычисления MAX(id). В зависимости от вашего приложения, это может иметь значение много или очень мало.

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

SELECT t.id, t.name -- etc, etc
FROM table t
INNER JOIN (
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max := (SELECT MAX(id) FROM table)) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1) UNION
    (SELECT id FROM table INNER JOIN (SELECT FLOOR(RAND() * @max) + 1 as rand) r on id > rand LIMIT 1)
) x ON x.id = t.id
ORDER BY t.id

Если вы хотите одну случайную запись (независимо от того, есть ли зазоры между идентификаторами):

PREPARE stmt FROM 'SELECT * FROM `table_name` LIMIT 1 OFFSET ?';
SET @count = (SELECT
        FLOOR(RAND() * COUNT(*))
    FROM `table_name`);

EXECUTE stmt USING @count;

источник: https://www.warpconduit.net/2011/03/23/selecting-a-random-record-using-mysql-benchmark-results/#comment-1266


старый вопрос, но это то, с чем я столкнулся сегодня, желая выбрать случайную страницу. Я решил не использовать ни один из ответов здесь из-за проблем с производительностью и того факта, что многие из них имеют сильные предубеждения в "случайном". Вот мое решение (с использованием PHP):

Pages model:

public static function getIDs() {
    $sql  = "SELECT `id` FROM `pages`;";
    $db   = static::getDB();
    $stmt = $db->query($sql);

    return $stmt->fetchAll(PDO::FETCH_ASSOC);
}

Pages controller:

public function randomAction() {
    $pages  = Pages::getIDs();
    $random = $pages[rand(0, count($pages))];

    $this->redirect('/' . $random['id'], 307);
}

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

если вы хотите 10 записей, просто повторите массив и удалите выбранные, чтобы избежать дублирования, а затем добавьте их в отдельный массив результатов. Что-то вроде этого:--6-->

public static function randomAction() {
    $pages   = Pages::getIDs();
    $count   = count($pages);
    $results = [];

    for($i = 0; $i < 10; $i++) {
        $random = rand(0, $count);
        $count -= 1;

        $results[] = $pages[$random];
        unset($pages[$random]);
    }

    return $results;
}

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

Если вы хотите предельной простоты и скорости, при незначительной стоимости, то мне кажется, имеет смысл хранить случайное число против каждой строки в БД. Просто создайте дополнительный столбец,random_number, и установите значение по умолчанию RAND(). Создайте индекс для этого столбца.

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

SELECT FROM tbl WHERE random_number >= :random LIMIT 1

Я думаю, хотя это очень аккуратно для одной строки, для десяти строк, таких как OP, вам придется называть это десять раз (или придумать умную настройку, которая сразу ускользает от меня)


Я использую этот запрос:

select floor(RAND() * (SELECT MAX(key) FROM table)) from table limit 10

время запроса: 0.016 s


используйте приведенный ниже простой запрос для получения случайных данных из таблицы.

SELECT user_firstname ,
COUNT(DISTINCT usr_fk_id) cnt
FROM userdetails 
GROUP BY usr_fk_id 
ORDER BY cnt ASC  
LIMIT 10

вот как я это делаю:

select * 
from table_with_600k_rows
where rand() < 10/600000
limit 10

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


Я думаю, это лучший из возможных способов..

SELECT id, id * RAND( ) AS random_no, first_name, last_name
FROM user
ORDER BY random_no