【问题标题】:Rails - Speeding up my Items#Index action with database queriesRails - 使用数据库查询加速我的 Items#Index 操作
【发布时间】:2017-07-31 18:35:06
【问题描述】:

我注意到,随着我一直在开发我的应用程序并创建大量项目/列出加载时间变得越来越长。我怀疑这是我在控制器中的操作查询数据库的方式。该视图使用 erb 执行 @item.each 并检查 @item.orders.any 是否?并且将只显示没有订单的项目。代码基本上必须遍历数据库中的每个项目来执行此检查,然后呈现结果,我怀疑这将是超过 100 多个项目的噩梦。

有没有更聪明的方法来做到这一点?

这是一个简单的市场应用程序,买家和卖家可以在其中开展业务。卖家列出了一个项目,它与其他项目一起显示在 Items#Index 页面上。买家点击它并点击购买,这会将他们带到一个订单页面,然后他们可以使用 PayPal 结账。收到 PayPal IPN 后,订单将提交到数据库。

代码如下:

加载索引操作时从服务器日志中摘录

Started GET "/" for 73.110.34.200 at 2017-07-31 13:19:38 -0500
Cannot render console from 73.110.34.200! Allowed networks: 127.0.0.1, ::1, 127.0.0.0/127.255.255.255
Processing by ItemsController#index as */*
  Rendering items/index.html.erb within layouts/application
  Item Load (10.3ms)  SELECT "items".* FROM "items" ORDER BY "items"."created_at" DESC, created_at DESC
  Order Exists (1.7ms)  SELECT  1 AS one FROM "orders" WHERE "orders"."item_id" = $1 LIMIT $2  [["item_id", 19], ["LIMIT", 1]]
  Order Exists (3.2ms)  SELECT  1 AS one FROM "orders" WHERE "orders"."item_id" = $1 LIMIT $2  [["item_id", 18], ["LIMIT", 1]]
  Order Exists (0.4ms)  SELECT  1 AS one FROM "orders" WHERE "orders"."item_id" = $1 LIMIT $2  [["item_id", 17], ["LIMIT", 1]]
  Order Exists (0.7ms)  SELECT  1 AS one FROM "orders" WHERE "orders"."item_id" = $1 LIMIT $2  [["item_id", 16], ["LIMIT", 1]]
  Order Exists (0.6ms)  SELECT  1 AS one FROM "orders" WHERE "orders"."item_id" = $1 LIMIT $2  [["item_id", 15], ["LIMIT", 1]]
  Order Exists (0.6ms)  SELECT  1 AS one FROM "orders" WHERE "orders"."item_id" = $1 LIMIT $2  [["item_id", 14], ["LIMIT", 1]]
  Order Exists (0.8ms)  SELECT  1 AS one FROM "orders" WHERE "orders"."item_id" = $1 LIMIT $2  [["item_id", 13], ["LIMIT", 1]]
  Order Exists (0.6ms)  SELECT  1 AS one FROM "orders" WHERE "orders"."item_id" = $1 LIMIT $2  [["item_id", 12], ["LIMIT", 1]]
  Order Exists (0.8ms)  SELECT  1 AS one FROM "orders" WHERE "orders"."item_id" = $1 LIMIT $2  [["item_id", 11], ["LIMIT", 1]]
  Order Exists (0.4ms)  SELECT  1 AS one FROM "orders" WHERE "orders"."item_id" = $1 LIMIT $2  [["item_id", 10], ["LIMIT", 1]]
  Order Exists (0.8ms)  SELECT  1 AS one FROM "orders" WHERE "orders"."item_id" = $1 LIMIT $2  [["item_id", 9], ["LIMIT", 1]]
  Order Exists (3.7ms)  SELECT  1 AS one FROM "orders" WHERE "orders"."item_id" = $1 LIMIT $2  [["item_id", 8], ["LIMIT", 1]]
  Order Exists (0.5ms)  SELECT  1 AS one FROM "orders" WHERE "orders"."item_id" = $1 LIMIT $2  [["item_id", 7], ["LIMIT", 1]]
  Order Exists (0.5ms)  SELECT  1 AS one FROM "orders" WHERE "orders"."item_id" = $1 LIMIT $2  [["item_id", 6], ["LIMIT", 1]]
  Order Exists (0.6ms)  SELECT  1 AS one FROM "orders" WHERE "orders"."item_id" = $1 LIMIT $2  [["item_id", 5], ["LIMIT", 1]]
  Order Exists (0.4ms)  SELECT  1 AS one FROM "orders" WHERE "orders"."item_id" = $1 LIMIT $2  [["item_id", 4], ["LIMIT", 1]]
  Order Exists (0.5ms)  SELECT  1 AS one FROM "orders" WHERE "orders"."item_id" = $1 LIMIT $2  [["item_id", 3], ["LIMIT", 1]]
  Order Exists (0.4ms)  SELECT  1 AS one FROM "orders" WHERE "orders"."item_id" = $1 LIMIT $2  [["item_id", 2], ["LIMIT", 1]]
  Order Exists (0.8ms)  SELECT  1 AS one FROM "orders" WHERE "orders"."item_id" = $1 LIMIT $2  [["item_id", 1], ["LIMIT", 1]]
  Rendered items/index.html.erb within layouts/application (226.2ms)
  Rendered layouts/_rails_default.html.erb (194.0ms)
  Rendered layouts/_shim.html.erb (0.5ms)
  Rendered layouts/_header.html.erb (20.0ms)
  Rendered layouts/_messages.html.erb (1.1ms)
