MySQL memilih 10 baris acak dari 600 ribu baris dengan cepat

463

Bagaimana cara terbaik menulis kueri yang memilih 10 baris secara acak dari total 600k?

Francisc
sumber
15
Inilah 8 teknik ; mungkin seseorang akan bekerja dengan baik dalam kasus Anda.
Rick James

Jawaban:

386

Pos besar yang menangani beberapa kasus, dari yang sederhana, hingga yang kosong, hingga yang tidak seragam dengan celah.

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

Untuk sebagian besar kasus umum, inilah cara Anda melakukannya:

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

Ini mengandaikan bahwa distribusi id adalah sama, dan bahwa mungkin ada kesenjangan dalam daftar id. Lihat artikel untuk contoh lebih lanjut

Riedsio
sumber
52
Ya, jika Anda memiliki celah besar dalam ID maka kemungkinan ID terendah Anda diambil secara acak jauh lebih rendah daripada ID tinggi Anda. Faktanya kesempatan bahwa ID pertama setelah jeda terbesar diambil sebenarnya yang tertinggi. Oleh karena itu ini tidak acak menurut definisi.
lukeocodes
6
Bagaimana Anda mendapatkan 10 baris acak berbeda? Apakah Anda harus menetapkan batas ke 10 dan kemudian mengulanginya 10 kali dengan mysqli_fetch_assoc($result)? Atau apakah 10 hasil itu belum tentu dapat dibedakan?
Adam
12
Acak membutuhkan peluang yang sama untuk hasil apa pun, dalam pikiranku. ;)
lukeocodes
4
Artikel lengkap membahas masalah-masalah seperti distribusi yang tidak merata dan hasil yang berulang.
Bradd Szonye
1
khususnya, jika Anda memiliki celah di awal ID Anda, yang pertama akan dipilih (min / maks-min) saat itu. Untuk kasus itu, tweak sederhana adalah MAX () - MIN () * RAND + MIN (), yang tidak terlalu lambat.
Code Abominator
343
SELECT column FROM table
ORDER BY RAND()
LIMIT 10

Bukan solusi yang efisien tetapi bekerja

Preetam Purbia
sumber
139
ORDER BY RAND()relatif lambat
Mateusz Charytoniuk
7
Mateusz - proof pls, SELECT words, transcription, translation, sound FROM vocabulary WHERE menu_id=$menuId ORDER BY RAND() LIMIT 10butuh 0,0010, tanpa LIMIT 10 butuh 0,0012 (dalam tabel itu 3500 kata).
Arthur Kushman
26
@zeusakm 3500 kata tidak banyak; masalahnya adalah ia meledak melewati titik tertentu karena MySQL harus benar-benar mengurutkan SEMUA catatan setelah membaca masing-masing; sekali operasi itu menyentuh hard disk Anda dapat merasakan perbedaannya.
Ja͢ck
16
Saya tidak ingin mengulang sendiri tetapi sekali lagi, itu adalah pemindaian tabel penuh. Di meja besar itu sangat memakan waktu dan memori dan dapat menyebabkan pembuatan & operasi di atas meja sementara pada disk yang sangat lambat.
matt
10
Ketika saya mewawancarai Facebook pada tahun 2010, mereka bertanya kepada saya bagaimana memilih catatan acak dari file besar dengan ukuran yang tidak diketahui, dalam satu bacaan. Setelah Anda menemukan sebuah ide, mudah untuk menggeneralisasikannya untuk memilih beberapa catatan. Jadi ya, menyortir seluruh file itu konyol. Pada saat yang sama, ini sangat berguna. Saya hanya menggunakan pendekatan ini untuk memilih 10 baris acak dari sebuah tabel dengan 1.000.000 + baris. Tentu, saya harus menunggu sedikit; tapi saya hanya ingin mendapatkan ide, seperti apa bentuk baris dalam tabel ini ...
osa
27

Permintaan sederhana yang memiliki kinerja luar biasa dan bekerja dengan kesenjangan :

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

Kueri ini pada tabel 200K memakan waktu 0,08s dan versi normal (PILIH * DARI ORDER tbl DENGAN RAND () LIMIT 10) membutuhkan 0,35s pada mesin saya.

