LoginSignup
2
0

More than 3 years have passed since last update.

Rails6 のちょい足しな新機能を試す86(optimizer_hints 編)

Posted at

はじめに

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 の機能を確認するためです。

db/migrate/20190915055510_create_users.rb
class CreateUsers < ActiveRecord::Migration[6.0]
  def change
    ...
    add_index :users, :name
  end
end

seed データを作る

あらかじめデータを登録しておくため、seed データを作ります。

db/seeds.rb
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 専用で、汎用性がありません。

app/models/user.rb
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

参考情報

2
0
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
2
0