MySQL은 600K 행에서 10 개의 무작위 행을 빠르게 선택합니다.


463

총 600k에서 무작위로 10 개의 행을 선택하는 쿼리를 작성하는 방법은 무엇입니까?


15
여기 8 개 기술 ; 아마도 하나는 당신의 경우에 잘 작동 할 것입니다.
Rick James

답변:


386

간결한 간격에서 비 균일 한 간격에 이르기까지 여러 사례를 처리하는 훌륭한 포스트입니다.

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

이것은 id의 분포가 같고 id 목록에 간격이있을 수 있다고 가정합니다. 고급 예제는 기사를 참조하십시오.


52
예, ID에 잠재적으로 큰 차이가있는 경우 가장 낮은 ID가 임의로 선택 될 가능성은 높은 ID보다 훨씬 낮습니다. 실제로 가장 큰 간격 이후 첫 번째 ID가 선택 될 가능성은 실제로 가장 높습니다. 따라서 이것은 정의에 따라 무작위가 아닙니다.
lukeocodes

6
10 개의 다른 임의의 행을 어떻게 얻습니까? 제한을 10으로 설정 한 다음을 사용하여 10 회 반복해야 mysqli_fetch_assoc($result)합니까? 아니면 그 10 개의 결과가 반드시 구별 될 필요는 없습니까?
Adam

12
내 마음에, 임의의 결과에 대한 동등한 기회가 필요합니다. ;)
lukeocodes가

4
전체 기사는 불균형 분포 및 반복 결과와 같은 문제를 다룹니다.
Bradd Szonye

1
특히 ID를 시작할 때 틈이 있으면 첫 번째 시간이 선택됩니다 (최소 / 최대-최소). 이 경우 간단한 조정은 MAX ()-MIN () * RAND + MIN ()이며 너무 느리지 않습니다.
Code Abominator 4

342
SELECT column FROM table
ORDER BY RAND()
LIMIT 10

효율적인 솔루션은 아니지만 작동


139
ORDER BY RAND()상대적으로 느리다
Mateusz Charytoniuk

7
Mateusz-증거 pls SELECT words, transcription, translation, sound FROM vocabulary WHERE menu_id=$menuId ORDER BY RAND() LIMIT 10는 0.0010을 취하고, LIMIT 10을 사용하지 않고 0.0012를 취했습니다 (그 표에서 3500 단어).
Arthur Kushman

26
@zeusakm 3500 단어는 그렇게 많지 않습니다. 문제는 MySQL이 각 레코드를 읽은 후 실제로 모든 레코드를 정렬해야하기 때문에 특정 지점을 지나서 폭발한다는 것입니다. 해당 작업이 하드 디스크에 닿으면 차이를 느낄 수 있습니다.
Ja͢ck

16
나는 반복하고 싶지 않지만 다시 한 번 전체 테이블 스캔입니다. 큰 테이블에서는 시간과 메모리가 많이 소모되며 디스크의 임시 테이블에서 생성 및 작동이 매우 느릴 수 있습니다.
matt

10
2010 년에 Facebook과의 인터뷰에서 그들은 알 수없는 크기의 거대한 파일에서 무작위로 레코드를 한 번에 선택하는 방법을 물었습니다. 아이디어가 나오면 여러 레코드를 선택하기 위해 아이디어를 일반화하기 쉽습니다. 예, 전체 파일을 정렬하는 것은 말도 안됩니다. 동시에 매우 편리합니다. 방금 1,000,000 + 행이있는 테이블에서 10 개의 임의 행을 선택하기 위해이 접근법을 사용했습니다. 물론 조금 기다려야했다. 하지만 저는이 표의 일반적인 행이 어떻게 보이는지 알고 싶었습니다.
osa

27

성능우수하고 간격 이있는 간단한 쿼리 :

SELECT * FROM tbl AS t1 JOIN (SELECT id FROM tbl ORDER BY RAND() LIMIT 10) as t2 ON t1.id=t2.id

