2009-12-01 11 views
45

He leído sobre algunas alternativas a la función ORDER BY RAND() de MySQL, pero la mayoría de las alternativas se aplican solo cuando se necesita un solo resultado aleatorio.MySQL: Alternativas a ORDER BY RAND()

¿Alguien tiene alguna idea de cómo optimizar una consulta que devuelve varios resultados aleatorios, como este:

SELECT u.id, 
      p.photo 
    FROM users u, profiles p 
    WHERE p.memberid = u.id 
     AND p.photo != '' 
     AND (u.ownership=1 OR u.stamp=1) 
ORDER BY RAND() 
    LIMIT 18 
+0

No entiendo lo que estás buscando. ¿Por qué no es adecuado 'ORDER BY RAND()'? ¿Le preocupa principalmente la eficiencia? – outis

+0

Sí, eso es correcto. No he llegado ni siquiera cerca de la escala presentada en su gráfico y ya estaba recibiendo un golpe. – Tony

+1

@outis: Como no escala, consulte: http://www.dasprids.de/blog/2008/06/07/fetching-random-rows-of-mysql-efficiently –

Respuesta

18

Aquí hay una alternativa, pero todavía se basa en el uso de RAND():

SELECT u.id, 
     p.photo, 
     ROUND(RAND() * x.m_id) 'rand_ind' 
    FROM users u, 
     profiles p, 
     (SELECT MAX(t.id) 'm_id' 
      FROM USERS t) x 
    WHERE p.memberid = u.id 
    AND p.photo != '' 
    AND (u.ownership=1 OR u.stamp=1) 
ORDER BY rand_ind 
    LIMIT 18 

Esto es ligeramente más compleja, pero dio una mejor distribución de los valores random_ind:

SELECT u.id, 
     p.photo, 
     FLOOR(1 + RAND() * x.m_id) 'rand_ind' 
    FROM users u, 
     profiles p, 
     (SELECT MAX(t.id) - 1 'm_id' 
      FROM USERS t) x 
    WHERE p.memberid = u.id 
    AND p.photo != '' 
    AND (u.ownership=1 OR u.stamp=1) 
ORDER BY rand_ind 
    LIMIT 18 
+0

OMG excepcional. Muchas gracias. Si bien mi DB no es enorme, ya noté un ligero aumento en el rendimiento y me siento mucho más cómodo para seguir adelante. – Tony

+0

Esta respuesta me ayudó mucho, ya que la mayoría de las otras "soluciones" que he encontrado descartan las complejas cláusulas WHERE. ¡Gracias! – MattBianco

+0

@OMG ¿me puede explicar su código? – Michelle

0

Me encontré con esto hoy y estaba tratando de usar 'DISTINCT' junto con JOINs, pero estaba obteniendo duplicados, supongo porque el RAND estaba haciendo que cada fila de JOIN fuera distinta. Me embrollado un poco y encontré una solución que funciona, así:

SELECT DISTINCT t.id, 
       t.photo 
     FROM (SELECT u.id, 
        p.photo, 
        RAND() as rand 
       FROM users u, profiles p 
       WHERE p.memberid = u.id 
        AND p.photo != '' 
        AND (u.ownership=1 OR u.stamp=1) 
       ORDER BY rand) t 
     LIMIT 18 
+0

Esto parece exactamente lo mismo que MySql cuando usas' ORDER BY RAND() '. – rcdmk

+0

lo probé y si tiene un valor de rand en su conjunto de resultados (como se hace en las soluciones de Ponies de OMG), DISTINCT se niega. Así que así fue como logré eso. –

0

La solución que estoy usando también se puede encontrar en el siguiente enlace: How can i optimize MySQL's ORDER BY RAND() function?

Estoy asumiendo que su tabla de usuarios va a ser más grande que la tabla de perfiles, si no es así, es de 1 a 1 cardinalidad.

Si es así, primero haría una selección aleatoria en la tabla de usuarios antes de unirme a la tabla de perfiles.

En primer lugar hacer la selección:

SELECT * 
FROM users 
WHERE users.ownership = 1 OR users.stamp = 1 

Luego de este grupo, escoger al azar a través de las filas probabilidad calculada. Si su tabla tiene M filas y desea seleccionar N filas al azar, la probabilidad de selección al azar debe ser N/M. Por lo tanto:

SELECT * 
FROM 
(
    SELECT * 
    FROM users 
    WHERE users.ownership = 1 OR users.stamp = 1 
) as U 
WHERE 
    rand() <= $limitCount/(SELECT count(*) FROM users WHERE users.ownership = 1 OR users.stamp = 1) 

Donde N es $ limitCount y M es la sub consulta que calcula el número de fila de la tabla. Sin embargo, dado que estamos trabajando en la probabilidad, es posible tener MENOS de $ limitCount de filas devueltas. Por lo tanto, debemos multiplicar N por un factor para aumentar el tamaño del conjunto aleatorio.

es decir:

SELECT* 
FROM 
(
    SELECT * 
    FROM users 
    WHERE users.ownership = 1 OR users.stamp = 1 
) as U 
WHERE 
    rand() <= $limitCount * $factor/(SELECT count(*) FROM users WHERE users.ownership = 1 OR users.stamp = 1) 

lo general conjunto $ = factor de 2. Se puede establecer el factor a un valor más bajo para reducir aún más el tamaño de la piscina al azar (por ejemplo 1,5).

En este punto, ya habríamos limitado una tabla de tamaño M hasta aproximadamente 2N de tamaño. Desde aquí podemos hacer un JOIN y luego LIMIT.

SELECT * 
FROM 
(
     SELECT * 
     FROM 
     (
      SELECT * 
      FROM users 
      WHERE users.ownership = 1 OR users.stamp = 1 
     ) as U 
     WHERE 
      rand() <= $limitCount * $factor/(SELECT count(*) FROM users WHERE users.ownership = 1 OR users.stamp = 1) 
) as randUser 
JOIN profiles 
ON randUser.id = profiles.memberid AND profiles.photo != '' 
LIMIT $limitCount 

En una tabla grande, esta consulta superará a una ORDEN normal por consulta RAND().

Espero que esto ayude!

1

Crea una columna o únete a una selección con números aleatorios (generados en, por ejemplo, php) y ordena por esta columna.

20

ACTUALIZACIÓN 2016

Esta solución funciona mejor usando una columna indexada .

Aquí hay un ejemplo simple de banco de consulta optimizado marcado con 100.000 filas.

optimizado: 300ms

SELECT 
    g.* 
FROM 
    table g 
     JOIN 
    (SELECT 
     id 
    FROM 
     table 
    WHERE 
     RAND() < (SELECT 
       ((4/COUNT(*)) * 10) 
      FROM 
       table) 
    ORDER BY RAND() 
    LIMIT 4) AS z ON z.id= g.id 

nota sobre límite ammount: Límite 4 y 4/count (*). Los 4 deben ser el mismo número. Cambiar la cantidad que devuelve no afecta tanto la velocidad. El punto de referencia en el límite 4 y el límite 1000 son iguales. Límite de 10,000 lo llevó a 600 ms

Nota sobre unirse a: aleatorizar solo el ID es más rápido que aleatorizar una fila completa. Como tiene que copiar toda la fila en la memoria, aleatorícela. La unión puede ser cualquier tabla que esté vinculada a la subconsulta Its para evitar tablescans.

observe donde la cláusula: El recuento donde limita la cantidad de resultados que se están aleatorizando. Toma un porcentaje de los resultados y los ordena en lugar de toda la tabla.

nota sub consulta: las condiciones if junt y extra where cláusula necesita colocarlas tanto en la subconsulta como en la subversión. Tener un recuento preciso y recuperar los datos correctos.

no optimizadas: 1200ms

SELECT 
    g.* 
FROM 
    table g 
ORDER BY RAND() 
LIMIT 4 

PROS

4x más rápido que order by rand(). Esta solución puede funcionar con cualquier tabla con una columna indexada.

CONS

Es un poco complejo con consultas complejas. Necesidad de mantener 2 bases de código en las subconsultas

+2

Muy agradable. Voy a estar seguro de usar esto. –

+2

Usar un rango de identificadores aleatorios podría ser aún más útil si toma esos identificadores y los coloca en una capa de almacenamiento en caché durante 10 segundos, luego deje que la aplicación seleccione aleatoriamente los identificadores en la capa de almacenamiento en caché. –

-1

Order by rand() es muy lento en tablas grandes,

me encontré con la siguiente solución en un script php:

Select min(id) as min, max(id) as max from table; 

Después, realice al azar en php

$rand = rand($min, $max); 

Entonces

'Select * from table where id>'.$rand.' limit 1'; 

Parece ser bastante rápido ...

2

No es el más rápido, pero más rápido que forma común ORDER BY RAND():

ORDER BY RAND() no es tan lento, cuando lo utiliza para encontrar columna sólo indexado. Puede tomar todas sus identificaciones en una consulta como esta:

SELECT id 
FROM testTable 
ORDER BY RAND(); 

para obtener una secuencia de identificadores de azar, y JOIN el resultado a otra pregunta con otra SELECT o WHERE parámetros:

SELECT t.* 
FROM testTable 
JOIN 
    (SELECT id 
    FROM `testTable` 
    ORDER BY RAND()) AS z ON z.id= t.id 
WHERE isVisible = 1 
LIMIT 100; 

en su caso sería:

SELECT u.id, p.photo 
FROM users u, profiles p 
JOIN 
    (SELECT id 
    FROM users 
    ORDER BY RAND()) AS z ON z.id= u.id 
WHERE p.memberid = u.id 
    AND p.photo != '' 
    AND (u.ownership=1 OR u.stamp=1) 
LIMIT 18 

es método muy contundente y no puede ser adecuado con mesas muy grandes, pero todavía es más rápido que RAND() común. Tengo un tiempo de ejecución 20 veces más rápido buscando 3000 filas aleatorias en casi 400000.