Ini cepat karena fase pengurutan hanya menggunakan kolom ID yang diindeks. Anda dapat melihat perilaku ini di penjelasan:

PILIH * DARI Tbl ORDER OLEH RAND () LIMIT 10: Jelaskan Sederhana

SELECT * DARI tbl AS t1 BERGABUNG (SELECT id FROM tbl ORDER BY RAND () LIMIT 10) sebagai t2 ON t1.id = t2.id masukkan deskripsi gambar di sini

Versi Tertimbang : https://stackoverflow.com/a/41577458/893432

Ali
sumber
1
Maaf, saya diuji! kinerja lambat pada catatan 600 ribu.
Dylan B
@DylanB Saya memperbarui jawabannya dengan ujian.
Ali
17

Saya mendapatkan pertanyaan cepat (sekitar 0,5 detik) dengan cpu lambat , memilih 10 baris acak dalam 400K register database MySQL ukuran 2Gb non-cache. Lihat di sini kode saya: Pilihan cepat baris acak di 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);
?>
snippetsofcode
sumber
11
Dengan catatan saya lebih dari 14 juta tabel, ini sangat lambatORDER BY RAND()
Fabrizio
5
@snippetsofcode Dalam kasus Anda - 400k baris Anda dapat menggunakan "ORDER BY rand ()" sederhana. Trik Anda dengan 3 pertanyaan tidak berguna. Anda dapat menulis ulang seperti "SELECT id, url DARI halaman WHERE id IN (SELECT id DARI halaman ORDER BY rand () LIMIT 10)"
Roman Podlinov
4
Teknik Anda masih melakukan pemindaian tabel. Gunakan FLUSH STATUS; SELECT ...; SHOW SESSION STATUS LIKE 'Handler%';untuk melihatnya.
Rick James
4
Coba juga jalankan kueri itu di halaman web 200 req / s. Concurrency akan membunuhmu.
Marki555
@RomanPodlinov manfaat dari ini di atas dataran ORDER BY RAND()adalah bahwa ia hanya mengurutkan id (bukan baris penuh), sehingga tabel temp lebih kecil, tetapi masih harus mengurutkan semuanya.
Marki555
16

Permintaan baris yang sangat sederhana dan tunggal.

SELECT * FROM Table_Name ORDER BY RAND() LIMIT 0,10;
Muhammad Azeem
sumber
21
FYI, order by rand()sangat lambat jika meja besar
evilReiko
6
Terkadang SLOW diterima jika saya ingin tetap SEDERHANA
Pengindeksan harus diterapkan di atas meja jika besar.
Muhammad Azeem
1
Pengindeksan tidak akan membantu di sini. Indeks sangat membantu untuk hal-hal yang sangat spesifik, dan kueri ini bukan salah satunya.
Andrew
13

Dari buku:

Pilih Baris Acak Menggunakan Offset

Masih teknik lain yang menghindari masalah yang ditemukan dalam alternatif sebelumnya adalah menghitung baris dalam kumpulan data dan mengembalikan angka acak antara 0 dan hitungan. Kemudian gunakan nomor ini sebagai offset saat menanyakan kumpulan data

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

Gunakan solusi ini ketika Anda tidak dapat mengasumsikan nilai kunci yang berdekatan dan Anda perlu memastikan setiap baris memiliki peluang yang sama untuk dipilih.

zloctb
sumber
1
untuk tabel yang sangat besar, SELECT count(*)menjadi lambat.
Hans Z
7

Cara memilih baris acak dari tabel:

Dari sini: Pilih baris acak di MySQL

Peningkatan cepat atas "pemindaian tabel" adalah dengan menggunakan indeks untuk mengambil id acak.

SELECT *
FROM random, (
        SELECT id AS sid
        FROM random
        ORDER BY RAND( )
        LIMIT 10
    ) tmp
WHERE random.id = tmp.sid;
pengguna1931858
sumber
1
Itu membantu beberapa untuk MyISAM, tetapi tidak untuk InnoDB (dengan asumsi id adalah clustered PRIMARY KEY).
Rick James
7

Nah, jika Anda tidak memiliki celah pada kunci Anda dan semuanya berupa angka, Anda dapat menghitung angka acak dan memilih garis itu. tetapi ini mungkin tidak akan terjadi.

