LoginSignup
0
0

More than 3 years have passed since last update.

[あやふや解消シリーズ]ORMについて[vol.1]

Last updated at Posted at 2020-04-07

TL;DR

  • ORMとは、オブジェクト指向のプログラミング言語で関係データベースを扱えるようにうまく変換してくれるもの。
  • ORMを利用する際には「N+1問題」に注意する必要がある。
  • Railsでは基本的にはORマッパーにAciruveRecordを使用している

3行で分かるこの記事を書いた動機

筆者:「Rails独学で勉強してます!」
????:「ORマッパーは何使ってる?」
筆者:「ミ°(学びの浅はかさを見破られ恥ずかしさのあまり舌をかみ切って死亡)」

ORMってなんの略?

ORMとは、
Object-relational mapping
(日:オブジェクト関係マッピング)
の略称です。

ORMって何?

  • ORMとは、アプリケーションが持つリッチなオブジェクトをリレーショナルデータベース(RDBMS)のテーブルに接続することです。
  • オブジェクト関係マッピングは、オブジェクト指向言語からリレーショナルデータベースにアクセスする技術である。
  • オブジェクト関係マッピングとは、データベースとオブジェクト指向プログラミング言語の間の非互換なデータを変換するプログラミング技法である。

引用:
Railsガイド/ 1.2 O/Rマッピング
ORMは不快なアンチパターン/ ORMの仕組み
Wikipedia/ オブジェクト関係マッピング

つまり、O/Rマッピングとは、オブジェクト指向プログラミング言語におけるオブジェクトとリレーショナルデータベース(RDB)の間でデータ形式の相互変換を行う機能のことを指す。

ORMのメリット/デメリットは?

メリット

SQLを書かなくてもデータベースにアクセスできる

  • 例1 以下のようなデータベースから特定の名前を持つユーザの情報を検索したい場合、

table name -> users

id name sex
1 Alice female
2 Bob man
3 Chris man
//MySQL
SELECT * FROM users WHERE name = 'Alice';

//ORM(例:ActiveRecord)
User.find_by(name: "Alice")

利用するSQLの種類によって生じる微妙な差異を吸収できる

  • 例2 以下のようなデータベースからpointが60以下のユーザの特定のカラムの数値を更新したい場合、

table name -> users

id name point promotion flag
1 Alice 80 1 0
2 Bob 50 1 0
3 Chris 80 1 0
//MySQL
UPDATE users SET promotion = 0, flag = 1 WHERE point <= 60;

//PostgreSQL
UPDATE users SET {promotion,flag} = {0,1} WHERE point <= 60;

//ORM(例:ActiveRecord)
User.where("point <= 60").update(promotion:0, flag:1)

デメリット

N+1問題によるパフォーマンスの悪化

N+1問題とは、一覧を取得するSQLを発行してから、各要素ごとに個別のSQLを発行してしまうこと。
厳密には1+N問題と呼んだほうが、実態をより正確に表現できる

引用:
SlideShare/ O/Rマッパーによるトラブルを未然に防ぐ/ 32

N+1問題の怖いところは、問題を放置していても動きはすることである。
必要以上にSQLクエリを走らせることになるため、データ量が増えるにつれパフォーマンスを低下させてしまう。

  • 具体例
前提条件
UserとPostは1:Nの関係
# app/models/user.rb
class User < ActiveRecord::Base
  has_many :posts
end

# app/models/post.rb
class Post < ActiveRecord::Base
  belongs_to :user
end

コントローラではPostモデルのみ取得
# app/contollers/posts_controller.rb
  def index
    @posts = Post.all  # SELECT "posts".* FROM "posts" が発行される。実際にはViewで発行される
  end
ビューでUserモデルの情報を表示
<h1>Listing posts</h1>

