Ayuda para pptimizar consulta SQL: ORDER BY RAND() muy lento :/

  • Autor Autor RTR0N
  • Fecha de inicio Fecha de inicio
R

RTR0N

Beta
¡Usuario con pocos negocios! ¡Utiliza siempre saldo de Forobeta!
Amigos de Forobeta, después de horas de lectura, escribir mucho código y hacer muchos benchmarks vengo a consultarles lo siguiente:

Resulta que tengo 3 tablas, las cuales son más o menos así:

Insertar CODE, HTML o PHP:
tp_categorias
-cat_id
-cat_titulo

tp_videos
-vid_id
-vid_titulo

tp_videos_categorias
-vid_id
-cat_id
tp_videos.webp

  • La relación entre tp_videos y tp_categorias es de muchos a muchos (adjunto captura).
  • La tabla tp_categorias almacena informacion sobre las categorias para etiquetar un video.
  • La tabla tp_videos almacena información del video como título, descripción, etc.
  • La tabla tp_videos_categorias obviamente la relación entre estas 2 tablas.
Hasta el momento la tabla tp_categorias tiene 53 registros; tp_videos 22425 registros, tp_videos_categorias 47388 registros.

La aplicación debe en primer lugar mostrar información sobre el video que se esta visualizando (datos como título del video, su descripción, fecha en que fué subido, etc), los tags con los que fué etiquetado y finalmente mostrar un listado de 20 videos relacionados.

Para lo primero hice una consulta simple donde busco el registro en la tabla tp_videos a partir de un código. Mi consulta es esta:

Insertar CODE, HTML o PHP:
SELECT v.vid_id, v.vid_titulo
FROM tp_videos v
WHERE v.vid_codigo = '%s'
LIMIT 1

Con eso obtengo el ID y demás datos del video en cuestión.

La siguiente consulta que hago es para mostrar las etiquetas/categorias a las que pertenece el video. La consulta es:

Insertar CODE, HTML o PHP:
SELECT c.cat_id, c.cat_titulo
FROM tp_categorias c
WHERE c.cat_id IN (
[INDENT]SELECT vc.cat_id
FROM tp_videos_categorias vc
WHERE vc.vid_id = %d[/INDENT]
)

Eso me devuelve el listado de categorias con las que fué etiquedado el video.

Hasta ahí ningún problema, las 2 consultas van como el rayo.

Mi siguiente consulta es para mostrar un listado con 20 videos relacionados de forma aleatoria. Lo resolví con 2 consultas:

Insertar CODE, HTML o PHP:
SELECT vc.vid_id id
FROM tp_videos_categorias vc
WHERE vc.cat_id IN (%s)
ORDER BY RAND()
LIMIT %d
Aquí en %s va el listado de los cat_id que he recuperado en la consulta anterior y %d es la cantidad de videos relacionados.

Insertar CODE, HTML o PHP:
SELECT v.vid_id, v.vid_titulo
FROM tp_videos v
WHERE v.vid_id IN (%s)
Y finalmente en esta consulta recupero la información de los videos a partir de los vid_id que ha devuelto la consulta anterior.

Cabe mencionar que todo lo anterior lo he resuelto de distintas formas, pero a pesar de que esta última forma son 4 consultas es la que me va más rápido.

EL PROBLEMA básicamente esta con el ORDER BY RAND() de la penúltima consulta, hace que el servidor se ponga muy lento y eso es lo que trato de evitar y tener la consulta lo más optimizado posible para que mi aplicación pueda soportar la máxima cantidad de usuarios al mismo tiempo y de ser posible soportar algunos ataques de niños rata que nunca faltan en la red.
Si alguien pudiera darme una mano para optimizar esa consulta o tiene alguna idea mejor le estaría muy agradecido. No quiero que el servidor explote :/

Haciendo pruebas de rendimiento, ejecutar esas consultas 1000 veces le demora al servidor lo siguientes:

- Sólo la primera consulta: 0.35 seg de media.
- Las 2 primeras consultas : 0.65 seg de media.
- Las 4 consultas: 31 seg. de media :ambivalence:
- Las 4 consultas sin el ORDER BY RAND(): 1.45seg de media:

Como podrán ver el ordenamiento aleatorio hace que la consulta se demore en promedio 20 veces más, pero es muy importante que los videos relacionados se muestren de forma aleatoria o de lo contrario siempre serian los mismos y así no va.

Si no resuelvo esto pronto me volveré loco o voy a morir, ya van toda la noche leyendo (ahora son 05:30am) y ninguna forma de resolverlo :sleeping:

Mi servidor de pruebas es un Core 2 duo E8400.
 
Primero, te recomiendo, si no lo has hecho, que hagas uso de JOINs en vez de subselects en condicionales.

Por otro lado, si no te importa que los videos aleatorios de la categoría sean seguidos, tienes un método mucho más rápido para ello, por ejemplo, si quieres 4 vídeos aleatorias de la categoría X, Y y Z prueba:

  1. Saber el total de videos de dichas categorías: SELECT COUNT(*) xxxxxxxxx WHERE categoria IN(X,Y,Z)
  2. Sacar un número aleatorio en PHP entre 0 y el número total del punto 1: $aleatorio = rand(0, (total_videos - NúmeroDeVideosQueQuieresMostrar));
  3. Hacer un LIMIT con ese número aleatorio: SELECT video_ID xxxxx WHERE categoria IN(X,Y,Z) LIMIT $aleatorio, NúmeroDeVideosQueQuieresMostrar

