はじめに
Rails 6 に追加された新機能を試す第86段。 今回は、 optimizer_hints
編です。
Rails 6 では、ActiveRecord で optimizer_hints
により、 OPTIMIZER HINTS が使えるようになりました。
Ruby 2.6.4, Rails 6.0.0 で確認しました。
また、データベースは MySQL 8.0.17 を使っています。
$ rails --version
Rails 6.0.0
今回は、 name
の属性を持つUserモデルを作り、scope を使い、 rails console
から試してみます。
プロジェクトを作る
rails new rails_sandbox
cd rails_sandbox
User モデルを作る
name
の属性を持つ User モデルを作ります。
bin/rails g model User name
name
にインデックスを追加する
name
にユニークインデックスをつけます。
optimizer_hints
の機能を確認するためです。
class CreateUsers < ActiveRecord::Migration[6.0]
def change
...
add_index :users, :name
end
end
seed データを作る
あらかじめデータを登録しておくため、seed データを作ります。
now = Time.now
users = 10_000.times.each.map do |i|
{ name: "name#{i}", created_at: now, updated_at: now }
end
User.insert_all(users)
User モデルに scope を定義する
かなり手抜きですが、 optimizer_hints
の動作を確認する目的ですので、そこは割り切ります。
また、ここで定義している no_range_optimizer
は、 MySQL 専用で、汎用性がありません。
class User < ApplicationRecord
scope :name1, -> { where("name like 'name1%'") }
# for MySQL only
scope :no_optimizer, -> { optimizer_hints("NO_RANGE_OPTIMIZATION(#{table_name})") }
end
マイグレーションを行い seed データを登録する
$ bin/rails db:create db:migrate db:seed
rails console で確認する
まずは、 optimizer_hints
を使わない場合です。 explain
メソッドを使って実行計画を確認します。
irb(main):001:0> User.name1.explain
...
User Load (2.4ms) SELECT `users`.* FROM `users` WHERE (name like 'name1%')
=> EXPLAIN for: SELECT `users`.* FROM `users` WHERE (name like 'name1%')
+----+-------------+-------+------------+-------+---------------------+---------------------+---------+------+------+----------+-----------------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+-------+---------------------+---------------------+---------+------+------+----------+-----------------------+
| 1 | SIMPLE | users | NULL | range | index_users_on_name | index_users_on_name | 1023 | NULL | 1111 | 100.0 | Using index condition |
+----+-------------+-------+------------+-------+---------------------+---------------------+---------+------+------+----------+-----------------------+
1 row in set (0.00 sec)
key に index_users_on_name
を用いた範囲検索 (type=range) になることがわかります。
では、今度は、 optimizer_hints
を使ってみます。
irb(main):002:0> User.name1.no_optimizer.explain
User Load (8.4ms) SELECT /*+ NO_RANGE_OPTIMIZATION(users) */ `users`.* FROM `users` WHERE (name like 'name1%')
=> EXPLAIN for: SELECT /*+ NO_RANGE_OPTIMIZATION(users) */ `users`.* FROM `users` WHERE (name like 'name1%')
+----+-------------+-------+------------+------+---------------------+------+---------+------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------------+------+---------------------+------+---------+------+------+----------+-------------+
| 1 | SIMPLE | users | NULL | ALL | index_users_on_name | NULL | NULL | NULL | 9884 | 11.11 | Using where |
+----+-------------+-------+------------+------+---------------------+------+---------+------+------+----------+-------------+
1 row in set (0.00 sec)
key を使わない (key=NULL) フルテーブルスキャン (type=ALL) になっていることがわかります。
(通常、検索処理が遅くなる。)
本来は...
本来は、SQLを高速に実行できるように、 OPTIMIZER HINTS を使うので、今回の使い方は本末転倒な気もしますが、適切な例を思いつけませんでした。一応、実際に OPTIMIZER HINTS が使えることが確認できたので、これで良しとします。
試したソース
試したソースは以下にあります。
https://github.com/suketa/rails_sandbox/tree/try086_optimizer_hint