Kiểm tra query n+1 với gem Bullet trong rails
Bài đăng này đã không được cập nhật trong 6 năm
Gem Bullet được thiết kế để giúp bạn tăng hiệu suất của ứng dụng bằng cách giảm số lượng truy vấn nó làm. Nó sẽ xem các truy vấn của bạn trong khi bạn phát triển ứng dụng của bạn và thông báo cho bạn khi nào bạn nên thêm tải mong muốn (N + 1 truy vấn), khi bạn đang sử dụng tải mong muốn mà không cần thiết và khi nào bạn nên sử dụng bộ nhớ cache truy cập.
1. Cài Đặt gem Bullet
Bạn có thể cài đặt nó như là một đá quý:
gem install bullet
Hoặc thêm nó vào một Gemfile (Bundler):
gem 'bullet', group: 'development'
2. Cấu hình
Bullet sẽ không làm bất cứ điều gì trừ khi bạn cấu hình cho nó. Thêm vào
config / environment / development.rb initializer
với đoạn code như sau
config.after_initialize do
Bullet.enable = true
Bullet.alert = true
Bullet.bullet_logger = true
Bullet.console = true
Bullet.rails_logger = true
Bullet.add_footer = true
end
Chú thích:
- Bullet.enable = true: kích hoạt Bullet gem, false thì không làm gì cả
- Bullet.alert = true: Hiện cảnh báo query n+1 trên trình duyệt, còn flase thì ngược lại
- Bullet.bullet_logger = true: Dăng nhập vào tệp nhật ký Bullet (Rails.root / log / bullet.log)
- Bullet.console = true: Cảnh báo đăng nhập vào trình duyệt của bạn console.log (trình duyệt Safari / Webkit hoặc Firefox với Firebug cài đặt)
- Bullet.rails_logger = true: Thêm cảnh báo trực tiếp vào bản ghi Rails
- Bullet.add_footer = true: Thêm chi tiết ở góc dưới cùng bên trái của trang. Nhấp đúp vào footer hoặc sử dụng nút close để ẩn chân footer
3. Ví dụ
Mình có 2 model là user và order được thiết kế như sau
class User < ApplicationRecord
has_many :orders, dependent: :destroy
end
class Order < ApplicationRecord
belongs_to :user
scope :sort_by_id, ->{order :id}
end
Trong controller Order mình muốn lấy ra tất cả order để quản lí
class Admin::OrdersController < ApplicationController
def index
@orders = Order.sort_by_id
end
end
Trong view admin/orders/index.html.erb
<h1><%= t "admin.order.title" %></h1>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th><%= t "admin.order.person" %></th>
<th><%= t "admin.order.phone" %></th>
<th><%= t "admin.order.address" %></th>
<th><%= t "admin.order.totala" %></th>
<th><%= t "admin.order.status" %></th>
<th><%= t "admin.order.create" %></th>
<th><%= t "admin.order.update" %></th>
</tr>
</thead>
<tbody>
<%= render partial: "order", collection: @orders %>
</tbody>
</table>
</div>
Trong view admin/orders/_order.html.erb
<tr>
<td><%= order.user.name %></td>
<td><%= order.phone %></td>
<td><%= order.address %></td>
<td><%= order.total_amount %></td>
<td>
<span class="label label-warning">
<%= order.status %>
</span>
</td>
<td><%= order.created_at.to_date %></td>
<td><%= order.updated_at.to_date %></td>
</tr>
Khi chạy trên trình duyệt thì sẽ hiện ra cảnh báo
user: pho
GET /admin/orders
USE eager loading detected
Order => [:user]
Add to your finder: :includes => [:user]
Call stack
/home/pho/Documents/fdf_35/app/views/admin/orders/_order.html.erb:2:in `_app_views_admin_orders__order_html_erb__3528259263961660782_69985335319940'
/home/pho/Documents/fdf_35/app/views/admin/orders/index.html.erb:19:in `_app_views_admin_orders_index_html_erb___3757855983818732562_69985337821920'
Câu query mà nó truy vấn khi n+1
Started GET "/admin/orders" for 127.0.0.1 at 2018-02-10 20:55:15 +0700
Processing by Admin::OrdersController#index as HTML
User Load (0.2ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 ORDER BY `users`.`id` ASC LIMIT 1
Rendering admin/orders/index.html.erb within layouts/application
Order Load (0.4ms) SELECT `orders`.* FROM `orders` ORDER BY `orders`.`id` ASC
User Load (0.3ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
CACHE User Load (0.0ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1 [["id", 1], ["LIMIT", 1]]
CACHE User Load (0.0ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1 [["id", 1], ["LIMIT", 1]]
CACHE User Load (0.0ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1 [["id", 1], ["LIMIT", 1]]
CACHE User Load (0.0ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1 [["id", 1], ["LIMIT", 1]]
CACHE User Load (0.0ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1 [["id", 1], ["LIMIT", 1]]
CACHE User Load (0.0ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1 [["id", 1], ["LIMIT", 1]]
CACHE User Load (0.0ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1 [["id", 1], ["LIMIT", 1]]
CACHE User Load (0.0ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1 [["id", 1], ["LIMIT", 1]]
CACHE User Load (0.0ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1 [["id", 1], ["LIMIT", 1]]
CACHE User Load (0.0ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1 [["id", 1], ["LIMIT", 1]]
CACHE User Load (0.0ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1 [["id", 1], ["LIMIT", 1]]
CACHE User Load (0.0ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1 [["id", 1], ["LIMIT", 1]]
CACHE User Load (0.0ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1 [["id", 1], ["LIMIT", 1]]
CACHE User Load (0.0ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1 [["id", 1], ["LIMIT", 1]]
Để giái quyết vấn đề này thì ta cần sửa lại như sau
class Admin::OrdersController < ApplicationController
def index
@orders = Order.includes(:user).sort_by_id
end
end
Khi đó câu query sẽ load lại như sau
Started GET "/admin/orders" for 127.0.0.1 at 2018-02-10 21:31:21 +0700
(185.6ms) SET NAMES utf8, @@SESSION.sql_mode = CONCAT(CONCAT(@@sql_mode, ',STRICT_ALL_TABLES'), ',NO_AUTO_VALUE_ON_ZERO'), @@SESSION.sql_auto_is_null = 0, @@SESSION.wait_timeout = 2147483
(24.4ms) SELECT `schema_migrations`.`version` FROM `schema_migrations` ORDER BY `schema_migrations`.`version` ASC
Processing by Admin::OrdersController#index as HTML
User Load (3.8ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 ORDER BY `users`.`id` ASC LIMIT 1
Rendering admin/orders/index.html.erb within layouts/application
Order Load (33.4ms) SELECT `orders`.* FROM `orders` ORDER BY `orders`.`id` ASC
User Load (0.5ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1
Rendered collection of admin/orders/_order.html.erb [42 times] (34.6ms)
Rendered admin/orders/index.html.erb within layouts/application (711.7ms)
Và khi load lại trang thì ta sẽ không thấy thông báo query n+1 nữa.
4. Tổng kết
Gem Bullet rất hiệu quả khi ta muốn kiểm tra xem trang website của mình có bị query n+1 đúng không nào, hi vọng qua bài viết này bạn có thể tự kiếm tra được trang web của mình có bị n+1 hay không và giải quyết nó để cho tốc độ load trang website của mình được nhanh hơn, cảm ơn các bạn đã đoc bài viết của mình
Tài liệu tham khảo: https://github.com/flyerhzm/bullet
All rights reserved