¿Fallo mío o del rails?

Fijaos en este código, que saca los tres últimos productos de
determinado subtipo y con plan completo:

@productos_guia = Producto.find(:all, :order => “productos.created_at
desc”, :limit => 3, :include => :images, :conditions =>
“productos.subtipo_id IN (81, 82, 83, 84, 85, 86, 87) and
productos.plan_completo”)

Detecto que la SQL va algo lenta, así que opto por añadir en MySQL un
índice para los campos en cuestión: subtipo_id, plan_completo y
created_at. Y fijaos mi sorpresa al repasar las SQLs generadas:

Producto Load IDs For Limited Eager Loading
(0.036371)
SELECT id FROM productos WHERE (productos.subtipo_id IN (81, 82, 83,
84, 85, 86, 87) and productos.plan_completo) ORDER BY
productos.created_at desc LIMIT 3e

Producto Load Including Associations
(0.237125)
SELECT […] FROM productos LEFT OUTER JOIN images ON
images.producto_id = productos.id WHERE (productos.subtipo_id IN (81,
82, 83, 84, 85, 86, 87) and productos.plan_completo) AND productos.id
IN (35694, 35628, 35626) ORDER BY productos.created_at desc

Primero hace un eager loading, no sé bien por qué, pero que pasa por el
índice que he definido y va rápido…

Pero después, en la SQL buena, me mete un filtrado por todas las
condiciones Y ADEMÁS POR ID, con lo que ya no pasa por mi índice y
vuelve a ir lenta. Pero es que es un filtrado estúpido: si con el eager
loading ha cargado las IDs, basta con filtrar por IDs, sobraría el
filtrado por subtipo_id y plan_completo. O si no, que deje sólo el
filtrado por subtipo_id y plan_completo, que ya está optimizado, y no
añada también el filtrado por ID… y se salte la optimización!!

¿Esto es un fallo de programación del Rails (2.0.2, en mi caso), o es
que debería haberle pasado alguna condición que no le estoy pasando?

s2

Hola,

created_at. Y fijaos mi sorpresa al repasar las SQLs generadas:

sorpresa por no leerte la doc de los cambios en rails :wink:

Producto Load IDs For Limited Eager Loading

query de la tabla principal

Producto Load Including Associations

query de la tabla asociada uniendo con los id de la principal

¿Esto es un fallo de programación del Rails (2.0.2, en mi caso), o es
que debería haberle pasado alguna condición que no le estoy pasando?

no es un fallo, sino una feature. Después de unas cuantas discusiones se
decidió en rails core que el mecanismo de eager loading se cambiaba.

Cuando en tu select haces una include de sólo una tabla, no es un
problema serio, pero si haces varios includes, entonces la select que se
ejecuta es una select bastante grande cruzando todas las tablas y dando
lugar a un resultset bastante grande. Al parecer, en muchos casos es más
óptimo lanzar una query pequeña por tabla, que una query grande cruzando
todas las tablas. Es por eso que primero se hace una query contra la
tabla principal (lo que te dice como LIMITED eager loading), y luego se
hace una query simple para cada tabla asociada buscando los ID
directamente dentro de la misma tabla. De esta forma, el número de filas
en memoria se reduce, y parece que en la base de datos también es más
rápido.

A partir de ya, es la forma estándar de trabajar en rails, así que tenlo
en cuenta en tus índices.

saludos,

javier ramírez

javier ramirez wrote:

Hola,

created_at. Y fijaos mi sorpresa al repasar las SQLs generadas:

sorpresa por no leerte la doc de los cambios en rails :wink:

Producto Load IDs For Limited Eager Loading

query de la tabla principal

Producto Load Including Associations

query de la tabla asociada uniendo con los id de la principal

¿Esto es un fallo de programación del Rails (2.0.2, en mi caso), o es
que debería haberle pasado alguna condición que no le estoy pasando?

no es un fallo, sino una feature. Después de unas cuantas discusiones se
decidió en rails core que el mecanismo de eager loading se cambiaba.

Cuando en tu select haces una include de sólo una tabla, no es un
problema serio, pero si haces varios includes, entonces la select que se
ejecuta es una select bastante grande cruzando todas las tablas y dando
lugar a un resultset bastante grande. Al parecer, en muchos casos es más
óptimo lanzar una query pequeña por tabla, que una query grande cruzando
todas las tablas. Es por eso que primero se hace una query contra la
tabla principal (lo que te dice como LIMITED eager loading), y luego se
hace una query simple para cada tabla asociada buscando los ID
directamente dentro de la misma tabla. De esta forma, el número de filas
en memoria se reduce, y parece que en la base de datos también es más
rápido.

A partir de ya, es la forma estándar de trabajar en rails, así que tenlo
en cuenta en tus índices.

saludos,

javier ramírez

Gracias por la explicación; el mecanismo y la razón los intuía, pero lo
que me sorprende y pienso que puede ser un fallo es que, teniendo el
eager loading, no se utilice óptimamente. ¿Por qué hacer esto?

WHERE (productos.subtipo_id IN (81,
82, 83, 84, 85, 86, 87) and productos.plan_completo) AND productos.id
IN (35694, 35628, 35626)

En realidad, bastaría y sobraría con esto:

WHERE productos.id IN (35694, 35628, 35626)

Y fíjate si va a ir rápido, una búsqueda por ID!!

s2

Perdon, aprovecho este hilo para preguntar una cosa relacionada.
Tenia entendido que hay un limite de valores cuando se utliza la funcion
“in” de SQL. ¿Es cierto o me lo he inventado? Si es cierto, rails no
tendrá ningun problema cuando la primera consulta que mostraba nuestro
compañero Fernando devuelva muchos resultados?

Espero haberme explicado.

que me sorprende y pienso que puede ser un fallo es que, teniendo el
eager loading, no se utilice óptimamente. ¿Por qué hacer esto?

http://dev.rubyonrails.org/ticket/9640

ahí tienes el hilo donde se decidió el cambio con la información de
primera mano.

suerte,

javier ramírez

Creo que la razón por la que hace esas consultas y no una sobre
productos y otra sobre images es que en la primera solo recupera el id
de los productos, no sus datos asociados. Después en la segunda
consulta debe hacer el JOIN de las dos tablas, pero reduciendo el
número de productos mediante el “productos.id IN…” y obteniendo los
datos de los productos. Lo de repetir los datos de la condición es un
poco estupido, pero supongo que en algún caso son necesarios esos
datos y no los eliminan en la segunda consulta.

Javier Martínez wrote:

Perdon, aprovecho este hilo para preguntar una cosa relacionada.
Tenia entendido que hay un limite de valores cuando se utliza la funcion
“in” de SQL. ¿Es cierto o me lo he inventado? Si es cierto, rails no
tendrá ningun problema cuando la primera consulta que mostraba nuestro
compañero Fernando devuelva muchos resultados?

Espero haberme explicado.

Un tema interesante, en este caso voy con LIMIT 3, pero si voy con LIMIT
300 el IN sería un desastre…

La verdad es que la optimización de SQLs, cuando es importante, conviene
hacerla “a mano”. Y no porque Rails sea malo haciéndolo, ni mucho
menos… pero es que es un trabajo artesanal, que hay que hacer
personalizado para cada consulta, y difícilmente se pueden dar recetas
genéricas óptimas…

s2