MySQL เลือกสุ่ม 10 แถวจาก 600K แถวอย่างรวดเร็ว


463

ฉันจะเขียนแบบสอบถามที่ดีที่สุดที่เลือก 10 แถวสุ่มจากทั้งหมด 600k ได้อย่างไร


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 ของคุณคนแรกจะได้รับ (min / max-min) ของเวลา สำหรับกรณีนี้การปรับแต่งง่าย ๆ คือ MAX () - MIN () * RAND + MIN () ซึ่งไม่ช้าเกินไป
รหัส

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

ไม่ใช่วิธีที่มีประสิทธิภาพ แต่ใช้ได้ผล


139
ORDER BY RAND()ค่อนข้างช้า
Mateusz Charytoniuk

7
มาเตซัส - หลักฐาน pls SELECT words, transcription, translation, sound FROM vocabulary WHERE menu_id=$menuId ORDER BY RAND() LIMIT 10ใช้เวลา 0.0010 โดยไม่ จำกัด 10 เอา 0.0012 (ในตารางที่ 3500 คำ)
Arthur Kushman

26
@zeusakm 3,500 คำนั้นไม่มากนัก ปัญหาคือว่ามันระเบิดที่ผ่านจุดหนึ่งเพราะ MySQL จะต้องเรียงลำดับระเบียนทั้งหมดหลังจากอ่านแต่ละ; เมื่อการดำเนินการดังกล่าวกระทบกับฮาร์ดดิสก์คุณจะรู้สึกถึงความแตกต่าง
Ja͢ck

16
ฉันไม่ต้องการพูดซ้ำอีก แต่เป็นการสแกนแบบเต็มตาราง บนโต๊ะขนาดใหญ่นั้นใช้เวลาและหน่วยความจำมากและอาจทำให้เกิดการสร้าง & การดำเนินการบนตารางชั่วคราวบนดิสก์ซึ่งช้ามาก
แมตต์

10
เมื่อฉันสัมภาษณ์ Facebook ในปี 2010 พวกเขาถามฉันถึงวิธีการเลือกบันทึกแบบสุ่มจากไฟล์ขนาดใหญ่ที่ไม่รู้จักในการอ่านครั้งเดียว เมื่อคุณคิดไอเดียมันเป็นเรื่องง่ายที่จะทำให้เป็นเรื่องทั่วไปสำหรับการเลือกหลาย ๆ ระเบียน ใช่การเรียงไฟล์ทั้งหมดนั้นไร้สาระ ในเวลาเดียวกันมันมีประโยชน์มาก ฉันใช้วิธีนี้เพื่อเลือก 10 แถวสุ่มจากตารางที่มี 1,000,000 + แถว แน่นอนฉันต้องรอสักครู่; แต่ผมแค่อยากจะได้รับความคิดสิ่งที่แถวทั่วไปในตารางนี้ดูเหมือนว่า ...
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 * จาก tbl ORDER BY RAND () LIMIT 10) ใช้เวลา0.35 วินาทีในเครื่องของฉัน

สิ่งนี้เร็วเพราะเฟสการเรียงลำดับใช้คอลัมน์ ID ที่จัดทำดัชนี คุณสามารถดูพฤติกรรมนี้ได้ในคำอธิบาย:

เลือก * จากคำสั่ง tbl ตามแรนด์ () จำกัด 10: อธิบายอย่างง่าย

SELECT * จาก tbl AS t1 เข้าร่วม (เลือก id จาก tbl เรียงลำดับตามแรนด์ () จำกัด 10) เป็น t2 บน t1.id = t2.id ป้อนคำอธิบายรูปภาพที่นี่

เวอร์ชันถ่วงน้ำหนัก : https://stackoverflow.com/a/41577458/893432


1
ขออภัยฉันทดสอบ! ประสิทธิภาพช้าในการบันทึก 600k
Dylan B

@DylanB ฉันปรับปรุงคำตอบด้วยการทดสอบ
อาลี

17