Jadi satu solusi adalah sebagai berikut:

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

yang pada dasarnya akan memastikan bahwa Anda mendapatkan nomor acak dalam kisaran kunci Anda dan kemudian Anda memilih yang terbaik berikutnya yang lebih besar. Anda harus melakukan ini 10 kali.

namun ini TIDAK benar-benar acak karena kunci Anda kemungkinan besar tidak akan didistribusikan secara merata.

Ini benar-benar masalah besar dan tidak mudah untuk menyelesaikan semua persyaratan, rand MySQL () adalah yang terbaik yang bisa Anda dapatkan jika Anda benar-benar menginginkan 10 baris acak.

Namun ada solusi lain yang cepat tetapi juga memiliki trade off ketika datang ke keacakan, tetapi mungkin lebih cocok untuk Anda. Baca tentang ini di sini: Bagaimana saya bisa mengoptimalkan fungsi ORDER BY RAND () MySQL?

Pertanyaannya adalah seberapa acak Anda membutuhkannya?

Bisakah Anda menjelaskan lebih banyak sehingga saya bisa memberikan solusi yang baik.

Sebagai contoh, sebuah perusahaan tempat saya bekerja memiliki solusi di mana mereka membutuhkan keacakan mutlak sangat cepat. Mereka berakhir dengan pra-mengisi database dengan nilai acak yang dipilih turun dan diatur ke nilai acak yang berbeda setelah itu lagi.

Jika Anda hampir tidak pernah memperbarui Anda juga bisa mengisi id tambahan sehingga Anda tidak memiliki celah dan hanya dapat menghitung kunci acak sebelum memilih ... Itu tergantung pada kasus penggunaan!

The Surrican
sumber
Hai Joe. Dalam kasus khusus ini kunci tidak boleh kekurangan celah, tetapi seiring waktu hal ini dapat berubah. Dan sementara jawaban Anda bekerja, itu akan menghasilkan 10 baris acak (asalkan saya menulis batas 10) yang berurutan dan saya ingin lebih banyak keacakan sehingga untuk berbicara. :) Terima kasih.
Francisc
Jika Anda perlu 10 gunakan semacam persatuan untuk menghasilkan 10 baris unik.
johno
Apa yang saya katakan. Anda perlu menjalankan itu 10 kali. menggabungkannya dengan serikat pekerja adalah salah satu cara untuk memasukkannya ke dalam satu permintaan. lihat addendum saya 2 menit yang lalu.
The Surrican
1
@TheSurrican, Solusi ini terlihat keren tetapi sangat cacat . Coba masukkan satu saja yang sangat besar Iddan semua kueri acak Anda akan mengembalikan yang itu Id.
Pacerier
1
FLOOR(RAND()*MAX(id))bias terhadap pengembalian id yang lebih besar.
Rick James
3

Saya membutuhkan kueri untuk mengembalikan sejumlah besar baris acak dari tabel yang agak besar. Inilah yang saya pikirkan. Pertama-tama dapatkan id rekaman maksimum:

SELECT MAX(id) FROM table_name;

Kemudian gantilah nilai itu menjadi:

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

Di mana max adalah id rekaman maksimum dalam tabel dan n adalah jumlah baris yang Anda inginkan di set hasil Anda. Asumsinya adalah bahwa tidak ada celah dalam id rekaman meskipun saya ragu itu akan mempengaruhi hasilnya jika ada (belum mencobanya). Saya juga membuat prosedur tersimpan ini menjadi lebih umum; masukkan nama tabel dan jumlah baris yang akan dikembalikan. Saya menjalankan MySQL 5.5.38 pada Windows 2008, 32GB, dual 3GHz E5450, dan di atas meja dengan 17.361.264 baris cukup konsisten pada ~ .03 detik / ~ 11 detik untuk mengembalikan 1.000.000 baris. (kali dari MySQL Workbench 6.1; Anda juga bisa menggunakan CEIL alih-alih LANTAI dalam pernyataan pilih kedua tergantung pada preferensi Anda)

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
$$

kemudian

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

