Ich möchte meine Abfragen optimieren, damit ich in mysql-slow.log
nachschaue.
Die meisten meiner langsamen Abfragen enthalten ORDER BY Rand()
. Ich kann keine echte Lösung finden, um dieses Problem zu lösen. Es gibt eine mögliche Lösung unter MySQLPerformanceBlog , aber ich denke nicht, dass dies ausreicht. Bei schlecht optimierten (oder häufig aktualisierten, vom Benutzer verwalteten) Tabellen funktioniert es nicht oder ich muss zwei oder mehr Abfragen ausführen, bevor ich meine PHP
- generierte zufällige Zeile auswählen kann.
Gibt es eine Lösung für dieses Problem?
Ein Dummy-Beispiel:
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
Versuche dies:
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
Dies ist besonders effizient bei MyISAM
(da die COUNT(*)
instant ist), aber selbst bei InnoDB
ist 10
mal effizienter als ORDER BY Rand()
.
Die Hauptidee hierbei ist, dass wir nicht sortieren, sondern zwei Variablen beibehalten und den running probability
einer Zeile berechnen, die im aktuellen Schritt ausgewählt werden soll.
Weitere Informationen finden Sie in diesem Artikel in meinem Blog:
Update:
Wenn Sie nur einen zufälligen Datensatz auswählen müssen, versuchen Sie Folgendes:
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
)
)
Dies setzt voraus, dass Ihre ac_id
-Werte mehr oder weniger gleichmäßig verteilt sind.
Es hängt davon ab, wie zufällig Sie sein müssen. Die von Ihnen verlinkte Lösung funktioniert IMO ziemlich gut. Wenn Sie keine großen Lücken im ID-Feld haben, ist das immer noch ziemlich zufällig.
Sie sollten jedoch in der Lage sein, dies in einer Abfrage zu tun (um einen einzelnen Wert auszuwählen):
SELECT [fields] FROM [table] WHERE id >= FLOOR(Rand()*MAX(id)) LIMIT 1
Andere Lösungen:
random
hinzu und füllen Sie es mit Zufallszahlen. Sie können dann in PHP eine Zufallszahl generieren und "SELECT ... WHERE rnd > $random"
So würde ich es machen:
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;
Dadurch erhalten Sie eine einzelne Unterabfrage, die den Index verwendet, um eine zufällige ID abzurufen. Die andere Abfrage wird ausgelöst, um Ihre verbundene Tabelle abzurufen.
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
)
(Ja, ich bekomme das Ding, weil ich nicht genug Fleisch habe, aber kann man nicht einen Tag vegan sein?)
Fall: Aufeinanderfolgendes AUTO_INCREMENT ohne Lücken, 1 Zeile zurückgegeben
Fall: Aufeinanderfolgendes AUTO_INCREMENT ohne Lücken, 10 Zeilen
Fall: AUTO_INCREMENT mit Lücken, 1 Zeile zurückgegeben
Fall: Zusätzliche FLOAT-Spalte für das Randomisieren
Fall: UUID- oder MD5-Spalte
Diese 5 Fälle können für große Tische sehr effizient gestaltet werden. Siehe mein Blog für Details.
Die Lösung für Ihr Dummy-Beispiel wäre:
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
Um mehr über Alternativen zu ORDER BY Rand()
zu erfahren, sollten Sie diesen Artikel lesen.
Ich optimiere viele bestehende Abfragen in meinem Projekt. Die Lösung von Quassnoi hat mir geholfen, die Anfragen viel zu beschleunigen! Ich finde es jedoch schwierig, die genannte Lösung in alle Abfragen zu integrieren, insbesondere für komplizierte Abfragen, die viele Unterabfragen für mehrere große Tabellen umfassen.
Ich verwende also eine weniger optimierte Lösung. Grundsätzlich funktioniert es genauso wie die Lösung von 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]
ermittelt die Wahrscheinlichkeit, eine zufällige Zeile auszuwählen. Der Rand () generiert eine Zufallszahl. Die Zeile wird ausgewählt, wenn Rand () kleiner oder gleich der Wahrscheinlichkeit ist. Dies führt effektiv eine zufällige Auswahl durch, um die Tabellengröße zu begrenzen. Da die Wahrscheinlichkeit geringer ist, dass weniger als die festgelegte Anzahl von Grenzwerten zurückgegeben wird, müssen wir die Wahrscheinlichkeit erhöhen, um sicherzustellen, dass wir genügend Zeilen auswählen. Daher multiplizieren wir $ size mit einem $ -Faktor (ich setze normalerweise $ factor = 2, funktioniert in den meisten Fällen). Zum Schluss machen wir den limit $size
Das Problem besteht nun darin, die Datei accommodation_table_row_count ..__ auszuarbeiten. Wenn wir die Tabellengröße kennen, KÖNNEN wir die Tabellengröße hart codieren. Das würde am schnellsten laufen, aber das ist offensichtlich nicht ideal. Wenn Sie Myisam verwenden, ist das Abrufen der Tabellenanzahl sehr effizient. Da ich innodb verwende, mache ich nur eine einfache Zählung + Auswahl. In Ihrem Fall würde es so aussehen:
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
Der knifflige Teil ermittelt die richtige Wahrscheinlichkeit. Wie Sie sehen, berechnet der folgende Code tatsächlich nur die grobe Temp-Tabellengröße (tatsächlich zu grob!): (select (SELECT count(*) FROM accomodation) * (SELECT count(*) FROM accomodation_category))
Sie können diese Logik jedoch verfeinern, um eine Annäherung an die Tabellengröße zu erreichen. Beachten Sie, dass es besser ist, OVER-select zu wählen als Zeilen unter- zuwählen. Wenn die Wahrscheinlichkeit zu niedrig eingestellt ist, besteht die Gefahr, dass Sie nicht genügend Zeilen auswählen.
Diese Lösung läuft langsamer als die Lösung von Quassnoi, da die Tabellengröße neu berechnet werden muss. Ich finde diese Codierung jedoch viel einfacher zu handhaben. Dies ist ein Kompromiss zwischen Genauigkeit + Leistung vs Codierkomplexität . Bei großen Tischen ist dies jedoch noch weitaus schneller als Order by Rand ().
Hinweis: Wenn die Abfragelogik dies zulässt, führen Sie die Zufallsauswahl so früh wie möglich vor allen Join-Vorgängen aus.