<table>
  <thead>
    <tr>
      <th>Title</th>
      <th>Content</th>
      <th>User</th>
      <th colspan="3"></th>
    </tr>
  </thead>

  <tbody>
    <!-- @posts.eachで SELECT "posts".* FROM "posts" クエリが発行される。 -->
    <% @posts.each do |post| %>
      <tr>
        <td><%= post.title %></td>
        <td><%= post.content %></td>
        <!-- post.user.name で SELECT  "users".* FROM "users"  WHERE "users"."id" = ? LIMIT 1  [["id", user_id]] クエリが発行される。 -->
        <td><%= post.user.name %></td>
        <td><%= link_to 'Show', post %></td>
        <td><%= link_to 'Edit', edit_post_path(post) %></td>
        <td><%= link_to 'Destroy', post, method: :delete, data: { confirm: 'Are you sure?' } %></td>
      </tr>
    <% end %>
  </tbody>
</table>
この時のログ
Processing by PostsController#index as HTML
  Post Load (0.2ms) SELECT "posts".* FROM "posts"
  User Load (0.2ms) SELECT  "users".* FROM "users"  WHERE "users"."id" = ? LIMIT 1  [["id", 1]]
  User Load (0.1ms) SELECT  "users".* FROM "users"  WHERE "users"."id" = ? LIMIT 1  [["id", 2]]
  User Load (0.1ms) SELECT  "users".* FROM "users"  WHERE "users"."id" = ? LIMIT 1  [["id", 3]]
  User Load (0.1ms) SELECT  "users".* FROM "users"  WHERE "users"."id" = ? LIMIT 1  [["id", 4]]
  User Load (0.1ms) SELECT  "users".* FROM "users"  WHERE "users"."id" = ? LIMIT 1  [["id", 5]]
  User Load (0.1ms) SELECT  "users".* FROM "users"  WHERE "users"."id" = ? LIMIT 1  [["id", 6]]
  User Load (0.1ms) SELECT  "users".* FROM "users"  WHERE "users"."id" = ? LIMIT 1  [["id", 7]]
  User Load (0.1ms) SELECT  "users".* FROM "users"  WHERE "users"."id" = ? LIMIT 1  [["id", 8]]
  User Load (0.1ms) SELECT  "users".* FROM "users"  WHERE "users"."id" = ? LIMIT 1  [["id", 9]]
  User Load (0.1ms) SELECT  "users".* FROM "users"  WHERE "users"."id" = ? LIMIT 1  [["id", 10]]
  Rendered posts/index.html.erb within layouts/application (32.9ms)
Completed 200 OK in 147ms (Views: 132.6ms | ActiveRecord: 2.0ms)

対策;N+1問題を検出するgemであるbulletを利用する

Gemfileに追加してbundle install、いくつかの設定を調整したのち、問題のUserモデルの情報を表示するとポップアップで
N+1問題を検出し、解決方法を提示してくれる。
下記のように変更し、再び表示させるとポップアップは表示されないようになる。

変更点
# app/contollers/posts_controller.rb
  def index
    @posts = Post.all.includs(:user)
    # 下記2つのSQLが発行される
    # SELECT "posts".* FROM "posts"
    # SELECT "users".* FROM "users"  WHERE "users"."id" IN (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

    # この変更によってpost.user.nameの際にいちいち
   SELECT  "users".* FROM "users"  WHERE "users"."id" = ? LIMIT 1  [["id", user_id]]
   を発行しなくてすむ
  end

引用
Rails Webook/ N+1問題/Eager Loadingとは
TECHSCORE BLOG/ Railsライブラリ紹介N+1問題を検出するgemであるbulletを利用する


Active Recordについて

Active Recordとは、ORMシステムに記述されている「Active Recordパターン」を実装したもの

Active Recordパターンは、データアクセスのロジックを常にオブジェクトに含めておくことで、そのオブジェクトの利用者にデータベースへの読み書き方法を指示できるといったもの

CoC(設定より規約)

ActiveRecordはこれを採択しているため、Railsで採用されている慣習に従っている限り、設定用のコードを最小限で済ませることが可能になっている。

引用
Railsガイド/ ActiveRecordの基礎

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