200K 테이블에 대한이 쿼리는 0.08 초가 걸리고 일반 버전 (SELECT * FROM tbl ORDER BY RAND () LIMIT 10)은 내 컴퓨터에서 0.35 초가 걸립니다 .

정렬 단계는 색인화 된 ID 열만 사용하므로 빠릅니다. Explain에서이 동작을 볼 수 있습니다.

RAND () 제한 10을 기준으로 tbl 주문에서 선택 * : 간단한 설명

t1부터 AS t1에서 선택 * t2 ON t1.id = t2.id로 (tbl ORDER BY RAND () LIMIT 10에서 id 선택) 여기에 이미지 설명을 입력하십시오

가중 버전 : https://stackoverflow.com/a/41577458/893432


1
미안, 테스트 했어! 600k 레코드에서 성능이 느려집니다.
Dylan B

@DylanB 테스트로 답변을 업데이트했습니다.
알리

17

나는 무엇입니까 빠른 쿼리 로 (약 0.5 초) 속도가 느린 CPU 400K 등록 MySQL 데이터베이스 캐시되지 않은 2 기가 바이트 크기의 10 개 임의의 행을 선택. 내 코드를 참조하십시오 : 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);
?>

11
1400 만 개가 넘는 레코드 테이블을 감안할 때, 이것은 느리다ORDER BY RAND()
Fabrizio

5
@snippetsofcode 귀하의 경우-400k 행은 간단한 "ORDER BY rand ()"를 사용할 수 있습니다. 3 개의 쿼리를 사용한 트릭은 쓸모가 없습니다. "ID 선택, URL이있는 페이지에서 ID를 선택하십시오 (ORDER BY rand () LIMIT 10에서 페이지에서 ID를 선택하십시오)"
Roman Podlinov

4
귀하의 기술은 여전히 ​​테이블 스캔을 수행합니다. FLUSH STATUS; SELECT ...; SHOW SESSION STATUS LIKE 'Handler%';그것을 볼 때 사용 하십시오.
Rick James

4
또한 200 req / s 웹 페이지에서 해당 쿼리를 실행하십시오. 동시성은 당신을 죽일 것입니다.
Marki555

@RomanPodlinov의 장점은 일반 ORDER BY RAND()행보다 ID 만 정렬하므로 임시 테이블은 작지만 여전히 정렬해야한다는 것입니다.
Marki555

16

매우 간단하고 한 줄로 된 쿼리입니다.

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

21
참고로, order by rand()테이블이 크면 매우 느립니다
evilReiko

6
내가 그것을 SIMPLE 유지하려면 때때로 SLOW이 허용됩니다

인덱스가 큰 경우 테이블에 적용해야합니다.
Muhammad Azeem

1
인덱싱은 여기서 도움이되지 않습니다. 인덱스는 매우 구체적인 사항에 도움이되며이 쿼리는 그 중 하나가 아닙니다.
앤드류

13

책에서 :

오프셋을 사용하여 임의의 행을 선택하십시오.

이전 대안에서 발견 된 문제점을 피하는 또 다른 기술은 데이터 세트의 행을 계산하고 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();

연속 키 값을 가정 할 수없고 각 행에 고른 기회가 있는지 확인해야 할 때이 솔루션을 사용하십시오.


1
매우 큰 테이블의 경우 SELECT count(*)속도가 느려집니다.
한스 Z

7

테이블에서 임의의 행을 선택하는 방법 :

여기에서 : MySQL에서 임의의 행을 선택 하십시오.

"테이블 스캔"보다 빠른 개선은 인덱스를 사용하여 임의의 ID를 선택하는 것입니다.

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

1
MyISAM에는 도움이되지만 InnoDB에는 도움이되지 않습니다 (id가 클러스터 된 것으로 가정 PRIMARY KEY).
Rick James

7

