LoginSignup
1
2

More than 3 years have passed since last update.

Rails で MySQL インデックスヒントを使う (USE INDEX, IGNORE INDEX, FORCE INDEX)

Last updated at Posted at 2019-07-19

MySQL を使った Rails で

Model.where(created_at: 1.yea.ago..1.day.ago).order(:id).last

などとすると、MySQL の仕様で created_at の INDEX ではなく、PRIMARY を使ってしまってものすごく遅くなることがあった。

調べてみたところ、どうやら自分で拡張するしかないようなので書いたという話です。

Concern

ググると類似コードが出てきますが、違いは下記の通り

  • カラム名で指定可能
  • 複合インデックスも[カラム名, カラム名]で指定可能
  • 普通にインデックス名もok
app/models/concerns/index_hint.rb
module IndexHint
  extend ActiveSupport::Concern

  class_methods do
    def convert_to_index_name_in_case_of_column_name(*names)
      names.map { |idx| find_index_name_by_colomun_names(idx) || idx }
    end

    private

    def find_index_name_by_colomun_names(column_names)
      column_names = Array.wrap(column_names).map(&:to_s)

      name = connection.indexes(table_name).find { |index| index.columns == column_names }&.name
      connection.quote_column_name(name) if name
    end
  end

  included do
    scope :use_index, lambda { |*indexes|
      index_names = convert_to_index_name_in_case_of_column_name(*indexes)
      from("#{quoted_table_name} USE INDEX(#{index_names.join(', ')})")
    }

    scope :ignore_index, lambda { |*indexes|
      index_names = convert_to_index_name_in_case_of_column_name(*indexes)
      from("#{quoted_table_name} IGNORE INDEX(#{index_names.join(', ')})")
    }

    scope :force_index, lambda { |*indexes|
      index_names = convert_to_index_name_in_case_of_column_name(*indexes)
      from("#{quoted_table_name} FORCE INDEX(#{index_names.join(', ')})")
    }
  end
end

Use

ApplicationRecord レベルで include してあげれば全てのモデルで使えますね。
モデル単体で include してもokです。

app/models/application_record.rb
class ApplicationRecord < ActiveRecord::Base
  include IndexHint
...

実際に使うとこんな感じ

Model.use_index(:created_at).to_sql
#=> "SELECT `models`.* FROM `models` USE INDEX(`index_models_on_created_at`)"

Model.force_index([:section, :name]).to_sql
#=> "SELECT `models`.* FROM `models` FORCE INDEX(`index_models_on_section_and_name`)"

Model.ignore_index([:section, :name], :created_at).to_sql
#=> "SELECT `models`.* FROM `models` IGNORE INDEX(`index_models_on_section_and_name`, `index_models_on_created_at`)"

注意点

  • from, use_index, force_index, ignore_index を組み合わせられない

参考

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2