Así te ahorras el RAND, lo malo, es que este método va genial para sacar un RAND de 1 elemento, si es de más de uno, como en tu caso, te hará un RAND pero inicial, es decir, te dará los videos: 4,5,6,7 ... o .... 29,30,31,32. Es decir, randomiza el 1r video, pero los otros X que te interesen serán secuenciales a partir de ese random inicial.
 
Para las categorias puedes usar JOINS

SELECT c.cat_id, c.cat_titulo
FROM tp_videos_categorias vc
INNER JOIN tp_categorias c ON c.cat_id = vc.cat_id
WHERE vc.vid_id = %d
 
Estás usando índices en las tablas? Son muy pocos datos para que tarden tanto las consultas
 
Primero, te recomiendo, si no lo has hecho, que hagas uso de JOINs en vez de subselects en condicionales.

Por otro lado, si no te importa que los videos aleatorios de la categoría sean seguidos, tienes un método mucho más rápido para ello, por ejemplo, si quieres 4 vídeos aleatorias de la categoría X, Y y Z prueba:

  1. Saber el total de videos de dichas categorías: SELECT COUNT(*) xxxxxxxxx WHERE categoria IN(X,Y,Z)
  2. Sacar un número aleatorio en PHP entre 0 y el número total del punto 1: $aleatorio = rand(0, (total_videos - NúmeroDeVideosQueQuieresMostrar));
  3. Hacer un LIMIT con ese número aleatorio: SELECT video_ID xxxxx WHERE categoria IN(X,Y,Z) LIMIT $aleatorio, NúmeroDeVideosQueQuieresMostrar

Así te ahorras el RAND, lo malo, es que este método va genial para sacar un RAND de 1 elemento, si es de más de uno, como en tu caso, te hará un RAND pero inicial, es decir, te dará los videos: 4,5,6,7 ... o .... 29,30,31,32. Es decir, randomiza el 1r video, pero los otros X que te interesen serán secuenciales a partir de ese random inicial.

Acabo de hacer nuevas pruebas y ejecutando sólo las 2 primeras consultas (esta vez con 10 mil veces) y usando el JOIN los tiempos se reducen en exactamente un 20%. No sé si sea por el sueño o las drogas duras, pero estoy casi seguro que ayer usando JOIN me daba resultados peores y por eso opté por las subconsultas. De cualquier forma fué muy bueno esa ayuda, gracias 😛8:

Sobre elegir un número aleatorio entre el 0 y el total de resultados me temo que no daría muy buen resultado, porque puede pasar que me salga un número de ID que no necesariamente le corresponda a las categorias que estoy buscando, pero voy a probar en un momento a ver que sale :ambivalence:

Para las categorias puedes usar JOINS

SELECT c.cat_id, c.cat_titulo
FROM tp_videos_categorias vc
INNER JOIN tp_categorias c ON c.cat_id = vc.cat_id
WHERE vc.vid_id = %d

Gracias Prefs, la consulta funciona de maravillas, no sé como ayer supuestamente me dió resultados peores :topsy_turvy:

Estás usando índices en las tablas? Son muy pocos datos para que tarden tanto las consultas

Si amigo a1e30 😱 las columnas con las que estoy trabajando son las claves primarias y por lo tanto deberian ser tambien índices. Tambien la columna vid_code que uso en la primera columna esta indexado :s
 
Sobre elegir un número aleatorio entre el 0 y el total de resultados me temo que no daría muy buen resultado, porque puede pasar que me salga un número de ID que no necesariamente le corresponda a las categorias que estoy buscando, pero voy a probar en un momento a ver que sale :ambivalence:
¿Por qué? Si en la consulta que te he puesto filtro que devuelva IDs de videos con las categorías correspondientes, sólo te va a mostrar videos relacionados con esas X categorías, aplicas el LIMIT aleatorio y ya.
 
¿Por qué? Si en la consulta que te he puesto filtro que devuelva IDs de videos con las categorías correspondientes, sólo te va a mostrar videos relacionados con esas X categorías, aplicas el LIMIT aleatorio y ya.

Eres un genio LiveOrDie, no sé donde tenia la cabeza :topsy_turvy: esto funciona muy bien, con la única desventaja que ya has mencionado (los siguientes 10 resultados son casi contiguos), pero de todas formas es una solución muy brillante y me ha reducido los tiempos hasta en un 50%, pasando de 31 a 15 segundos 😀

Por ahora me quedo con esta solución y dejo el tema aquí.

Gracias a todos por sus aportes. :encouragement:
 
PHP:
SELECT loquesea FROM dondesea WHERE loquesea IN (SELECT FLOOR(RAND()*(SELECT MAX(loquesea) FROM dondesea)) FROM dondesea) LIMIT 20

:devilish:
 
Atrás
Arriba