Saya ingin mengoptimalkan kueri saya, jadi saya memeriksanya mysql-slow.log
.
Sebagian besar kueri lambat saya berisi ORDER BY RAND()
. Saya tidak dapat menemukan solusi nyata untuk menyelesaikan masalah ini. Ada solusi yang mungkin di MySQLPerformanceBlog tetapi saya rasa ini tidak cukup. Pada tabel yang dioptimalkan dengan buruk (atau sering diperbarui, dikelola pengguna), itu tidak berfungsi atau saya perlu menjalankan dua atau lebih kueri sebelum saya dapat memilih PHP
baris acak yang saya buat.
Apakah ada solusi untuk masalah ini?
Contoh dummy:
SELECT accomodation.ac_id,
accomodation.ac_status,
accomodation.ac_name,
accomodation.ac_status,
accomodation.ac_images
FROM accomodation, accomodation_category
WHERE accomodation.ac_status != 'draft'
AND accomodation.ac_category = accomodation_category.acat_id
AND accomodation_category.acat_slug != 'vendeglatohely'
AND ac_images != 'b:0;'
ORDER BY
RAND()
LIMIT 1
mysql
random
performance
fabrik
sumber
sumber
Jawaban:
Coba ini:
SELECT * FROM ( SELECT @cnt := COUNT(*) + 1, @lim := 10 FROM t_random ) vars STRAIGHT_JOIN ( SELECT r.*, @lim := @lim - 1 FROM t_random r WHERE (@cnt := @cnt - 1) AND RAND(20090301) < @lim / @cnt ) i
Hal ini terutama efisien pada
MyISAM
(sejakCOUNT(*)
instan), tetapi bahkan diInnoDB
itu10
kali lebih efisien daripadaORDER BY RAND()
.Ide utamanya di sini adalah kita tidak mengurutkan, tetapi menyimpan dua variabel dan menghitung
running probability
baris yang akan dipilih pada langkah saat ini.Lihat artikel ini di blog saya untuk detail lebih lanjut:
Memperbarui:
Jika Anda perlu memilih satu catatan acak, coba ini:
SELECT aco.* FROM ( SELECT minid + FLOOR((maxid - minid) * RAND()) AS randid FROM ( SELECT MAX(ac_id) AS maxid, MIN(ac_id) AS minid FROM accomodation ) q ) q2 JOIN accomodation aco ON aco.ac_id = COALESCE ( ( SELECT accomodation.ac_id FROM accomodation WHERE ac_id > randid AND ac_status != 'draft' AND ac_images != 'b:0;' AND NOT EXISTS ( SELECT NULL FROM accomodation_category WHERE acat_id = ac_category AND acat_slug = 'vendeglatohely' ) ORDER BY ac_id LIMIT 1 ), ( SELECT accomodation.ac_id FROM accomodation WHERE ac_status != 'draft' AND ac_images != 'b:0;' AND NOT EXISTS ( SELECT NULL FROM accomodation_category WHERE acat_id = ac_category AND acat_slug = 'vendeglatohely' ) ORDER BY ac_id LIMIT 1 ) )
Ini mengasumsikan bahwa Anda
ac_id
didistribusikan lebih atau kurang merata.sumber
@fabrik
: coba sekarang. Akan sangat membantu jika Anda memposting skrip tabel sehingga saya dapat memeriksanya sebelum memposting.Itu tergantung pada seberapa acak Anda perlu. Solusi yang Anda tautkan berfungsi dengan cukup baik IMO. Kecuali Anda memiliki celah besar di bidang ID, itu masih cukup acak.
Namun, Anda harus dapat melakukannya dalam satu kueri menggunakan ini (untuk memilih satu nilai):
SELECT [fields] FROM [table] WHERE id >= FLOOR(RAND()*MAX(id)) LIMIT 1
Solusi lain:
random
ke tabel dan isi dengan angka acak. Anda kemudian dapat membuat nomor acak di PHP dan lakukan"SELECT ... WHERE rnd > $random"
sumber
SELECT [fields] FROM [table] WHERE id >= FLOOR(RAND()*(SELECT MAX(id) FROM [table])) LIMIT 1
tetapi ini tampaknya tidak berfungsi dengan baik karena tidak pernah mengembalikan rekor terakhirSELECT [fields] FROM [table] WHERE id >= FLOOR(1 + RAND()*(SELECT MAX(id) FROM [table])) LIMIT 1
Tampaknya melakukan trik untuk sayaInilah cara saya melakukannya:
SET @r := (SELECT ROUND(RAND() * (SELECT COUNT(*) FROM accomodation a JOIN accomodation_category c ON (a.ac_category = c.acat_id) WHERE a.ac_status != 'draft' AND c.acat_slug != 'vendeglatohely' AND a.ac_images != 'b:0;'; SET @sql := CONCAT(' SELECT a.ac_id, a.ac_status, a.ac_name, a.ac_status, a.ac_images FROM accomodation a JOIN accomodation_category c ON (a.ac_category = c.acat_id) WHERE a.ac_status != ''draft'' AND c.acat_slug != ''vendeglatohely'' AND a.ac_images != ''b:0;'' LIMIT ', @r, ', 1'); PREPARE stmt1 FROM @sql; EXECUTE stmt1;
sumber
OFFSET
(untuk apa@r
) tidak menghindari pemindaian - hingga pemindaian tabel penuh.(Ya, saya akan dihukum karena tidak memiliki cukup daging di sini, tetapi tidak bisakah Anda menjadi vegan untuk satu hari?)
Kasus: AUTO_INCREMENT berturut-turut tanpa celah, 1 baris dikembalikan
Kasus: AUTO_INCREMENT berurutan tanpa celah, 10 baris
Kasus: AUTO_INCREMENT dengan celah, 1 baris dikembalikan
Kasus: Kolom FLOAT ekstra untuk pengacakan
Kasus: Kolom UUID atau MD5
Kelima casing tersebut dapat dibuat sangat efisien untuk tabel besar. Lihat blog saya untuk detailnya.
sumber
Ini akan memberi Anda satu sub kueri yang akan menggunakan indeks untuk mendapatkan id acak, lalu kueri lain akan mengaktifkan tabel gabungan Anda.
SELECT accomodation.ac_id, accomodation.ac_status, accomodation.ac_name, accomodation.ac_status, accomodation.ac_images FROM accomodation, accomodation_category WHERE accomodation.ac_status != 'draft' AND accomodation.ac_category = accomodation_category.acat_id AND accomodation_category.acat_slug != 'vendeglatohely' AND ac_images != 'b:0;' AND accomodation.ac_id IS IN ( SELECT accomodation.ac_id FROM accomodation ORDER BY RAND() LIMIT 1 )
sumber
Solusi untuk contoh dummy Anda adalah:
SELECT accomodation.ac_id, accomodation.ac_status, accomodation.ac_name, accomodation.ac_status, accomodation.ac_images FROM accomodation, JOIN accomodation_category ON accomodation.ac_category = accomodation_category.acat_id JOIN ( SELECT CEIL(RAND()*(SELECT MAX(ac_id) FROM accomodation)) AS ac_id ) AS Choices USING (ac_id) WHERE accomodation.ac_id >= Choices.ac_id AND accomodation.ac_status != 'draft' AND accomodation_category.acat_slug != 'vendeglatohely' AND ac_images != 'b:0;' LIMIT 1
Untuk membaca lebih lanjut tentang alternatif
ORDER BY RAND()
, Anda harus membaca artikel ini .sumber
Saya mengoptimalkan banyak kueri yang ada dalam proyek saya. Solusi Quassnoi telah membantu saya mempercepat banyak kueri! Namun, saya merasa sulit untuk memasukkan solusi tersebut di semua kueri, terutama untuk kueri rumit yang melibatkan banyak subkueri di beberapa tabel besar.
Jadi saya menggunakan solusi yang kurang dioptimalkan. Pada dasarnya ini bekerja dengan cara yang sama seperti solusi Quassnoi.
SELECT accomodation.ac_id, accomodation.ac_status, accomodation.ac_name, accomodation.ac_status, accomodation.ac_images FROM accomodation, accomodation_category WHERE accomodation.ac_status != 'draft' AND accomodation.ac_category = accomodation_category.acat_id AND accomodation_category.acat_slug != 'vendeglatohely' AND ac_images != 'b:0;' AND rand() <= $size * $factor / [accomodation_table_row_count] LIMIT $size
$size * $factor / [accomodation_table_row_count]
menghitung probabilitas untuk memilih baris acak. Rand () akan menghasilkan nomor acak. Baris akan dipilih jika rand () lebih kecil atau sama dengan probabilitas. Ini secara efektif melakukan pemilihan acak untuk membatasi ukuran tabel. Karena ada kemungkinan itu akan kembali kurang dari jumlah batas yang ditentukan, kita perlu meningkatkan kemungkinan untuk memastikan kita memilih baris yang cukup. Karenanya kami mengalikan $ ukuran dengan $ faktor (saya biasanya menetapkan $ faktor = 2, berfungsi dalam banyak kasus). Akhirnya kami melakukanlimit $size
Masalahnya sekarang adalah mengerjakan accomodation_table_row_count . Jika kita mengetahui ukuran tabel, kita BISA kode keras ukuran tabel. Ini akan berjalan paling cepat, tetapi jelas ini tidak ideal. Jika Anda menggunakan Myisam, mendapatkan hitungan tabel sangat efisien. Karena saya menggunakan innodb, saya hanya melakukan hitungan + pemilihan sederhana. Dalam kasus Anda, akan terlihat seperti ini:
SELECT accomodation.ac_id, accomodation.ac_status, accomodation.ac_name, accomodation.ac_status, accomodation.ac_images FROM accomodation, accomodation_category WHERE accomodation.ac_status != 'draft' AND accomodation.ac_category = accomodation_category.acat_id AND accomodation_category.acat_slug != 'vendeglatohely' AND ac_images != 'b:0;' AND rand() <= $size * $factor / (select (SELECT count(*) FROM `accomodation`) * (SELECT count(*) FROM `accomodation_category`)) LIMIT $size
Bagian yang sulit adalah mencari kemungkinan yang benar. Seperti yang Anda lihat, kode berikut sebenarnya hanya menghitung ukuran tabel temp kasar (Sebenarnya, terlalu kasar!):
(select (SELECT count(*) FROM accomodation) * (SELECT count(*) FROM accomodation_category))
Tetapi Anda dapat memperbaiki logika ini untuk memberikan perkiraan ukuran tabel yang lebih dekat. Perhatikan bahwa lebih baik memilih OVER daripada memilih baris yang kurang. yaitu jika probabilitas disetel terlalu rendah, Anda berisiko tidak memilih cukup baris.Solusi ini berjalan lebih lambat daripada solusi Quassnoi karena kita perlu menghitung ulang ukuran tabel. Namun, menurut saya pengkodean ini jauh lebih mudah dikelola. Ini adalah trade off antara akurasi + kinerja vs kompleksitas pengkodean . Karena itu, pada tabel besar ini masih jauh lebih cepat daripada Order by Rand ().
Catatan: Jika logika kueri mengizinkan, lakukan pemilihan acak sedini mungkin sebelum operasi gabungan apa pun.
sumber
function getRandomRow(){ $id = rand(0,NUM_OF_ROWS_OR_CLOSE_TO_IT); $res = getRowById($id); if(!empty($res)) return $res; return getRandomRow(); } //rowid is a key on table function getRowById($rowid=false){ return db select from table where rowid = $rowid; }
sumber