Semua jawaban terbaik telah diposting (terutama yang mereferensikan tautan http://jan.kneschke.de/projects/mysql/order-by-rand/ ).

Saya ingin menunjukkan kemungkinan percepatan lain - caching . Pikirkan mengapa Anda perlu mendapatkan baris acak. Mungkin Anda ingin menampilkan beberapa posting acak atau iklan acak di situs web. Jika Anda mendapatkan 100 req / s, apakah benar-benar diperlukan setiap pengunjung mendapatkan baris acak? Biasanya baik-baik saja untuk men-cache X ini baris acak selama 1 detik (atau bahkan 10 detik) Tidak masalah jika 100 pengunjung unik dalam 1 detik yang sama mendapatkan posting acak yang sama, karena detik berikutnya 100 pengunjung lainnya akan mendapatkan serangkaian posting yang berbeda.

Saat menggunakan caching ini, Anda juga dapat menggunakan beberapa solusi yang lebih lambat untuk mendapatkan data acak karena akan diambil dari MySQL hanya sekali per detik terlepas dari kebutuhan Anda.

Marki555
sumber
3

Saya memperbaiki jawaban yang dimiliki @Riedsio. Ini adalah kueri paling efisien yang dapat saya temukan pada tabel besar, terdistribusi secara merata dengan celah (diuji untuk mendapatkan 1000 baris acak dari tabel yang memiliki> baris 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)

Biarkan saya membongkar apa yang terjadi.

  1. @max := (SELECT MAX(id) FROM table)
    • Saya menghitung dan menyimpan maks. Untuk tabel yang sangat besar, ada sedikit overhead untuk menghitung MAX(id)setiap kali Anda membutuhkan satu baris
  2. SELECT FLOOR(rand() * @max) + 1 as rand)
    • Mendapat id acak
  3. SELECT id FROM table INNER JOIN (...) on id > rand LIMIT 1
    • Ini mengisi celah. Pada dasarnya jika Anda secara acak memilih nomor di celah, itu hanya akan memilih id berikutnya. Dengan asumsi kesenjangan tersebar secara seragam, ini seharusnya tidak menjadi masalah.

Melakukan penyatuan membantu Anda memasukkan semuanya ke dalam 1 kueri sehingga Anda dapat menghindari melakukan beberapa kueri. Ini juga memungkinkan Anda menghemat biaya perhitungan MAX(id). Tergantung pada aplikasi Anda, ini mungkin penting atau sangat sedikit.