ฉันได้รับข้อความค้นหาอย่างรวดเร็ว (ประมาณ 0.5 วินาที) ด้วยซีพียูช้าเลือก 10 แถวแบบสุ่มใน 400K ลงทะเบียนฐานข้อมูล MySQL ขนาดแคช 2Gb ไม่ลงทะเบียน ดูที่นี่รหัสของฉัน: เลือกแถวสุ่มใน 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
เนื่องจากตารางบันทึกของฉันมีมากกว่า 14 ล้านแผ่นนี่ก็ช้าเท่ากับORDER BY RAND()
Fabrizio

5
@snippetsofcode ในกรณีของคุณ - 400 พันแถวคุณสามารถใช้ "ORDER BY rand ()" แบบง่าย เคล็ดลับของคุณที่มี 3 ข้อความค้นหานั้นไร้ประโยชน์ คุณสามารถเขียนซ้ำได้เช่น "SELECT id, url จากหน้า WHERE ID IN (SELECT id จากหน้าเรียงตาม rand () LIMIT 10)"
Roman Podlinov

4
เทคนิคของคุณยังสแกนตาราง ใช้FLUSH STATUS; SELECT ...; SHOW SESSION STATUS LIKE 'Handler%';เพื่อดูมัน
Rick James

4
ลองเรียกใช้แบบสอบถามนั้นใน 200 req / s เว็บเพจ การเกิดขึ้นพร้อมกันจะฆ่าคุณ
Marki555

@RomanPodlinov ประโยชน์ของสิ่งนี้เหนือธรรมดาORDER BY RAND()คือมันเรียงลำดับรหัส (ไม่ใช่แถวเต็ม) ดังนั้นตาราง temp จึงมีขนาดเล็กลง แต่ยังต้องเรียงลำดับทั้งหมด
Marki555

16

มันง่ายมากและแบบสอบถามบรรทัดเดียว

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

21
FYI order by rand()ช้ามากถ้าตารางมีขนาดใหญ่
evilReiko

6
บางครั้งความช้าก็เป็นที่ยอมรับถ้าฉันต้องการเก็บมันไว้อย่างง่าย

การสร้างดัชนีควรนำไปใช้กับตารางหากมีขนาดใหญ่
มูฮัมหมัดอาเซม

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(*)ช้า
Hans Z

7

วิธีเลือกสุ่มแถวจากตาราง:

จากที่นี่: เลือกแถวสุ่มใน MySQL

การปรับปรุงอย่างรวดเร็วผ่าน "การสแกนตาราง" คือการใช้ดัชนีเพื่อรับรหัสสุ่ม

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 () นั้นดีที่สุดที่คุณจะได้รับถ้าคุณต้องการสุ่ม 10 แถว

มีวิธีแก้ไขปัญหาอื่นที่รวดเร็ว แต่ยังมีการแลกเปลี่ยนเมื่อมันมาถึงการสุ่ม แต่อาจเหมาะกับคุณดีกว่า อ่านเกี่ยวกับที่นี่: ฉันจะเพิ่มประสิทธิภาพฟังก์ชั่น ORDER BY RAND () ของ MySQL ได้อย่างไร?

คำถามคือวิธีการสุ่มที่คุณต้องการให้เป็น

คุณช่วยอธิบายเพิ่มเติมอีกหน่อยได้ไหมเพื่อให้ฉันได้คำตอบที่ดี

ตัวอย่างเช่น บริษัท ที่ฉันทำงานด้วยมีวิธีแก้ปัญหาที่พวกเขาต้องการการสุ่มแบบเร็วมากอย่างรวดเร็ว พวกเขาลงเอยด้วยการเติมฐานข้อมูลล่วงหน้าด้วยค่าสุ่มที่เลือกจากมากไปน้อยและตั้งค่าเป็นค่าสุ่มต่าง ๆ หลังจากนั้นอีกครั้ง

หากคุณไม่ได้อัปเดตคุณสามารถใส่รหัสที่เพิ่มขึ้นเพื่อที่คุณจะได้ไม่มีช่องว่างและสามารถคำนวณปุ่มสุ่มก่อนที่จะเลือก ... ขึ้นอยู่กับกรณีการใช้งาน!


