Active Recordでのヒント句の書き方

Active Recordでのヒント句の書き方について。

クエリの実行計画の最適化を RDBMSオプティマイザ (プランナ) に任せずに、アプリケーション側で指定するのにヒント句というのがあります。通常 RDBMSオプティマイザに任せたりしていますが、DBA からチューニングのアドバイスがあったりしたときに使ったりできます。

OracleMySQL 5.7.7 以上 (MariaDB 除く) 、PostgreSQL だと pg_hint_plan が使えるものあたりがサポートしているようです。ヒント句は既存のクエリ自体は書き換えることなく、コードコメントで指示を出せるのが売りのようです。

SELECT /*+ MAX_EXECUTION_TIME(50000) NO_INDEX_MERGE(topics) */ `topics`.* FROM `topics`

Active Record で書く際もなるべく既存のクエリ API に手を入れない形で書けると嬉しいところ。

Rails 6.0 以上だと、kamipo さんのパッチによって optimizer_hints メソッドを使った指定ができます。メソッドチェーンで足す形で書けるのが便利。以下 API ドキュメントから抜粋。

Example (for MySQL):

Topic.optimizer_hints("MAX_EXECUTION_TIME(50000)", "NO_INDEX_MERGE(topics)")
#=> SELECT /*+ MAX_EXECUTION_TIME(50000) NO_INDEX_MERGE(topics) */ `topics`.* FROM `topics`

Example (for PostgreSQL with pg_hint_plan):

Topic.optimizer_hints("SeqScan(topics)", "Parallel(topics 8)")
#=> SELECT /*+ SeqScan(topics) Parallel(topics 8) */ "topics".* FROM "topics"

github.com

Rails 5.2 以下であれば、select メソッドを使って指定するといった形式になると思います。 以下は select の振る舞いを利用して、カラムの前にコメントとしてヒントを足す方法ですが、既存の select メソッドの引数に手を加える必要があるのが難点です。

Topic.select("/*+ MAX_EXECUTION_TIME(50000) NO_INDEX_MERGE(topics) */ `topics`.*")

他にも方法はあると思いますが、Rails 6.0 以上でのoptimizer_hints メソッドが使えれば流れるようなインタフェースにできてクールでしょう。

bliki-ja.github.io

2020-04-21 追記

PostgreSQL へのサポートについて kamipo さんからのアドバイスを追記しておきます。