키에 틈이없고 모두 숫자이면 임의의 숫자를 계산하고 해당 줄을 선택할 수 있습니다. 그러나 이것은 아마도 그렇지 않을 것입니다.

따라서 한 가지 해결책은 다음과 같습니다.

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

기본적으로 키 범위에서 임의의 숫자를 얻은 다음 다음 중 가장 큰 것을 선택합니다. 이것을 10 번해야합니다.

그러나 키가 균등하게 분배되지 않기 때문에 이것은 실제로 무작위가 아닙니다.

실제로 큰 문제이며 모든 요구 사항을 충족시키기가 쉽지 않습니다. MySQL의 rand ()는 10 개의 임의 행을 원할 때 얻을 수있는 최선입니다.

그러나 다른 솔루션은 빠르지 만 무작위성에 관해서는 트레이드 오프가 있지만 더 잘 맞을 수 있습니다. 여기에서 읽으십시오 : MySQL의 ORDER BY RAND () 함수를 어떻게 최적화 할 수 있습니까?

질문은 당신이 얼마나 랜덤해야 하는가입니다.

좀 더 설명해 주시면 좋은 해결책을 드릴 수 있습니다.

예를 들어, 내가 함께 일한 회사는 절대 무작위성이 매우 빠른 솔루션을 가지고있었습니다. 결과적으로 내림차순으로 선택한 임의의 값으로 데이터베이스를 미리 채우고 나중에 다른 임의의 값으로 설정했습니다.

거의 업데이트하지 않으면 증분 ID를 채울 수 있으므로 간격이 없으며 선택하기 전에 임의의 키를 계산할 수 있습니다 ... 사용 사례에 따라 다릅니다!


안녕 조. 이 특별한 경우 키에는 간격이 없어야하지만 시간이 지남에 따라 변경 될 수 있습니다. 그리고 귀하의 답변이 작동하는 동안 연속적인 임의의 10 행 (제한 10을 쓰면 제공)이 생성되고 더 많은 임의성을 원했습니다. :) 감사합니다.
Francisc

10이 필요한 경우 일종의 공용체를 사용하여 10 개의 고유 행을 생성하십시오.
johno

내가 말한 것을 유인합니다. 10 번 실행해야합니다. 그것을 조합하여 하나의 쿼리에 넣는 한 가지 방법입니다. 2 분 전에 내 부록을 참조하십시오.
Surrican

1
@ TheSurrican,이 솔루션은 멋지지만 결함이 있습니다. 삽입 시도 단지 하나의 매우 큰 Id모든 사용자의 임의 쿼리는 당신에게 하나를 반환합니다 Id.
Pacerier

1
FLOOR(RAND()*MAX(id))더 큰 ID를 반환하는쪽으로 편향되어 있습니다.
Rick James

3

다소 큰 테이블에서 많은 수의 임의 행을 반환하는 쿼리가 필요했습니다. 이것이 내가 생각해 낸 것입니다. 먼저 최대 레코드 ID를 얻으십시오.

SELECT MAX(id) FROM table_name;

그런 다음 해당 값을 다음으로 대체하십시오.

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

여기서 max는 테이블의 최대 레코드 ID이고 n은 결과 집합에서 원하는 행 수입니다. 레코드 ID에 차이가 없다고 가정하지만 (있는 경우 시도하지 않은 경우) 결과에 영향을 줄 것입니다. 또한이 저장 프로 시저를보다 일반적인 것으로 만들었습니다. 반환 할 테이블 이름과 행 수를 전달하십시오. Windows 2008, 32GB, 듀얼 3GHz E5450 및 17,361,264 행이있는 테이블에서 MySQL 5.5.38을 실행 중이며 1,000,000 행을 반환하기 위해 ~ .03 초 / ~ 11 초에서 상당히 일관성이 있습니다. (시간은 MySQL Workbench 6.1에서 온 것입니다. 선호도에 따라 두 번째 select 문에서 FLOOR 대신 CEIL을 사용할 수도 있습니다)

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);