สวัสดีโจ ในกรณีนี้คีย์ไม่ควรขาดช่องว่าง แต่เมื่อเวลาผ่านไปอาจมีการเปลี่ยนแปลง และในขณะที่คำตอบของคุณทำงานมันจะสร้างแบบสุ่ม 10 แถว (หากฉันเขียนขีด จำกัด 10) ที่ต่อเนื่องกันและฉันต้องการการสุ่มมากขึ้นเพื่อที่จะพูด :) ขอบคุณ.
Francisc

หากคุณต้องการ 10 ใช้สหภาพบางอย่างเพื่อสร้าง 10 แถวที่ไม่ซ้ำกัน
johno

สิ่งที่ฉันพูด คุณต้องดำเนินการนั้น 10 ครั้ง การรวมยูเนี่ยน wition เป็นวิธีหนึ่งที่จะใส่ไว้ในแบบสอบถามเดียว ดูภาคผนวกของฉัน 2 นาทีที่แล้ว
Surrican

1
@TheSurrican, การแก้ปัญหานี้ก็ดูดี แต่มีข้อบกพร่องอย่างมาก ลองใส่ข้อความค้นหาที่มีขนาดใหญ่มากเพียงรายการเดียวIdและแบบสอบถามแบบสุ่มทั้งหมดของคุณจะส่งคืนข้อความIdนั้น
Pacerier

1
FLOOR(RAND()*MAX(id))มีอคติต่อการส่งคืนรหัสที่ใหญ่กว่า
Rick James

3

ฉันต้องการแบบสอบถามเพื่อส่งคืนแถวสุ่มจำนวนมากจากตารางที่ค่อนข้างใหญ่ นี่คือสิ่งที่ฉันมาด้วย อันดับแรกรับรหัสบันทึกสูงสุด:

SELECT MAX(id) FROM table_name;

จากนั้นแทนที่ค่านั้นเป็น:

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

โดยที่ max คือ id บันทึกสูงสุดในตารางและ n คือจำนวนแถวที่คุณต้องการในชุดผลลัพธ์ ข้อสันนิษฐานคือว่าไม่มีช่องว่างใน id ของบันทึกแม้ว่าฉันสงสัยว่ามันจะส่งผลกระทบต่อผลถ้ามี (ยังไม่ได้ลองแม้ว่า) ฉันยังสร้างกระบวนงานที่เก็บไว้นี้ให้เป็นแบบทั่วไปมากขึ้น ผ่านในชื่อตารางและจำนวนแถวที่จะส่งคืน ฉันใช้ MySQL 5.5.38 ใน Windows 2008, 32GB, 3GHz คู่ E5450 และบนโต๊ะที่มี 17,361,264 แถวมันค่อนข้างสอดคล้องกันที่ ~ .03 วินาที / ~ 11 วินาทีเพื่อกลับ 1,000,000 แถว (เวลามาจาก MySQL Workbench 6.1 คุณสามารถใช้ CEIL แทน FLOOR ในคำสั่ง select ที่ 2 ขึ้นอยู่กับความต้องการของคุณ)

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 วินาที) ไม่สำคัญว่าผู้เข้าชมที่ไม่ซ้ำ 100 คนใน 1 วินาทีเดียวกันจะได้รับโพสต์แบบสุ่มหรือไม่เพราะผู้เข้าชม 100 คนต่อมาจะได้รับโพสต์ชุดอื่น

เมื่อใช้แคชนี้คุณสามารถใช้วิธีการช้าลงบางส่วนในการรับข้อมูลแบบสุ่มเนื่องจากจะถูกดึงจาก MySQL เพียงหนึ่งครั้งต่อวินาทีโดยไม่คำนึงถึงความต้องการของคุณ


3

ฉันปรับปรุงคำตอบที่ @Riedsio ได้ นี่เป็นแบบสอบถามที่มีประสิทธิภาพที่สุดที่ฉันสามารถหาได้ในตารางที่มีขนาดใหญ่และกระจายอย่างสม่ำเสมอโดยมีช่องว่าง (ทดสอบกับการรับ 1,000 แถวสุ่มจากตารางที่มี> 2.6B แถว)

