ActiveRecordは便利だけど、どんなSQLが発行されてるかデータベースへの不要なアクセスをしていないかなどを意識してより効率的なクエリを作っていきましょう╭( ・ㅂ・)و ̑̑
selectとpluck
値が欲しいだけならpluck
を使うべきだけど、メソッドも使いたい場合はselect
を使ったほうが良い。
-
select
は選択したフィールドを取り出しモデルのメソッドとリレーションにアクセスできるモデルオブジェクトが返ってくる。 -
pluck
は選択したフィールドを取り出し値の配列を返すのでメモリは少なくなる。
ActiveRecordのクエリメソッドよりRubyメソッドを使う
クエリメソッドの代わりにRubyメソッドを使うことが出来るなら不要なデータベース呼び出しを減らせる。
例えばcount
ではなくlength
を使うようにする。
eager_loadを使ってN+1クエリを防止する
includesやeage_loadを正しく使ってN+1クエリが発行されないように注意する。
詳細は下記リンクの記事を読むと理解が深まる。
# このコードではクエリが11回も発行されてる。
translators = Translator.first(10)
translators.each do |translator|
translator.books.pluck(:title)
end
# `includes(:posts)`を使うことによってクエリを1回にして解決することが出来る。
translators = Translator.first(10).includes(:books)
translators.each do |translator|
translator.books.pluck(:title)
end
find_eachを使う
大量のメモリを消費する場合、パフォーマンスが低下する。
そういうときはfind_each
を使ってレコードを分割取得して処理すると良い。
複数レコードの更新をまとめて行う
同じ値で複数レコードを更新する場合は下のコードで実現できる。
User.where(is_admin: true).update_all(is_admin: false)
異なる値で複数レコードを更新したい場合は単一のクエリでは不可能だしパフォーマンスが低くなる。
だけどトランザクションブロックにラップすると、すべてのレコードが一度に更新されず、同じ量のクエリが実行されてもトランザクションは1回だけのコミットになるためパフォーマンスが向上する。
# ラップされていない
20.times do |i|
Book.update(title: "UPDATE_TITLE#{i}")
end
# トランザクションで更新をラップする
ActiveRecord::Base.transaction do
20.times do |i|
Book.update(title: "UPDATE_TITLE#{i}")
end
end