3

모든 최고의 답변이 이미 게시되었습니다 (주로 http://jan.kneschke.de/projects/mysql/order-by-rand/ 링크 참조 ).

또 다른 속도 향상 가능성을 캐싱 하고 싶습니다 . 임의의 행을 가져와야하는 이유를 생각해보십시오. 웹 사이트에 임의의 게시물이나 임의의 광고를 표시하려고 할 수 있습니다. 100 req / s를 얻는다면 각 방문자가 임의의 행을 가져와야합니까? 일반적으로 이러한 X 임의 행을 1 초 (또는 10 초) 동안 캐시하는 것이 좋습니다. 동일한 1 초에 100 명의 순 방문자가 동일한 임의의 게시물을 얻는다면 문제가되지 않습니다. 다음 초에 100 명의 다른 방문자가 다른 게시물을 가져 오기 때문입니다.

이 캐싱을 사용할 때 요청 / 초에 관계없이 초당 1 회만 MySQL에서 가져 오기 때문에 임의의 데이터를 가져 오기 위해 더 느린 솔루션을 사용할 수도 있습니다.


3

@Riedsio의 답변을 개선했습니다. 이것은 간격 이 좁고 균일하게 분산 된 큰 테이블에서 찾을 수있는 가장 효율적인 쿼리입니다 (> 2.6B 행이있는 테이블에서 1000 개의 임의 행을 가져 오는 테스트).

(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)경우 행이 필요할 때마다 계산에 약간의 오버 헤드 가 있습니다.
  2. SELECT FLOOR(rand() * @max) + 1 as rand)
    • 임의의 ID를 가져옵니다
  3. SELECT id FROM table INNER JOIN (...) on id > rand LIMIT 1
    • 이것은 공백을 채 웁니다. 기본적으로 간격에서 숫자를 임의로 선택하면 다음 ID 만 선택합니다. 격차가 균일하게 분포되어 있다고 가정하면 문제가되지 않습니다.

통합을 수행하면 모든 쿼리를 하나의 쿼리에 맞추는 데 도움이되므로 여러 쿼리를 수행하지 않아도됩니다. 또한 계산 오버 헤드를 줄일 수 있습니다 MAX(id). 응용 프로그램에 따라이 문제는 매우 중요하거나 거의 중요하지 않을 수 있습니다.

이것은 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

그래서 내가 변경해야합니다, 30 개 임의의 기록을 필요 LIMIT 1LIMIT 30쿼리에서 사방
Hassaan

