冒险村14 - counter cache

14 - counter cache

在许多情况下,会需要统计一对多关联的资料数量。举例来说像是 User has_many Post。这时如果要统计该 user 拥有的 post 数量时,若直接使用 User.posts.size 则每次读取页面都会重新再统计一次,影响网站效能,而 ActiveRecord's counter_cache 可以来帮助解决这个问题。


以 User has_many post 的例子来看:

  # app/controllers/users_controller.rb
  class UsersController < ApplicationController
    def index
      @users = User.all
    end
  end
  # app/views/users/index.html.erb
  <% @users.each do |user| %>
    <tr>
      <td><%= user.name %></td>
      <td><%= user.email %></td>
      <td><%= user.tel %></td>
      <td><%= user.posts.size %></td>
      <td><%= link_to 'Show', user %></td>
      <td><%= link_to 'Edit', edit_user_path(user) %></td>
    </tr>
  <% end %>
  # app/models/user.rb
  class User < ApplicationRecord
    has_many :posts
  end

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

从 server console 中可以看到 N+1 问题,会重复的一直去捞资料库。

  SELECT "users".* FROM "users"
  SELECT COUNT(*) FROM "posts" WHERE "posts"."user_id" = $1  [["user_id", 1]]
  SELECT COUNT(*) FROM "posts" WHERE "posts"."user_id" = $1  [["user_id", 2]]
  SELECT COUNT(*) FROM "posts" WHERE "posts"."user_id" = $1  [["user_id", 3]]
  SELECT COUNT(*) FROM "posts" WHERE "posts"."user_id" = $1  [["user_id", 4]]
  SELECT COUNT(*) FROM "posts" WHERE "posts"."user_id" = $1  [["user_id", 5]]

接下来就来新增栏位 field_count(惯例为 xxx_count 结尾) 来透过 counter_cache 来把存数字进资料库,解决 SQL count 查询造成的 N+1 问题,神奇的是 Post 数量有更新时,栏位也会自动增减。

产生栏位

  rails g migration add_posts_count_to_user

migration

  class AddPostsCountToUser < ActiveRecord::Migration[6.1]
    def change
      add_column :users, :posts_count, :integer, default: 0

      User.pluck(:id).each do |id|
        User.reset_counters(id, :posts)
      end
    end
  end

注: reset_countersActiveRecord::CounterCache method

也可以将重新计算的 counter 的程序写成 rake 来执行比放在 migration 内来的适合

rake task

  # lib/migrate/reset_user_posts_count.rb
  namespace :migrate do
    desc 'Reset user posts_count counter cache'
    task reset_user_posts_count: :environment do
      User.pluck(:id).each do |id|
        User.reset_counters(id, :posts)
      end
    end
  end

并记得要跑 bundle exec rails migrate:reset_user_posts_count

model

再来在 Post model 修正加上 counter_cache: true 即可。

  # app/models/post.rb
  class Post < ApplicationRecord
    belongs_to :user, counter_cache: true
  end

这时候就会发现原本的 N+1 问题解决了~user.posts.size 这段就会被自动改成 user.posts_count,数量也都会自动更新!

参考来源

My blog


<<:  Day29 ( 高级 ) 绘制正多角星形 ( 多线版 )

>>:  Day29 - 铁人付外挂部署与发行(二)- 部署正式机

Spring Framework X Kotlin Day 10 Schedule

GitHub Repo https://github.com/b2etw/Spring-Kotlin...

Endpoint

我们用到的 API endpoint 只有一个,就是用来取得港铁机场快綫、东涌綫、屯马綫及将军澳綫最...

【DB】B tree B+ tree

从今天开始不讲 Leetcode 了除非有想到什麽还没点到。 後面要提一下对於其他知识点的准备, 毕...

DAY22 时刻表选取组别功能实现

if event.postback.data[:9] == "Schedule:"...

【Day 16】Function - Practice 2

题目 此题题目指定使用的演算法为 输入输出格式 此题测资会给定机场选址数量airportQ (n)、...