web-dev-qa-db-fra.com

Comment fonctionne ORDER BY Rand () de MySQL?

J'ai fait des recherches et des tests sur la façon de faire une sélection aléatoire rapide dans MySQL. Au cours du processus, j'ai eu des résultats inattendus et je ne suis pas tout à fait sûr de savoir comment ORDER BY Rand () fonctionne réellement.

J'ai toujours pensé que lorsque vous faites ORDER BY Rand () sur la table, MySQL ajoute une nouvelle colonne à la table remplie de valeurs aléatoires, puis trie les données en fonction de cette colonne, puis par ex. vous prenez la valeur ci-dessus qui est arrivée au hasard. J'ai fait beaucoup de recherches sur Google et de tests et j'ai finalement trouvé que la requête Jay propose sur son blog est en effet la solution la plus rapide:

SELECT * FROM Table T JOIN (SELECT CEIL(MAX(ID)*Rand()) AS ID FROM Table) AS x ON T.ID >= x.ID LIMIT 1;

Alors que ORDER BY Rand () prend entre 30 et 40 secondes sur ma table de test, sa requête effectue le travail en 0,1 seconde. Il explique comment cela fonctionne dans le blog, je vais donc simplement l'ignorer et enfin passer à la chose étrange.

Ma table est une table commune avec une clé primaire id et d'autres éléments non indexés tels que username, age, etc. Voici ce que j'ai du mal à expliquer.

SELECT * FROM table ORDER BY Rand() LIMIT 1; /*30-40 seconds*/
SELECT id FROM table ORDER BY Rand() LIMIT 1; /*0.25 seconds*/
SELECT id, username FROM table ORDER BY Rand() LIMIT 1; /*90 seconds*/

Je m'attendais en quelque sorte à voir à peu près le même temps pour les trois requêtes car je trie toujours sur une seule colonne. Mais pour une raison quelconque, cela ne s'est pas produit. S'il vous plaît laissez-moi savoir si vous avez des idées à ce sujet. J'ai un projet où je dois faire vite ORDER BY Rand () et personnellement, je préférerais utiliser

SELECT id FROM table ORDER BY Rand() LIMIT 1;
SELECT * FROM table WHERE id=ID_FROM_PREVIOUS_QUERY LIMIT 1;

ce qui, oui, est plus lent que la méthode de Jay, mais il est plus petit et plus facile à comprendre. Mes requêtes sont plutôt volumineuses avec plusieurs JOIN et la clause WHERE. Bien que la méthode de Jay fonctionne toujours, la requête devient très grosse et complexe car je dois utiliser tous les JOIN et WHERE dans la sous-requête JOINed (appelée x dans sa requête).

Merci pour votre temps!

25
Eugene

Bien qu’il n’existe pas de «commande rapide par Rand ()», il existe une solution de contournement pour votre tâche spécifique. 

Pour obtenir une seule ligne aléatoire , vous pouvez faire comme ce blogueur allemand: http://www.roberthartung.de/mysql-order-by-Rand-a-case-study-of- alternatives/ (Je ne pouvais pas voir une URL de lien hypertexte. Si quelqu'un en voyait une, n'hésitez pas à modifier le lien.)

Le texte est en allemand, mais le code SQL est un peu en bas de la page et dans de grandes cases blanches, donc ce n'est pas difficile à voir.

Essentiellement, il crée une procédure permettant d’obtenir une ligne valide. Cela génère un nombre aléatoire compris entre 0 et max_id. Essayez d'extraire une ligne. Si elle n'existe pas, continuez jusqu'à ce que vous en rencontriez une. Il permet d'extraire x nombre de lignes aléatoires en les stockant dans une table temporaire. Vous pouvez donc probablement réécrire la procédure pour obtenir un peu plus rapide l'extraction d'une seule ligne.

L'inconvénient est que si vous supprimez BEAUCOUP de lignes et qu'il y a d'énormes lacunes, il y a de grandes chances qu'il manque des milliers de fois, ce qui le rend inefficace.

Mise à jour: différents temps d'exécution

SELECT * FROM table ORDER BY Rand () LIMIT 1;/30-40 secondes /

SELECT id FROM table ORDER BY Rand () LIMIT 1;/0,25 seconde /

SELECT id, nom d'utilisateur FROM table ORDER BY Rand () LIMIT 1;/90 secondes /