Completed 200 OK in 666ms (Views: 564.1ms | ActiveRecord: 28.3ms)

ItemsController

class ItemsController < ApplicationController
  before_action :set_item, only: [:show, :edit, :update, :destroy]
  before_action :authenticate_user!, except: [:index, :show]
  rescue_from ActiveRecord::RecordNotFound, with: :deny_access
  rescue_from ActionView::MissingTemplate, with: :template_not_found


  def garage
    @items = current_user.items
  end

  def show
    @item = Item.find(params[:id])
  end

  # GET /items
  # GET /items.json
  def index
    @items = Item.all
    if params[:search]
      @items = Item.search(params[:search]).order("created_at DESC")
    else
      @items = Item.all.order("created_at DESC")
    end
  end

  # GET /items/new
  def new
    @item = current_user.items.build
  end

  # GET /items/1/edit
  def edit
  end

  # POST /items
  # POST /items.json
  def create
    @item = current_user.items.build(item_params)
    @item.username = current_user.username

    respond_to do |format|
      if @item.save
        format.html { redirect_to @item, notice: 'Item was successfully created.' }
        format.json { render :show, status: :created, location: @item }
      else
        format.html { render :new }
        format.json { render json: @item.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /items/1
  # PATCH/PUT /items/1.json
  def update
    respond_to do |format|
      if @item.update(item_params)
        format.html { redirect_to @item, notice: 'Item was successfully updated.' }
        format.json { render :show, status: :ok, location: @item }
      else
        format.html { render :edit }
        format.json { render json: @item.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /items/1
  # DELETE /items/1.json
  def destroy
    @item.destroy
    respond_to do |format|
      format.html { redirect_to items_url, notice: 'Item was successfully destroyed.' }
      format.json { head :no_content }
    end
  end

  def favorite
    @item = Item.find(params[:id])
    type = params[:type]
    if type == "favorite"
      current_user.favorites << @item
      redirect_to :back

    elsif type == "unfavorite"
      current_user.favorites.delete(@item)
      redirect_to :back

    else
      redirect_to :back
    end
  end

  def favorites
    @items = current_user.favorites
  end

  def deny_access
    redirect_to :back
  rescue ActionController::RedirectBackError
    redirect_to root_path
  end

  def template_not_found
    redirect_to :back
  rescue ActionView::MissingTemplate
    redirect_to root_path
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_item
      @item = Item.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def item_params
      params.require(:item).permit(:title, :content, :filepicker_url, :price, :shipping_price, :paypal_email)
    end
end

Items#Index 视图

<div class="row">
  <%= form_tag(items_path, :method => "get") do %>
    <div class="col-lg-6 col-lg-offset-3">
      <div class="input-group">
        <%= text_field_tag :search, params[:search], placeholder: "Search items...", class: "form-control" %>
        <div class="input-group-btn">
            <%= submit_tag "Search", :name => nil, class: "btn btn-default btn-block" %>
        </div>
      </div>
    </div>
  <% end %>
</div>
<br>

<% if @items.blank? %>
  <h4>There are no items containing the term <%= params[:search] %>.</h4>
<% end %>

<h1>Items</h1>
      <% @items.each do |item| %>
      <% if item.orders.any? %>
        <% else %>
          <div class="col-md-3">
              <%= link_to(filepicker_image_tag(item.filepicker_url.split(",").last, w: 200, h: 200, fit: 'crop'), item) %><br>
              <p class="bold"><%= item.title %><br></p>
              <p><%= link_to number_to_currency(item.price), item %></p>
              <p class="small"><%= time_ago_in_words(item.created_at) %> ago</p>
          </div> 
        <% end %>
      <% end %>
<br>

感谢您的宝贵时间!

【问题讨论】:

    标签: ruby-on-rails ruby database postgresql model-view-controller


    【解决方案1】:

    尝试使用includes 预先加载关联。

    class ItemsController < ApplicationController
    
      def index
        if params[:search]
          @items = Item.search(params[:search]).includes(:orders).order("created_at DESC")
        else
          @items = Item.all.includes(:orders).order("created_at DESC")
        end
      end
    end
    

    【讨论】:

    • 谢谢!这很好用。服务器日志不再显示每个项目查询和执行订单加载而不是订单存在。平均加载时间减少了约 25 毫秒。
    • @bloodrunner 太棒了!作为表达感谢的方式,如果您对答案感到满意,您可以接受吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-14
    • 1970-01-01
    • 2014-02-17
    • 2011-02-10
    • 1970-01-01
    相关资源
    最近更新 更多