(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)
    • รับรหัสสุ่ม
  3. SELECT id FROM table INNER JOIN (...) on id > rand LIMIT 1
    • นี้จะเติมเต็มในช่องว่าง โดยทั่วไปถ้าคุณสุ่มเลือกตัวเลขในช่องว่างมันก็จะเลือก ID ต่อไป สมมติว่าช่องว่างมีการกระจายอย่างสม่ำเสมอนี่จะไม่เป็นปัญหา

การทำสหภาพช่วยให้คุณพอดีทุกอย่างเป็น 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

ฉันต้องการบันทึกแบบสุ่ม 30 รายการดังนั้นฉันควรเปลี่ยนLIMIT 1ไปLIMIT 30ทุกที่ในแบบสอบถาม
Hassaan

@Hassaan คุณไม่ควรที่LIMIT 1จะเปลี่ยนให้LIMIT 30คุณได้รับ 30 ระเบียนในแถวจากจุดสุ่มในตาราง คุณควรมี(SELECT id FROM ....ส่วนที่อยู่ตรงกลาง30 สำเนา
ฮันส์ Z

ฉันได้ลองแล้ว แต่ดูเหมือนจะไม่มีประสิทธิภาพมากกว่านี้แล้วRiedsioตอบ ฉันได้ลองกับการเข้าชม 500 ครั้งต่อวินาทีไปที่หน้าโดยใช้ PHP 7.0.22 และ MariaDB บน ​​centos 7 ด้วยRiedsioคำตอบฉันได้รับการตอบกลับที่ประสบความสำเร็จมากกว่า 500+ ครั้งจากคำตอบของคุณ
Hassaan

1
คำตอบของ @Hassaan riedsio ให้ 1 แถวอันนี้ให้แถว n คุณรวมทั้งลดค่าใช้จ่าย I / O สำหรับการสืบค้น คุณอาจสามารถเพิ่มจำนวนแถวได้เร็วขึ้น แต่มีภาระมากขึ้นในระบบของคุณ
ฮันส์ Z

3

ฉันใช้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 ( แก้ไข:คอลัมน์นี้ถูกเพิ่มในตารางชั่วคราวที่สร้างโดยเคียวรีย่อยที่รันไทม์ไม่ส่งผลกระทบต่อตารางถาวรของคุณ):

   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 เรียงเป็นการดำเนินการที่เร็วที่สุดเพื่อรับ max 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);

FYI: หากต้องการรับ 10 แถวสุ่มจากตาราง 200k ฉันใช้เวลา 1.78 ms (รวมถึงการดำเนินการทั้งหมดในด้าน php)


3
ขอแนะนำให้คุณเพิ่มLIMITเล็กน้อย - คุณสามารถได้รับซ้ำ
Rick James

2

นี่คือเร็วสุดและสุ่ม 100% แม้ว่าคุณจะมีช่องว่าง

  1. นับจำนวนxแถวที่คุณมีSELECT COUNT(*) as rows FROM TABLE
  2. เลือก 10 ตัวเลขสุ่มที่แตกต่างกันa_1,a_2,...,a_10ระหว่าง 0 ถึงx
  3. ค้นหาแถวของคุณดังนี้: SELECT * FROM TABLE LIMIT 1 offset a_iสำหรับ i = 1, ... , 10

ผมพบว่าสับนี้ในหนังสือSQL Antipatternsจากบิล Karwin


ฉันคิดเกี่ยวกับวิธีการแก้ปัญหาเดียวกันโปรดบอกฉันว่าเร็วกว่าวิธีอื่นหรือไม่
G. Adnane

@ G.Adnane ไม่เร็วหรือช้ากว่าคำตอบที่ยอมรับ แต่คำตอบที่ยอมรับถือว่าการกระจาย ID ที่เท่ากัน ฉันไม่สามารถจินตนาการถึงสถานการณ์ที่รับประกันได้ โซลูชันนี้อยู่ใน O (1) โดยที่โซลูชันSELECT column FROM table ORDER BY RAND() LIMIT 10อยู่ใน O (nlog (n)) ใช่แล้วนี่คือทางออกที่รวดเร็วและใช้ได้กับการแจกแจงรหัสต่างๆ
Adam