당신이 안 @Hassaan이 변화 LIMIT 1하는 LIMIT 30테이블의 임의의 점에서 당신에게 연속 (30 개) 기록을 얻을 것입니다. 대신 (SELECT id FROM ....중간에 부품의 사본 30 개가 있어야합니다 .
Hans Z

나는 노력했지만 더 효율적으로 보이지 않습니다 Riedsio. 나는 centos 7에서 PHP 7.0.22와 MariaDB를 사용하여 초당 500 페이지의 히트를 시도했지만 Riedsio대답은 500 + 여분의 성공적인 응답을 얻었습니다.
Hassaan

1
@Hassaan riedsio의 대답은 1 행을 제공합니다.이 행은 n 행을 제공하고 쿼리를 위해 I / O 오버 헤드를 줄입니다. 행을 더 빨리 얻을 수 있지만 시스템에 더 많은로드가있을 수 있습니다.
한스 Z

3

Riedsio가 게시 한이 http://jan.kneschke.de/projects/mysql/order-by-rand/를 사용했습니다 (하나 이상의 임의의 값을 반환하는 저장 프로 시저의 경우를 사용했습니다).

   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;

이 기사에서 그는 테이블을 유지함으로써 트리거 결과 등무작위로 생성하지 않는 id 의 간격 문제를 해결 합니다 (트리거 등을 사용하여 기사 참조). 1부터 시작하여 연속적인 숫자로 채워진 다른 열을 테이블에 추가하여 문제를 해결하고 있습니다 ( 편집 : 이 열은 런타임시 하위 쿼리에 의해 생성 된 임시 테이블에 추가되며 영구 테이블에는 영향을 미치지 않습니다).

   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;

이 기사에서는 코드를 최적화하기 위해 많은 시간을 보냈습니다. 변경 사항이 성능에 영향을 미치지 만 나에게 매우 잘 작동한다면 아이디어가 없습니다.


"내 변화가 성능에 얼마나 영향을 미치는지에 대한 아이디어는 없습니다"-상당히. 를 들어 @no_gaps_id어떤 인덱스를 사용할 수 있습니다 당신이 보면, 그래서 EXPLAIN당신의 쿼리에 대한, 당신은 Using filesort하고 Using where원래의 질의에 대비, 하위 쿼리에 대한 (인덱스 제외).
Fabian Schmengler

2

여기 많은 사람들에게 도움이 될 수있는 게임 체인저가 있습니다.

200k 개의 행이있는 테이블이 있고 순차 ID가 N 개의 임의의 행 을 선택해야 하므로 테이블에서 가장 큰 ID를 기반으로 임의의 값을 생성하도록 선택하면이 스크립트가 가장 빠른 작업인지 확인합니다.

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.8418693542479ms
  • 최대 : 0.241041183472ms
  • 주문 : 0.216960906982ms

이 결과를 바탕으로 order desc는 최대 id를 얻는 가장 빠른 작업입니다.
다음은 질문에 대한 답변입니다.

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);

참고하십시오 200K 테이블에서 10 개 임의의 행을 얻으려면, 그것은 나에게 1.78했다 MS (PHP는 측면에서 모든 작업을 포함하여)


3
LIMIT약간 늘리는 것이 좋습니다. 복제본을 얻을 수 있습니다.
Rick James

2

이것은 매우 빠르며 간격이 있어도 100 % 무작위입니다.

  1. x사용 가능한 행 수 를 계산하십시오.SELECT COUNT(*) as rows FROM TABLE
  2. a_1,a_2,...,a_100과 0 사이의 10 개의 고유 난수를 선택하십시오.x
  3. 다음과 같이 행을 쿼리하십시오. SELECT * FROM TABLE LIMIT 1 offset a_ifor i = 1, ..., 10

나는이 책이 해킹 발견 SQL 안티 패턴 에서 빌 Karwin을 .


나는 같은 해결책에 대해 생각하고 있었다. 다른 방법보다 빠르다.
G. Adnane

@ G.Adnane은 허용 된 답변보다 빠르거나 느리지 않지만 허용되는 답변은 id의 동일한 분포를 가정합니다. 이것이 보장 될 수있는 시나리오를 상상할 수 없습니다. 이 솔루션은 SELECT column FROM table ORDER BY RAND() LIMIT 10O (nlog (n))에 있는 O (1)에 있습니다. 예, 이것은 빠른 해결책이며 모든 ID 배포에 작동합니다.
Adam

아니요, 허용 된 솔루션에 게시 된 링크에 다른 방법이 있기 때문에이 솔루션이 다른 솔루션보다 빠르지 않은지 알고 싶습니다. 다른 방법으로, 다른 방법을 찾으려고 노력할 수 있습니다. 당신의 대답을 위해. 나는 같은 것을 사용하고 있었다
G. Adnane

x 개의 행 수를 가져오고 싶지만 오프셋이 <x 행 또는 1 행만 반환하는 테이블의 끝으로 이동하는 경우가 있습니다. 내가 게시하기 전에 귀하의 답변을 보지 못했지만 여기에서 더 명확하게했습니다. stackoverflow.com/a/59981772/10387008
ZOLDIK

