martes, 30 de noviembre de 2010

Tablas particionadas

Si tienes una tabla con varios millones de registros y sabes de antemano que se va a consultar por periodos mensuales, puedes particionar la tabla por años y meses. Así cuando hagas consultas, sólo utilizarás la partición correspondiente y mejorará el rendimiento.

Al particionar por los valores de un determinado campo, es obligatorio tener dicho campo como parte de la primary key de la tabla.

A continuación una migración para crear una tabla particionada por meses y años:

 class CreateHistoryAlerts < ActiveRecord::Migration

  FIRST_YEAR = 2010
  LAST_YEAR  = 2020

  def self.up
    create_table :history_alerts do |t|
      t.integer :agent_id,              :references => nil
      t.integer :alert_type_id,         :references => nil
      t.integer :country_id,            :references => nil
      t.string  :reason
      t.boolean :read

      t.timestamps
    end

    sql = "ALTER TABLE `history_alerts` drop primary key, add primary key(id, created_at)"
    execute(sql);


    sql = "ALTER TABLE `history_alerts` PARTITION BY RANGE (YEAR(created_at)) \
           SUBPARTITION BY HASH (MONTH(created_at)) \
           ("

    for year in (FIRST_YEAR..LAST_YEAR)
      sql += "PARTITION p_#{year} VALUES LESS THAN (#{year}) \
              (SUBPARTITION s_jan_#{year}, \
               SUBPARTITION s_feb_#{year}, \
               SUBPARTITION s_mar_#{year}, \
               SUBPARTITION s_apr_#{year}, \
               SUBPARTITION s_may_#{year}, \
               SUBPARTITION s_jun_#{year}, \
               SUBPARTITION s_jul_#{year}, \
               SUBPARTITION s_aug_#{year}, \
               SUBPARTITION s_sep_#{year}, \
               SUBPARTITION s_oct_#{year}, \
               SUBPARTITION s_nov_#{year}, \
               SUBPARTITION s_dec_#{year}),"
    end
    sql += "PARTITION p_all VALUES LESS THAN MAXVALUE \
            (SUBPARTITION s_jan_all, \
             SUBPARTITION s_feb_all, \
             SUBPARTITION s_mar_all, \
             SUBPARTITION s_apr_all, \
             SUBPARTITION s_may_all, \
             SUBPARTITION s_jun_all, \
             SUBPARTITION s_jul_all, \
             SUBPARTITION s_aug_all, \
             SUBPARTITION s_sep_all, \
             SUBPARTITION s_oct_all, \
             SUBPARTITION s_nov_all, \
             SUBPARTITION s_dec_all))"
    execute(sql);
  end


  def self.down
    drop_table :history_alerts
  end

end

2 comentarios:

  1. Y en ese caso, ¿cuando consultas tienes que especificar siempre el campo por el que has particionado o puedes no usarlo y simplemente todo irá más lento?

    ResponderEliminar
  2. Es completamente transparente. Si utilizas otro campo en el where de la consulta, utilizará las particiones que hagan falta. Seguirá funcionando como si no hubiera particiones.

    La gracia de esto es tener claro desde el principio que las queries se van a hacer generalmente filtrando con el campo de particionado. Este ejemplo está pensado para consultar la tabla por periodos mensuales, así sólo utiliza una partición.

    ResponderEliminar