ไม่เพราะในลิงก์ที่โพสต์สำหรับโซลูชันที่ยอมรับมีวิธีอื่นฉันต้องการทราบว่าวิธีนี้เร็วกว่าวิธีอื่น ๆ หรือวิธีอื่นเราสามารถลองหาวิธีอื่นได้นั่นคือสาเหตุที่ IAM ถามไม่ทางใด ๆ +1 สำหรับคำตอบของคุณ ฉันใช้สิ่ง
G. Adnane

มีกรณีเมื่อคุณต้องการรับจำนวนแถว x แต่ชดเชยไปที่จุดสิ้นสุดของตารางซึ่งจะส่งกลับ <x แถวหรือเพียง 1 แถว ฉันไม่เห็นคำตอบของคุณก่อนที่จะโพสต์ของฉัน แต่ฉันทำให้ชัดเจนขึ้นที่นี่stackoverflow.com/a/59981772/10387008
ZOLDIK

@ZOLDIK ดูเหมือนว่าคุณเลือก 10 xแถวแรกหลังจากการชดเชย ฉันจะยืนยันว่านี่ไม่ใช่รุ่นสุ่ม 10 แถว ในคำตอบของฉันคุณต้องดำเนินการแบบสอบถามในขั้นตอนที่สาม 10 ครั้งคือหนึ่งได้รับเพียงหนึ่งแถวต่อการดำเนินการและไม่ต้องกังวลหากออฟเซตอยู่ท้ายตาราง
อดัม

1

หากคุณมีคำขออ่านเพียงครั้งเดียว

