Qué índices se deben usar cuando los resultados de la consulta se ordenan mediante una asociación has_many :through

La pregunta puede ser larga, pero el problema es realmente simple. Tengo 3 modelos: Lista, Apariencia y Película. Una Lista tiene muchas Películas a través de una Apariencia, y deben ordenarse por el rango de atributos del modelo de unión. Entonces, ¿qué índices debo usar? Así es como se ven mis modelos actualmente:

# Models
class Movie < ActiveRecord::Base  
  has_many :appearances, :dependent => :destroy
  has_many :lists, :through => :appearances
end

class Appearance < ActiveRecord::Base
  belongs_to :list
  belongs_to :movie
end

class List < ActiveRecord::Base
  has_many :appearances, :dependent => :destroy
  has_many :movies, :through => :appearances

  def self.find_complete(id)
    List.includes({:appearances => :movie}).where("appearances.rank IS NOT NULL").order("appearances.rank ASC").find(id)
  end
end

Y aquí están los índices que ya tengo. ¿Necesito un índice compuesto? ¿O simplemente un índice por rango?

# Tables
class CreateAppearances < ActiveRecord::Migration
  def change
    create_table :appearances do |t|
      t.integer :list_id, :null => false
      t.integer :movie_id, :null => false
      t.integer :rank
    end
    add_index :appearances, :list_id
    add_index :appearances, :movie_id
  end
end

Finalmente, ¿hay alguna forma de refactorizar ese método de lista find_complete? ¿Te gusta usar un default_scope? No olvide que un rango puede ser nulo.

preguntado el 22 de mayo de 12 a las 16:05

3 Respuestas

ActiveRecord está haciendo dos consultas.

SELECT DISTINCT `lists`.id
FROM `lists` LEFT OUTER JOIN `appearances` ON `appearances`.`list_id` = `lists`.`id`
             LEFT OUTER JOIN `movies` ON `movies`.`id` = `appearances`.`movie_id`
WHERE `lists`.`id` = 1 AND (appearances.rank IS NOT NULL)
ORDER BY appearances.rank ASC LIMIT 1

y

SELECT `lists`.`id` AS t0_r0, `lists`.`name` AS t0_r1, `lists`.`created_at` AS t0_r2, `lists`.`updated_at` AS t0_r3, `appearances`.`id` AS t1_r0, `appearances`.`list_id` AS t1_r1, `appearances`.`movie_id` AS t1_r2, `appearances`.`rank` AS t1_r3, `appearances`.`created_at` AS t1_r4, `appearances`.`updated_at` AS t1_r5, `movies`.`id` AS t2_r0, `movies`.`name` AS t2_r1, `movies`.`created_at` AS t2_r2, `movies`.`updated_at` AS t2_r3
FROM `lists` LEFT OUTER JOIN `appearances` ON `appearances`.`list_id` = `lists`.`id`
             LEFT OUTER JOIN `movies` ON `movies`.`id` = `appearances`.`movie_id`
WHERE `lists`.`id` = 1 AND `lists`.`id` IN (1) AND (appearances.rank IS NOT NULL)
ORDER BY appearances.rank ASC

En ambos casos, lo que necesita es un índice con listas.id y apariencias.rango. Algunas bases de datos crearán índices en varias tablas. Tampoco sé si su base de datos será lo suficientemente inteligente como para usar un índice en apariciones.list_id y apariciones.rank, pero vale la pena intentarlo.

add_index :appearances, [:list_id, :rank]

Si eso no ayuda, al menos indexe: rango, para que la base de datos pueda evitar la ordenación.

contestado el 22 de mayo de 12 a las 17:05

De hecho, tiene razón en que está haciendo dos consultas. Y supongo que debería haber explicado en mi pregunta que estoy usando PostgreSQL. Voy a probar tu solución. - Ashitaka

Debe ver qué consultas generará Rails para sus casos de uso específicos.

Mi mejor suposición sería agregar índices en las columnas de clave externa y luego usar un perfilador una vez que la aplicación esté lista y cargada con datos para ver si las consultas en particular tienen un rendimiento deficiente y ¡arréglelas!

Nunca intente burlar su base de datos, solo mida y corrija; cualquier otra cosa probablemente le costará más rendimiento de lo que ahorra.

Lo que está preguntando aquí es una forma de optimización prematura y también un poco de conjetura, considerando cómo Rails3 Arel aplazará la ejecución para que los índices que realmente se necesitan dependan en gran medida de cómo se utilizará esta asociación y cómo se atravesará.

contestado el 22 de mayo de 12 a las 16:05

Vuelva a comprobar sus asociaciones. Si

Una Lista tiene muchas Películas a través de una Apariencia

class List < ActiveRecord::Base
 has_many :appearances, :dependent => :destroy
 has_many :movies, :through => :appearances
end

class Appearance < ActiveRecord::Base
 belongs_to :list, :foreign_key => list_id
 has_many :movies
end

class Movie < ActiveRecord::Base  
 belongs_to :appearance, :foreign_key => appearance_id, :dependent => :destroy  
end

En cuanto al método find_complete, no veo por qué convertirlo en un alcance. Probablemente tenga que hacerse un poco más corto ya que ya está dentro del modelo List y se ve muy raro llamar a List.includes .... dentro de sí mismo.

No veo la necesidad de ese índice movie_id

class CreateAppearances < ActiveRecord::Migration
  def change
    create_table :appearances do |t|
      t.integer :list_id, :null => false
      t.integer :movie_id, :null => false
      t.integer :rank
    end
  add_index :appearances, :list_id
 end
end

Y también necesita un índice de apariencia_id para su tabla de películas.

espero no perderme nada

contestado el 22 de mayo de 12 a las 16:05

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.