@ ZOLDIK 오프셋 후 처음 10 행을 선택하는 것 같습니다 x. 나는 이것이 10 행의 무작위 생성이 아니라고 주장합니다. 내 대답에 따르면 3 단계에서 10 번 쿼리를 실행해야합니다. 즉, 하나는 실행 당 하나의 행만 가져오고 오프셋이 테이블의 끝에 있는지 걱정할 필요가 없습니다.
아담

1

읽기 요청이 하나만있는 경우

@redsio의 대답을 임시 테이블과 결합하십시오 (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 Answer 버전을 가져옵니다.

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라고 부를 수 있습니다. 구멍이 생길 수 있으므로 특정 간격 (일, 시간)으로 해당 테이블을 다시 작성하십시오. 테이블이 정말 커지면 구멍을 리필 할 수도 있습니다

    datatable_idlist에서 l.data_id를 전체로 선택합니다. l dt.id = l.data_id에서 왼쪽 조인 datatable dt를 선택합니다.

  2. 버전 : 데이터 세트에 직접 또는 영구적 인 추가 테이블에 임의의 sort_sortorder 열을 데이터 세트에 제공하십시오 datatable_sortorder. 해당 열을 색인화하십시오. 응용 프로그램에서 임의의 값을 생성하십시오 (이하라고 부릅니다 $rand).

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

이 솔루션은 '가장자리 행'을 가장 높고 가장 낮은 random_sortorder로 구분하므로 간격 (하루에 한 번)으로 다시 정렬하십시오.


1

또 다른 간단한 솔루션은 행의 순위를 매기고 그중 하나를 임의로 가져 오는 것입니다.이 솔루션을 사용하면 테이블에 '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;

1

자동 생성 된 ID가있는 경우 꽤 좋은 방법은 모듈로 연산자 '%'를 사용하는 것입니다. 예를 들어, 70,000 개 중 10,000 개의 임의 레코드가 필요한 경우 7 개 행 중 1 개가 필요하다고 말하면이를 단순화 할 수 있습니다. 이 쿼리에서이를 단순화 할 수 있습니다.

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;

이것은 전체 스캔을 필요로하지만 ORDER BY RAND보다 빠르며 내 의견으로는이 스레드에서 언급 된 다른 옵션보다 이해하기가 더 간단합니다. 또한 DB에 쓰는 시스템이 배치로 행 세트를 작성하는 경우 예상 한대로 임의의 결과를 얻지 못할 수 있습니다.


2
이제는 그렇게 생각할 때마다 호출 할 때마다 임의의 행이 필요하면 쓸모가 없습니다. 연구를하기 위해 세트에서 임의의 행을 가져와야 할 필요성에 대해서만 생각하고있었습니다. 나는 여전히 모듈로가 다른 경우에 도움이되는 좋은 것이라고 생각합니다. ORDER BY RAND 작업의 비용을 낮추기 위해 모듈로를 첫 번째 통과 필터로 사용할 수 있습니다.
Nicolas Cohen


1

나는 모든 대답을 살펴 보았지만 아무도이 가능성에 대해 언급하지 않았으며 그 이유를 모르겠습니다.

약간의 비용으로 최대한의 단순성과 속도를 원한다면 DB의 각 행에 대해 난수를 저장하는 것이 합리적입니다. 추가 열을 만들고 random_number기본값을로 설정하십시오 RAND(). 이 열에 색인을 작성하십시오.

그런 다음 행을 검색 할 때 코드에서 임의의 숫자 (PHP, Perl 등)를 생성하고 열과 비교하십시오.

SELECT FROM tbl WHERE random_number >= :random LIMIT 1

하나의 행에 대해 매우 깔끔하지만 OP와 같은 10 개의 행에 대해 10 개의 별도 시간을 호출해야한다고 요청했습니다 (또는 즉시 나를 탈출하는 영리한 조정을해야합니다)


이것은 실제로 매우 훌륭하고 효율적인 접근 방식입니다. 유일한 단점은 속도를 위해 공간을 교환했다는 사실입니다.
Tochukwu Nkemdilim

감사. 나는 임의의 행을 원했던 메인 테이블에 5 백만 개의 행과 상당히 많은 조인이있는 시나리오를 가지고 있었고이 질문에 대한 대부분의 접근법을 시도한 후에 이것이 내가 해결 한 결점이었습니다. 하나의 추가 칼럼은 매우 가치있는 트레이드 오프였습니다.
Codemonkey

0

다음은 빠르고 편견이 없으며 id 열과 독립적이어야합니다. 그러나 리턴 된 행 수가 요청 된 행 수와 일치한다고 보장하지는 않습니다.

SELECT *
FROM t
WHERE RAND() < (SELECT 10 / COUNT(*) FROM t)

설명 : 100 개 중 10 개의 행을 원한다고 가정하면 각 행은에 의해 달성 될 수있는 SELECTed를 얻을 확률이 1/10 WHERE RAND() < 0.1입니다. 이 방법은 10 개의 행을 보장하지 않습니다. 그러나 쿼리가 충분한 횟수로 실행되면 실행 당 평균 행 수는 약 10이되고 테이블의 각 행은 균등하게 선택됩니다.


0

한계가있는 임의의 오프셋을 쉽게 사용할 수 있습니다

PREPARE stm from 'select * from table limit 10 offset ?';
SET @total = (select count(*) from table);
SET @_offset = FLOOR(RAND() * @total);
EXECUTE stm using @_offset;

where 절을 적용 할 수도 있습니다.

PREPARE stm from 'select * from table where available=true limit 10 offset ?';
SET @total = (select count(*) from table where available=true);
SET @_offset = FLOOR(RAND() * @total);
EXECUTE stm using @_offset;

600,000 행 (700MB) 테이블 쿼리 실행시 ~ 0.016 초 HDD 드라이브에서

테스트 --EDIT--
   오프셋은 테이블의 끝 부분에 가까운 값을 가져 와서 select 문이 적은 행을 반환합니다. 이것을 피하기 위해 offset선언 후 다시 확인할 수 있습니다.

SET @rows_count = 10;
PREPARE stm from "select * from table where available=true limit ? offset ?";
SET @total = (select count(*) from table where available=true);
SET @_offset = FLOOR(RAND() * @total);
SET @_offset = (SELECT IF(@total-@_offset<@rows_count,@_offset-@rows_count,@_offset));
SET @_offset = (SELECT IF(@_offset<0,0,@_offset));
EXECUTE stm using @rows_count,@_offset;

-1

이 쿼리를 사용합니다.

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

쿼리 시간 : 0.016s


1,2,9,15와 같은 PK가 있습니다. 위의 쿼리로 4, 7, 14, 11과 같은 행이 부족합니다!
Junaid Atari

-2

이것이 내가하는 방법입니다.

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

나는 다른 테이블을 필요로하지 않기 때문에 좋아합니다. 쓰기가 쉽고 실행이 매우 빠릅니다.


5
그것은 전체 테이블 스캔이며 인덱스를 사용하지 않습니다. 큰 테이블과 번잡 한 환경에 적합합니다.
matt

-2

아래의 간단한 쿼리를 사용하여 테이블에서 임의의 데이터를 가져옵니다.

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

모든 join 문을 사용하려는 경우 필터를 사용할 수 있습니다.
MANOJ

3
쿼리의 어느 부분에서 무작위성을 얻습니까?
Marki555

-4

이것이 최선의 방법이라고 생각합니다 ..

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

8
지옥 아니, 그것은 테이블에서 임의의 행을 얻는 최악의 방법 중 하나입니다. 그것은 전체 테이블 스캔 + filesort + tmp table = 나쁜 성능입니다.
matt

1
성능 외에도 완벽하게 임의적 인 것은 아닙니다. 단순히 난수로 주문하는 대신 ID와 난수의 곱으로 주문합니다. 즉, 더 낮은 ID를 가진 행이 결과 세트의 초기에 나타나도록 편향됩니다.
Mark Amery
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.