J'aimerais optimiser mes requêtes afin que j'examine mysql-slow.log
.
La plupart de mes requêtes lentes contiennent ORDER BY RAND()
. Je ne trouve pas de vraie solution pour résoudre ce problème. Il existe une solution possible chez MySQLPerformanceBlog mais je ne pense pas que cela soit suffisant. Sur les tables mal optimisées (ou fréquemment mises à jour, gérées par l'utilisateur), cela ne fonctionne pas ou je dois exécuter deux requêtes ou plus avant de pouvoir sélectionner ma PHP
ligne aléatoire générée.
Existe-t-il une solution à ce problème?
Un exemple factice:
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
la source
la source
Réponses:
Essaye ça:
Ceci est particulièrement efficace sur
MyISAM
(puisque leCOUNT(*)
est instantané), mais même enInnoDB
son10
temps plus efficace queORDER BY RAND()
.L'idée principale ici est de ne pas trier, mais plutôt de conserver deux variables et de calculer la valeur
running probability
d'une ligne à sélectionner à l'étape en cours.Voir cet article dans mon blog pour plus de détails:
Mettre à jour:
Si vous ne devez sélectionner qu'un seul enregistrement aléatoire, essayez ceci:
Cela suppose que les vôtres
ac_id
sont distribués plus ou moins uniformément.la source
@fabrik
: Essayez maintenant. Ce serait vraiment utile si vous postiez les scripts de table afin que je puisse les vérifier avant de poster.Cela dépend de la façon dont vous devez être aléatoire. La solution que vous avez liée fonctionne plutôt bien IMO. Sauf si vous avez de grandes lacunes dans le champ ID, c'est toujours assez aléatoire.
Cependant, vous devriez pouvoir le faire en une seule requête en utilisant ceci (pour sélectionner une seule valeur):
Autres solutions:
random
à la table et remplissez-le avec des nombres aléatoires. Vous pouvez ensuite générer un nombre aléatoire en PHP et faire"SELECT ... WHERE rnd > $random"
la source
SELECT [fields] FROM [table] WHERE id >= FLOOR(RAND()*(SELECT MAX(id) FROM [table])) LIMIT 1
mais cela ne semble pas fonctionner correctement car il ne renvoie jamais le dernier enregistrementSELECT [fields] FROM [table] WHERE id >= FLOOR(1 + RAND()*(SELECT MAX(id) FROM [table])) LIMIT 1
Semble faire l'affaire pour moiVoici comment je le ferais:
la source
OFFSET
(ce qui est à quoi@r
sert) n'évite pas une analyse - jusqu'à une analyse complète de la table.(Ouais, je serai grogné de ne pas avoir assez de viande ici, mais ne peux-tu pas être végétalien pendant un jour?)
Cas: AUTO_INCREMENT consécutif sans espaces, 1 ligne renvoyée
Cas: AUTO_INCREMENT consécutif sans espaces, 10 lignes
Case: AUTO_INCREMENT avec espaces, 1 ligne renvoyée
Case: Colonne FLOAT supplémentaire pour la randomisation
Cas: colonne UUID ou MD5
Ces 5 cas peuvent être rendus très efficaces pour les grandes tables. Voir mon blog pour les détails.
la source
Cela vous donnera une seule sous-requête qui utilisera l'index pour obtenir un identifiant aléatoire, puis l'autre requête se déclenchera pour obtenir votre table jointe.
la source
La solution pour votre exemple factice serait:
Pour en savoir plus sur les alternatives à
ORDER BY RAND()
, vous devriez lire cet article .la source
J'optimise beaucoup de requêtes existantes dans mon projet. La solution de Quassnoi m'a beaucoup aidé à accélérer les requêtes! Cependant, j'ai du mal à incorporer ladite solution dans toutes les requêtes, en particulier pour les requêtes compliquées impliquant de nombreuses sous-requêtes sur plusieurs grandes tables.
J'utilise donc une solution moins optimisée. Fondamentalement, cela fonctionne de la même manière que la solution de Quassnoi.
$size * $factor / [accomodation_table_row_count]
calcule la probabilité de choisir une ligne aléatoire. Le rand () générera un nombre aléatoire. La ligne sera sélectionnée si rand () est plus petit ou égal à la probabilité. Cela effectue effectivement une sélection aléatoire pour limiter la taille de la table. Puisqu'il y a une chance qu'il renvoie moins que le nombre limite défini, nous devons augmenter la probabilité pour nous assurer que nous sélectionnons suffisamment de lignes. Par conséquent, nous multiplions $ size par un $ factor (je fixe généralement $ factor = 2, fonctionne dans la plupart des cas). Enfin nous faisons lelimit $size
Le problème est maintenant de travailler sur l' accomodation_table_row_count . Si nous connaissons la taille de la table, nous pourrions coder en dur la taille de la table. Ce serait le plus rapide, mais ce n'est évidemment pas l'idéal. Si vous utilisez Myisam, obtenir le nombre de tables est très efficace. Depuis que j'utilise innodb, je ne fais qu'un simple comptage + sélection. Dans votre cas, cela ressemblerait à ceci:
La partie la plus délicate consiste à déterminer la bonne probabilité. Comme vous pouvez le voir, le code suivant ne calcule en fait que la taille approximative de la table temporaire (en fait, trop approximative!):
(select (SELECT count(*) FROM accomodation) * (SELECT count(*) FROM accomodation_category))
Mais vous pouvez affiner cette logique pour donner une approximation plus proche de la taille de la table. Notez qu'il vaut mieux sur-sélectionner que sous-sélectionner des lignes. c'est-à-dire que si la probabilité est trop faible, vous risquez de ne pas sélectionner suffisamment de lignes.Cette solution fonctionne plus lentement que la solution de Quassnoi car nous devons recalculer la taille de la table. Cependant, je trouve ce codage beaucoup plus gérable. Il s'agit d'un compromis entre précision + performances et complexité de codage . Cela dit, sur les grandes tables, c'est encore beaucoup plus rapide que Order by Rand ().
Remarque: Si la logique de requête le permet, effectuez la sélection aléatoire le plus tôt possible avant toute opération de jointure.
la source
la source