รวมคำตอบของ @redsio เข้ากับ temp-table (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 เป็นโมฆะ

  2. เวอร์ชัน: ให้ชุดข้อมูลคอลัมน์ random_sortorder ทั้งโดยตรงใน DataTable datatable_sortorderหรือในตารางพิเศษถาวร ดัชนีคอลัมน์นั้น สร้างค่าสุ่มในใบสมัครของคุณ (ฉันจะเรียกมันว่า$rand)

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

วิธีการแก้ปัญหานี้จำแนก 'แถวของขอบ' ด้วยการสุ่มเรียงลำดับสูงสุดและต่ำสุดดังนั้นจัดเรียงใหม่ตามช่วงเวลา (วันละครั้ง)


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

วิธีหนึ่งที่ฉันพบว่าค่อนข้างดีถ้ามีรหัสที่สร้างอัตโนมัติคือใช้ตัวดำเนินการโมดูโล '%' ตัวอย่างเช่นถ้าคุณต้องการ 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;

สิ่งนี้ต้องใช้การสแกนแบบเต็ม แต่เร็วกว่า ORDER BY RAND และในความคิดของฉันง่ายต่อการเข้าใจมากกว่าตัวเลือกอื่น ๆ ที่กล่าวถึงในหัวข้อนี้ นอกจากนี้หากระบบที่เขียนไปยัง DB สร้างชุดของแถวเป็นชุดคุณอาจไม่ได้รับผลลัพธ์แบบสุ่มตามที่คุณคาดหวัง


2
ตอนนี้ฉันคิดอย่างนั้นถ้าคุณต้องการแถวสุ่มทุกครั้งที่คุณเรียกมันว่ามันไร้ประโยชน์ ฉันแค่คิดเกี่ยวกับความต้องการที่จะได้รับแถวสุ่มจากชุดที่จะทำวิจัยบางอย่าง ฉันยังคิดว่า modulo เป็นสิ่งที่ดีที่จะช่วยในกรณีอื่น ๆ คุณสามารถใช้โมดูโล่เป็นตัวกรองผ่านแรกเพื่อลดต้นทุนของการดำเนินการ ORDER BY RAND
นิโคลัสโคเฮน

1

หากคุณต้องการหนึ่งระเบียนสุ่ม (ไม่ว่าจะมีช่องว่างระหว่างรหัส):

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


1

ฉันได้ดูคำตอบทั้งหมดแล้วและฉันไม่คิดว่าจะมีใครพูดถึงความเป็นไปได้ทั้งหมดและฉันไม่แน่ใจว่าทำไม

หากคุณต้องการความเรียบง่ายและความเร็วสูงสุดด้วยค่าใช้จ่ายเล็กน้อยสำหรับฉันแล้วดูเหมือนว่าคุณควรเก็บตัวเลขสุ่มไว้กับแต่ละแถวในฐานข้อมูล เพียงแค่สร้างคอลัมน์พิเศษrandom_number, RAND()และการตั้งค่าเริ่มต้นของการ สร้างดัชนีในคอลัมน์นี้

จากนั้นเมื่อคุณต้องการเรียกแถวสร้างตัวเลขสุ่มในรหัสของคุณ (PHP, Perl, อะไรก็ตาม) และเปรียบเทียบกับคอลัมน์

SELECT FROM tbl WHERE random_number >= :random LIMIT 1

ฉันเดาว่าถึงแม้มันจะเป็นระเบียบสำหรับแถวเดียว แต่สำหรับสิบแถวอย่าง OP ขอให้คุณเรียกมันว่าสิบครั้งแยกกัน


นี่เป็นวิธีที่ดีและมีประสิทธิภาพจริงๆ ข้อเสียเปรียบเพียงอย่างเดียวคือความจริงที่ว่าคุณแลกเปลี่ยนพื้นที่เพื่อความเร็วซึ่งดูเหมือนว่าเป็นข้อตกลงที่ยุติธรรมในความคิดของฉัน
Tochukwu Nkemdilim

ขอบคุณ ฉันมีสถานการณ์ที่ตารางหลักที่ฉันต้องการแถวแบบสุ่มจากมี 5 ล้านแถวและค่อนข้างมากเข้าร่วมและหลังจากลองวิธีการส่วนใหญ่ในคำถามนี้นี่คือกระบองที่ฉันตัดสิน หนึ่งคอลัมน์พิเศษคือการแลกเปลี่ยนที่คุ้มค่ามากสำหรับฉัน
Codemonkey

0

ต่อไปนี้ควรเป็นไปอย่างรวดเร็วไม่ลำเอียงและเป็นอิสระจากคอลัมน์ id อย่างไรก็ตามไม่รับประกันว่าจำนวนแถวที่ส่งคืนจะตรงกับจำนวนแถวที่ร้องขอ

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

คำอธิบาย: สมมติว่าคุณต้องการ 10 แถวจาก 100 แล้วแต่ละแถวมี 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;

นอกจากนี้คุณยังสามารถใช้ประโยคที่เป็นเช่นนี้

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.016sec ฮาร์ดดิสก์ไดรฟ์

--EDIT--
   ชดเชยอาจใช้ค่าที่ใกล้ถึงจุดสิ้นสุดของตารางซึ่งจะส่งผลให้คำสั่งเลือกกลับมาแถวน้อย (หรืออาจเพียง 1 แถว) เพื่อหลีกเลี่ยงปัญหานี้เราสามารถตรวจสอบ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


มี PKs เช่น 1,2,9,15 โดยแบบสอบถามด้านบนคุณจะได้รับแถวเช่น 4, 7, 14, 11 ซึ่งไม่เพียงพอ!
Junaid Atari

-2

นี่คือวิธีที่ฉันทำ:

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

ฉันชอบเพราะไม่จำเป็นต้องใช้ตารางอื่น ๆ มันง่ายต่อการเขียนและสามารถดำเนินการได้อย่างรวดเร็ว


5
นั่นเป็นการสแกนแบบเต็มตารางและไม่ได้ใช้ดัชนีใด ๆ สำหรับตารางขนาดใหญ่และสภาพแวดล้อมที่ไม่ว่างนั้นใหญ่ไม่มาก
แมตต์

-2

ใช้แบบสอบถามง่ายๆด้านล่างเพื่อรับข้อมูลแบบสุ่มจากตาราง

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

หากคุณต้องการใช้คำสั่งเข้าร่วมใด ๆ และตัวกรองที่คุณสามารถใช้
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 และหมายเลขสุ่มแทนที่จะสั่งซื้อด้วยหมายเลขสุ่มซึ่งหมายความว่าแถวที่มีรหัสต่ำกว่าจะมีอคติต่อการปรากฏก่อนหน้านี้ในชุดผลลัพธ์ของคุณ
Mark Amery
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.