🐕

Railsにおけるインデックスヒントの使い方

2023/01/09に公開

はじめに

インデックスヒントはパフォーマンスチューニングにおいて欠かせないものの一つです。
今回はインデックスヒントとは何か、そして実際にRailsではどのように使うのか説明していきます。

実行計画について

インデックスヒントを理解するため、前提として知っておきたい実行計画について説明します。
MySQLはオプティマイザというものを使用して、いい感じに実行計画(実行するクエリの詳細)を作成しています。
https://dev.mysql.com/doc/refman/5.6/ja/optimizer-issues.html
この実行計画はオプティマイザが以下の統計情報を参照して作成されています。

  • テーブルに含まれる行数
  • 各インデックスのサイズ(ページ数)
  • 各インデックスのリーフページのサイズ(ページ数)
  • 各インデックスのカーディナリティ(何種類の値が存在するか?)

ここで注意しなければならないことは、実行計画はあくまで推測であり必ず最適な訳ではないことです。
場合によってはMySQLが最適だと判断したクエリが、想定以上に遅いクエリになってしまうことがあリます。

そういった場合はSQLチューニングによって適切な実行計画に変更する必要があるのですが、その際使われるのがインデックスヒントです。

インデックスヒントとは

インデックスヒントはSQLチューニングの際に利用するヒント句の1つです。
https://dev.mysql.com/doc/refman/5.6/ja/index-hints.html
インデックスヒントは3つありそれぞれ以下の役割があります。

  • USE INDEX
    • 特定のインデックスを使用するように、オプティマイザに指示を出す
  • FORCE INDEX
    • USE INDEXに似ているが、USE INDEXに加えて「テーブルスキャンを選択しない」という指示を出す
  • IGNORE INDEX
    • 特定のインデックスを使用しないように、オプティマイザに指示を出す

例えばhogeテーブルでindex_hoges_on_column_1インデックスを指定したい時は以下のように書きます

SELECT
    *
FROM
    hoges
FORCE INDEX (index_hoges_on_column_1)
where
    column_1 = 1
and column_2 = 2
;

どういった場合にこのインデックスヒントを使うのかは別記事で説明してますので、興味ある方はご覧ください
https://zenn.dev/zenkigen/articles/2022-09-kawamata-slowquery

Active Recordでの使い方

Railsでインデックヒントを使うにはActive Recordの発行するクエリの中にヒント句を差し込む必要があります。2通りの方法があるのでそれぞれご紹介します。

1. joinsメソッド

joinsメソッドはテーブルの内部結合で使われることが多いですが、引数に文字列を渡すことでクエリを編集することができます。

@hoges = Hoge.joins("FORCE INDEX (index_hoges_on_column_1)")
                        .where(column_1: 1)
                        .and(column_2: 2) 

しかし使い方によってはエラーが発生する場合があります。
エラーの原因はjoinsメソッドとテーブル結合のメソッド(includes, preload, joinsなど)を併用すると、ヒント句が正しい位置に差し込まれないためです。

@hoges = Hoge.joins("FORCE INDEX (index_hoges_on_column_1)")
                        . joins(:piyo)
                        .where(column_1: 1)
                        .and(column_2: 2) 
-- 発行クエリ
SELECT
    *
FROM
    hoges
-- この行にヒント句を書きたい。。。
    INNER JOIN
        piyos
    on  hoges.id = piyos.hoge_id
FORCE INDEX(index_hoges_on_column_1) -- FROM句の下にあるべき。。
where
    hoges.column_1 = 1
and hoges.column_2 = 2
;

そのような場合は次に紹介するfromメソッドを使います。

2. fromメソッド

fromメソッドはクエリのFROM句を書き換えることができるメソッドです。
ヒント句を使う際、特別な理由がなければfromメソッドを使えば良いかなと思います。

@hoges = Hoge.from("hoges FORCE INDEX (index_hoges_on_column_1)")
                        . joins(:piyo)
                        .where(column_1: 1)
                        .and(column_2: 2) 
-- 発行クエリ
SELECT
    *
FROM
    hoges
FORCE INDEX(index_hoges_on_column_1) -- FROM句の下に記述される。エラー発生しない。
    INNER JOIN
        piyos
    on  hoges.id = piyos.hoge_id
where
    hoges.column_1 = 1
and hoges.column_2 = 2
;

まとめ

Railsにおけるインデックスヒントとヒント句の使い方を説明しました。
ある程度の大きさのサービスになっていくとパフォーマンスチューニングが必要になる場合も増えていくので、そういったときに参考にしてください。

また基礎的なDB/SQLのチューニングについて知りたい方はこちらがおすすめです。
https://downloads.mysql.com/presentations/20151208_02_MySQL_Tuning_for_Beginners.pdf

Discussion