Je m'attendais en quelque sorte à voir à peu près le même temps pour les trois requêtes car je trie toujours sur une seule colonne. Mais pour une raison quelconque, cela ne s'est pas produit. S'il vous plaît laissez-moi savoir si vous avez des idées à ce sujet.

Cela peut avoir à voir avec l'indexation. id est indexé et facile d’accès, alors que l’ajout de username au résultat signifie qu’il doit le lire depuis chaque ligne et le placer dans la table de la mémoire. Avec le *, il doit également tout lire en mémoire, mais il n’est pas nécessaire de naviguer dans le fichier de données, ce qui signifie qu’il n’ya pas de temps perdu à chercher. 

Cela ne fait la différence que s'il existe des colonnes de longueur variable (varchar/text), ce qui signifie qu'il doit vérifier la longueur, puis ignorer cette longueur, au lieu de simplement ignorer une longueur définie (ou 0) entre chaque ligne.

13
Tor Valamo

Cela peut avoir à voir avec l'indexation. id est indexé et facile d’accès, alors que ajouter un nom d’utilisateur au résultat signifie que doit être lu dans chaque ligne et le mettre en mémoire. table. Avec , Le * doit également lire tout le contenu de Dans la mémoire, mais il n’a pas besoin de Parcourir le fichier de données, ce qui signifie Il n’ya pas de temps perdu chercher. Ceci Ne fait la différence que s'il y a Colonnes de longueur variable, ce qui signifie Il doit vérifier la longueur, puis ignorer Cette longueur, par opposition à saute une longueur définie (ou 0) entre chaque rangée

La pratique est meilleure que toutes les théories! Pourquoi ne pas simplement vérifier les plans? :)

mysql> explain select name from avatar order by Rand() limit 1;
+----+-------------+--------+-------+---------------+-----------------+---------+------+-------+----------------------------------------------+
| id | select_type | table  | type  | possible_keys | key             | key_len | ref  | rows  | Extra                                        |
+----+-------------+--------+-------+---------------+-----------------+---------+------+-------+----------------------------------------------+
|  1 | SIMPLE      | avatar | index | NULL          | IDX_AVATAR_NAME | 302     | NULL | 30062 | Using index; Using temporary; Using filesort |
+----+-------------+--------+-------+---------------+-----------------+---------+------+-------+----------------------------------------------+
1 row in set (0.00 sec)

mysql> explain select * from avatar order by Rand() limit 1;
+----+-------------+--------+------+---------------+------+---------+------+-------+---------------------------------+
| id | select_type | table  | type | possible_keys | key  | key_len | ref  | rows  | Extra                           |
+----+-------------+--------+------+---------------+------+---------+------+-------+---------------------------------+
|  1 | SIMPLE      | avatar | ALL  | NULL          | NULL | NULL    | NULL | 30062 | Using temporary; Using filesort |
+----+-------------+--------+------+---------------+------+---------+------+-------+---------------------------------+
1 row in set (0.00 sec)

 mysql> explain select name, experience from avatar order by Rand() limit 1;
+----+-------------+--------+------+--------------+------+---------+------+-------+---------------------------------+
| id | select_type | table  | type | possible_keys | key  | key_len | ref  | rows  | Extra                           |
+----+-------------+--------+------+---------------+------+---------+------+-------+---------------------------------+
|  1 | SIMPLE      | avatar | ALL  | NULL          | NULL | NULL    | NULL | 30064 | Using temporary; Using filesort |
+----+-------------+--------+------+---------------+------+---------+------+-------+---------------------------------+
2
Andrey Frolov

Je peux vous dire pourquoi le SELECT id FROM ... est beaucoup plus lent que les deux autres, mais je ne suis pas sûr de savoir pourquoi SELECT id, username est 2 à 3 fois plus rapide que SELECT *.

Lorsque vous avez un index (la clé primaire dans votre cas) et que le résultat inclut uniquement les colonnes de l'index, l'optimiseur MySQL peut uniquement utiliser les données de l'index, sans même regarder dans la table. Plus vous observerez de rangées, plus vous observerez d'effet, puisque vous substituez les opérations du système de fichiers IO aux opérations pures en mémoire. Si vous avez un index supplémentaire sur (id, nom d'utilisateur), vous obtiendrez une performance similaire dans le troisième cas également.

0
newtover

Pourquoi n’ajoutez-vous pas un index id, username sur la table pour voir si cela force mysql à utiliser l’index plutôt que juste une table filesort et une table temporaire.

0
jmoz