Perhatikan bahwa ini hanya mendapatkan id dan membuatnya secara acak. Jika Anda ingin melakukan sesuatu yang lebih maju, saya sarankan Anda melakukan ini:

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
Hans Z
sumber
Saya memerlukan 30 catatan acak, jadi saya harus mengubah LIMIT 1ke LIMIT 30mana - mana dalam permintaan
Hassaan
@Hassaan Anda tidak boleh, bahwa mengubah LIMIT 1ke LIMIT 30akan membuat Anda 30 catatan berturut-turut dari titik acak di tabel. Anda seharusnya memiliki 30 salinan (SELECT id FROM ....bagian di tengah.
Hans Z
Saya sudah mencoba tetapi tampaknya tidak lebih efisien daripada Riedsiomenjawab. Saya telah mencoba dengan 500 hit per halaman ke halaman menggunakan PHP 7.0.22 dan MariaDB pada centos 7, dengan Riedsiojawaban saya mendapat 500+ respon ekstra sukses maka jawaban Anda.
Hassaan
1
@Hassaan jawaban riedsio memberikan 1 baris, yang ini memberi Anda n baris, serta menghemat overhead I / O untuk query. Anda mungkin bisa mendapatkan baris lebih cepat, tetapi dengan lebih banyak memuat pada sistem Anda.
Hans Z
3

Saya menggunakan http://jan.kneschke.de/projects/mysql/order-by-rand/ yang diposting oleh Riedsio (saya menggunakan kasus prosedur tersimpan yang mengembalikan satu atau lebih nilai acak):

   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;

Dalam artikel tersebut ia memecahkan masalah kesenjangan dalam id yang menyebabkan hasil tidak begitu acak dengan mempertahankan tabel (menggunakan pemicu, dll ... lihat artikel); Saya memecahkan masalah dengan menambahkan kolom lain ke tabel, diisi dengan angka yang berdekatan, mulai dari 1 ( edit: kolom ini ditambahkan ke tabel sementara yang dibuat oleh subquery saat runtime, tidak mempengaruhi tabel permanen Anda):

   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;

Dalam artikel itu saya bisa melihat dia berusaha keras untuk mengoptimalkan kode; saya tidak tahu jika / seberapa besar perubahan saya berdampak pada kinerja tetapi bekerja sangat baik untuk saya.

bogdan
sumber
"Saya tidak punya ide jika / seberapa besar perubahan saya berdampak pada kinerja" - cukup banyak. Untuk @no_gaps_idindeks tidak dapat digunakan, jadi jika Anda melihat EXPLAINpermintaan Anda, Anda memiliki Using filesortdan Using where(tanpa indeks) untuk subqueries, berbeda dengan permintaan asli.
Fabian Schmengler
2

Berikut adalah pengubah permainan yang mungkin bermanfaat bagi banyak orang;

Saya memiliki tabel dengan 200k baris, dengan id berurutan , saya harus memilih N baris acak, jadi saya memilih untuk menghasilkan nilai acak berdasarkan ID terbesar dalam tabel, saya membuat skrip ini untuk mencari tahu mana yang merupakan operasi tercepat:

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

Hasilnya adalah:

  • Hitung: 36.8418693542479ms
  • Maks: 0.241041183472ms
  • Pesan: 0.216960906982ms

Berdasarkan hasil ini, order desc adalah operasi tercepat untuk mendapatkan max id,
Ini jawaban saya untuk pertanyaan:

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: Untuk mendapatkan 10 baris acak dari tabel 200k, butuh 1,78 ms (termasuk semua operasi di sisi php)

António Almeida
sumber
3
Sarankan Anda meningkatkan LIMITsedikit - Anda bisa mendapatkan duplikat.
Rick James
2

Ini sangat cepat dan 100% acak bahkan jika Anda memiliki celah.

  1. Hitung jumlah xbaris yang Anda milikiSELECT COUNT(*) as rows FROM TABLE
  2. Pilih 10 angka acak berbeda a_1,a_2,...,a_10antara 0 danx
  3. Kueri baris Anda seperti ini: SELECT * FROM TABLE LIMIT 1 offset a_iuntuk i = 1, ..., 10

Saya menemukan hack ini di buku SQL Antipatterns dari Bill Karwin .

Adam
sumber
Saya sedang memikirkan solusi yang sama, tolong beritahu saya, apakah lebih cepat daripada metode yang lain?
G. Adnane
@ G.Adnane tidak lebih cepat atau lebih lambat dari jawaban yang diterima, tetapi jawaban yang diterima mengasumsikan distribusi id yang sama. Saya tidak bisa membayangkan skenario di mana ini bisa dijamin. Solusi ini ada di O (1) di mana solusinya SELECT column FROM table ORDER BY RAND() LIMIT 10ada di O (nlog (n)). Jadi ya, ini adalah solusi puasa dan berfungsi untuk setiap distribusi id.
Adam
tidak, karena di tautan yang diposting untuk solusi yang diterima, ada metode lain, saya ingin tahu apakah solusi ini lebih cepat daripada yang lain, dengan cara lain, kita dapat mencoba mencari yang lain, itu sebabnya saya bertanya, dengan cara apa pun, +1 untuk jawabanmu. Saya menggunakan samething
G. Adnane
ada kasus ketika Anda ingin mendapatkan x jumlah baris tetapi offset menuju akhir tabel yang akan mengembalikan <x baris atau hanya 1 baris. saya tidak melihat jawaban Anda sebelum saya memposting milik saya tetapi saya membuatnya lebih jelas di sini stackoverflow.com/a/59981772/10387008
ZOLDIK
@ZOLDIK tampaknya Anda memilih 10 baris pertama setelah diimbangi x. Saya berpendapat bahwa ini bukan generasi acak 10 baris. Dalam jawaban saya, Anda harus menjalankan kueri di langkah tiga 10 kali, yaitu satu hanya mendapat satu baris per eksekusi dan tidak perlu khawatir jika offset ada di akhir tabel.
Adam
1

Jika Anda hanya memiliki satu Permintaan Baca

Gabungkan jawaban @redsio dengan temp-table (600K tidak terlalu banyak):

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;

Dan kemudian ambil versi @redsios Jawaban:

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;

Jika meja besar, Anda dapat mengayak pada bagian pertama:

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

Jika Anda memiliki banyak permintaan baca

  1. Versi: Anda bisa menyimpan tabel tmp_randorder tetap ada, sebut saja datatable_idlist. Buat ulang tabel itu dalam interval tertentu (hari, jam), karena meja juga akan berlubang. Jika meja Anda menjadi sangat besar, Anda juga bisa mengisi ulang lubang

    pilih l.data_id secara keseluruhan dari datatable_idlist l kiri gabung datatable dt di dt.id = l.data_id di mana dt.id bernilai null;

  2. Versi: Berikan Dataset Anda sebuah kolom random_sortorder baik secara langsung di datatable atau dalam tabel ekstra persisten datatable_sortorder. Buat indeks kolom itu. Hasilkan Nilai Acak di Aplikasi Anda (saya akan menyebutnya $rand).

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

Solusi ini membedakan 'baris tepi' dengan urutan random_sort tertinggi dan terendah, jadi atur ulangnya dalam interval (sekali sehari).

flaschenpost
sumber
1

Solusi sederhana lain adalah memberi peringkat pada baris dan mengambil salah satunya secara acak dan dengan solusi ini Anda tidak perlu memiliki kolom berdasarkan 'Id' di tabel.

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;

Anda dapat mengubah nilai batas sesuai kebutuhan Anda untuk mengakses baris sebanyak yang Anda inginkan tetapi itu sebagian besar akan menjadi nilai berturut-turut.

Namun, jika Anda tidak ingin nilai acak berturut-turut maka Anda dapat mengambil sampel yang lebih besar dan memilih secara acak dari itu. sesuatu seperti ...

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;
sactiw
sumber
1

Salah satu cara yang saya temukan cukup baik jika ada id yang di-autogenerasi adalah dengan menggunakan operator modulo '%'. Misalnya, jika Anda memerlukan 10.000 catatan acak dari 70.000, Anda dapat menyederhanakan ini dengan mengatakan Anda perlu 1 dari setiap 7 baris. Ini dapat disederhanakan dalam kueri ini:

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

Jika hasil membagi baris target dengan total yang tersedia bukan bilangan bulat, Anda akan memiliki beberapa baris tambahan dari yang Anda minta, jadi Anda harus menambahkan klausa LIMIT untuk membantu Anda memotong set hasil seperti ini:

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

Ini memang membutuhkan pemindaian penuh, tetapi lebih cepat dari ORDER BY RAND, dan menurut saya lebih mudah dimengerti daripada opsi lain yang disebutkan dalam utas ini. Juga jika sistem yang menulis ke DB membuat kumpulan baris dalam batch Anda mungkin tidak mendapatkan hasil acak seperti yang Anda harapkan.

Nicolas Cohen
sumber
2
Sekarang saya pikir begitu, jika Anda membutuhkan baris acak setiap kali Anda menyebutnya, ini tidak berguna. Saya hanya berpikir tentang perlunya mendapatkan baris acak dari set untuk melakukan penelitian. Saya masih berpikir modulo adalah hal yang baik untuk membantu dalam kasus lain. Anda dapat menggunakan modulo sebagai filter pass pertama untuk menurunkan biaya operasi ORDER BY RAND.
Nicolas Cohen
1

Saya telah memeriksa semua jawaban, dan saya tidak berpikir ada yang menyebutkan kemungkinan ini sama sekali, dan saya tidak yakin mengapa.

Jika Anda ingin kesederhanaan dan kecepatan maksimal, dengan biaya rendah, maka bagi saya tampaknya masuk akal untuk menyimpan angka acak terhadap setiap baris dalam DB. Cukup buat kolom tambahan random_number,, dan tetapkan default ke RAND(). Buat indeks pada kolom ini.

Kemudian ketika Anda ingin mengambil baris, buat angka acak dalam kode Anda (PHP, Perl, apa pun) dan bandingkan dengan kolom.

SELECT FROM tbl WHERE random_number >= :random LIMIT 1

Saya kira meskipun sangat rapi untuk satu baris, untuk sepuluh baris seperti OP meminta Anda harus menyebutnya sepuluh kali terpisah (atau muncul dengan tweak pintar yang segera lolos dari saya)

Codemonkey
sumber
Ini sebenarnya pendekatan yang sangat bagus dan efisien. Satu-satunya kelemahan adalah fakta bahwa Anda menukar ruang untuk kecepatan, yang sepertinya merupakan kesepakatan yang adil menurut saya.
Tochukwu Nkemdilim
Terima kasih. Saya mempunyai skenario di mana tabel utama saya ingin baris acak dari memiliki 5 juta baris, dan cukup banyak yang bergabung, dan setelah mencoba sebagian besar pendekatan dalam pertanyaan ini ini adalah kludge yang saya menetap. Satu kolom tambahan adalah pertukaran yang sangat berharga bagi saya.
Codemonkey
0

Berikut ini harus cepat, tidak bias dan independen dari kolom id. Namun itu tidak menjamin bahwa jumlah baris yang dikembalikan akan cocok dengan jumlah baris yang diminta.

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

Penjelasan: dengan asumsi Anda ingin 10 baris dari 100 maka setiap baris memiliki 1/10 kemungkinan mendapatkan SELECT yang dapat dicapai oleh WHERE RAND() < 0.1. Pendekatan ini tidak menjamin 10 baris; tetapi jika kueri dijalankan cukup kali jumlah rata-rata baris per eksekusi akan sekitar 10 dan setiap baris dalam tabel akan dipilih secara merata.

Salman A
sumber
0

Anda dapat dengan mudah menggunakan offset acak dengan batas

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

Anda juga dapat menerapkan klausa tempat seperti itu

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;

Diuji pada 600.000 baris (700MB) tabel eksekusi query mengambil ~ 0.016sec HDD Drive

EDIT
   offset mungkin mengambil nilai dekat dengan ujung meja, yang akan menghasilkan pernyataan pilih kembali kurang baris (atau mungkin hanya 1 baris), untuk menghindari ini kita dapat memeriksa offsetlagi setelah mendeklarasikannya, seperti itu

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;
ZOLDIK
sumber
-1

Saya Menggunakan kueri ini:

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

waktu permintaan: 0,016s

josejavierfm
sumber
Memiliki PK seperti 1,2,9,15. dengan kueri di atas Anda akan mendapatkan baris seperti 4, 7, 14, 11 yang tidak cukup!
Junaid Atari
-2

Beginilah cara saya melakukannya:

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

Saya suka karena tidak memerlukan tabel lain, mudah untuk menulis, dan sangat cepat untuk dieksekusi.

Bernardo Siu
sumber
5
Itu scan tabel penuh dan tidak menggunakan indeks apa pun. Untuk meja besar dan lingkungan yang sibuk, itu besar, tidak, tidak.
matt
-2

Gunakan kueri sederhana di bawah ini untuk mendapatkan data acak dari tabel.

SELECT user_firstname ,
COUNT(DISTINCT usr_fk_id) cnt
FROM userdetails 
GROUP BY usr_fk_id 
ORDER BY cnt ASC  
LIMIT 10
MANOJ
sumber
Jika Anda ingin menggunakan pernyataan gabungan apa pun dan di mana filter dapat Anda gunakan.
MANOJ
3
Dari bagian mana dari kueri yang Anda dapatkan secara acak?
Marki555
-4

Saya kira ini adalah cara terbaik yang mungkin ..

SELECT id, id * RAND( ) AS random_no, first_name, last_name
FROM user
ORDER BY random_no
Ritesh Patadiya
sumber
8
Sial, tidak, itu salah satu cara terburuk untuk mendapatkan baris acak dari tabel. Pemindaian tabel lengkap + filesort + tmp tabel = kinerja buruk.
matt
1
Selain kinerja, itu juga jauh dari acak; Anda memesan dengan produk dari id dan nomor acak, bukan hanya memesan dengan nomor acak, yang berarti bahwa baris dengan id lebih rendah akan cenderung muncul lebih awal di set hasil Anda.
Mark Amery