Me gustaría optimizar mis consultas, así que analizo mysql-slow.log
.
La mayoría de mis consultas lentas contienen ORDER BY RAND()
. No puedo encontrar una solución real para resolver este problema. Hay una posible solución en MySQLPerformanceBlog, pero no creo que sea suficiente. En tablas mal optimizadas (o frecuentemente actualizadas, administradas por el usuario) no funciona o necesito ejecutar dos o más consultas antes de poder seleccionar mi PHP
fila aleatoria generada.
¿Existe alguna solución para este problema?
Un ejemplo ficticio:
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
fuente
fuente
Respuestas:
Prueba esto:
Esto es especialmente eficiente en
MyISAM
(ya queCOUNT(*)
es instantáneo), pero incluso enInnoDB
sus10
momentos es más eficiente queORDER BY RAND()
.La idea principal aquí es que no ordenamos, sino que mantenemos dos variables y calculamos el valor
running probability
de una fila que se seleccionará en el paso actual.Consulte este artículo en mi blog para obtener más detalles:
Actualizar:
Si necesita seleccionar un solo registro aleatorio, intente esto:
Esto supone que
ac_id
los suyos se distribuyen de manera más o menos uniforme.fuente
@fabrik
: Probar ahora. Sería realmente útil si publicara los scripts de la tabla para que yo pudiera verificarlos antes de publicarlos.Depende de lo aleatorio que deba ser. La solución que vinculó funciona bastante bien en mi opinión. A menos que tenga grandes lagunas en el campo ID, sigue siendo bastante aleatorio.
Sin embargo, debería poder hacerlo en una consulta usando esto (para seleccionar un solo valor):
Otras soluciones:
random
a la tabla y rellénelo con números aleatorios. Luego puede generar un número aleatorio en PHP y hacer"SELECT ... WHERE rnd > $random"
fuente
SELECT [fields] FROM [table] WHERE id >= FLOOR(RAND()*(SELECT MAX(id) FROM [table])) LIMIT 1
pero esto no parece funcionar correctamente ya que nunca devuelve el último registroSELECT [fields] FROM [table] WHERE id >= FLOOR(1 + RAND()*(SELECT MAX(id) FROM [table])) LIMIT 1
Parece que me está haciendo el trucoAsí es como lo haría:
fuente
OFFSET
(para qué@r
sirve) no evita un escaneo, hasta un escaneo de tabla completo.(Sí, me van a pegar por no tener suficiente carne aquí, pero ¿no puedes ser vegano por un día?)
Caso: AUTO_INCREMENT consecutivo sin espacios, 1 fila devuelta
Caso: AUTO_INCREMENT consecutivo sin espacios, 10 filas
Caso: AUTO_INCREMENT con espacios, 1 fila devuelta
Caso: Columna FLOAT adicional para aleatorizar
Caso: UUID o columna MD5
Esos 5 casos se pueden hacer muy eficientes para mesas grandes. Vea mi blog para más detalles.
fuente
Esto le dará una única subconsulta que usará el índice para obtener una identificación aleatoria y luego la otra consulta se activará al obtener su tabla unida.
fuente
La solución para su ejemplo ficticio sería:
Para leer más sobre alternativas a
ORDER BY RAND()
, debe leer este artículo .fuente
Estoy optimizando muchas consultas existentes en mi proyecto. ¡La solución de Quassnoi me ha ayudado a acelerar mucho las consultas! Sin embargo, encuentro difícil incorporar dicha solución en todas las consultas, especialmente para consultas complicadas que involucran muchas subconsultas en múltiples tablas grandes.
Entonces estoy usando una solución menos optimizada. Fundamentalmente, funciona de la misma manera que la solución de Quassnoi.
$size * $factor / [accomodation_table_row_count]
calcula la probabilidad de elegir una fila aleatoria. El rand () generará un número aleatorio. La fila se seleccionará si rand () es menor o igual a la probabilidad. Esto efectivamente realiza una selección aleatoria para limitar el tamaño de la mesa. Dado que existe la posibilidad de que devuelva menos que el recuento límite definido, debemos aumentar la probabilidad para asegurarnos de que estamos seleccionando suficientes filas. Por lo tanto, multiplicamos $ size por un $ factor (normalmente establezco $ factor = 2, funciona en la mayoría de los casos). Finalmente hacemos ellimit $size
El problema ahora es resolver el conteo de filas de mesas de alojamiento . Si conocemos el tamaño de la tabla, PODEMOS codificar el tamaño de la tabla. Este sería el más rápido, pero obviamente no es lo ideal. Si está utilizando Myisam, obtener el recuento de tablas es muy eficiente. Como estoy usando innodb, solo estoy haciendo una selección simple de conteo +. En su caso, se vería así:
La parte complicada es calcular la probabilidad correcta. Como puede ver, el siguiente código en realidad solo calcula el tamaño aproximado de la tabla de temperatura (de hecho, ¡demasiado aproximado!):
(select (SELECT count(*) FROM accomodation) * (SELECT count(*) FROM accomodation_category))
Pero puede refinar esta lógica para dar una aproximación más cercana del tamaño de la tabla. Tenga en cuenta que es mejor SOBRE-seleccionar que sub-seleccionar filas. es decir, si la probabilidad es demasiado baja, corre el riesgo de no seleccionar suficientes filas.Esta solución funciona más lentamente que la solución de Quassnoi, ya que necesitamos recalcular el tamaño de la tabla. Sin embargo, encuentro esta codificación mucho más manejable. Esta es una compensación entre precisión + rendimiento y complejidad de codificación . Dicho esto, en tablas grandes esto sigue siendo mucho más rápido que Order by Rand ().
Nota: Si la lógica de la consulta lo permite, realice la selección aleatoria lo antes posible antes de cualquier operación de combinación.
fuente
fuente