You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Rails+Mongoid环境下获取Top5热门书籍的最优方案咨询

嘿,针对你用 Rails 5.1.1 + Mongoid 6.1.0 的场景,不想在 Book 模型里硬存 likes_count 和 histories_count 却要拿点赞+浏览总和前5的图书,我给你几个靠谱的 MongoDB 查询方案,都是不用冗余存储的思路:

方案一:实时聚合查询(适合小到中等数据量)

这个方案直接利用 MongoDB 的聚合管道,实时计算每本书的点赞数、浏览数,再求和排序。假设你的模型关联是 Like 和 History 都通过 book_id 关联到 Book,代码可以这么写:

top_books = Book.collection.aggregate([
  # 关联点赞记录
  {
    "$lookup" => {
      "from" => "likes",
      "localField" => "_id",
      "foreignField" => "book_id",
      "as" => "likes_data"
    }
  },
  # 关联浏览记录
  {
    "$lookup" => {
      "from" => "histories",
      "localField" => "_id",
      "foreignField" => "book_id",
      "as" => "histories_data"
    }
  },
  # 计算点赞数、浏览数及总和
  {
    "$addFields" => {
      "likes_count" => { "$size" => "$likes_data" },
      "histories_count" => { "$size" => "$histories_data" },
      "total_score" => { "$add" => [ { "$size" => "$likes_data" }, { "$size" => "$histories_data" } ] }
    }
  },
  # 按总和倒序排序
  { "$sort" => { "total_score" => -1 } },
  # 取前5条
  { "$limit" => 5 },
  # 移除不需要的关联字段,精简结果
  { "$project" => { "likes_data" => 0, "histories_data" => 0 } }
]).to_a

优缺点:

  • 优点:数据完全实时,不需要额外维护冗余字段,逻辑清晰。
  • 缺点:如果 Like 或 History 集合数据量极大,$size 会遍历所有关联文档,可能影响查询性能。

方案二:用 MongoDB 视图预统计(适合需要复用统计逻辑的场景)

MongoDB 3.4+ 支持创建视图,你可以提前定义好点赞和浏览数的统计视图,之后查询时直接关联视图即可,避免重复写聚合逻辑。

第一步:创建视图(在 Mongo Shell 执行)

// 创建图书点赞统计视图
db.createView("book_likes_stats", "likes", [
  { "$group": { "_id": "$book_id", "likes_count": { "$sum": 1 } } }
])

// 创建图书浏览统计视图
db.createView("book_histories_stats", "histories", [
  { "$group": { "_id": "$book_id", "histories_count": { "$sum": 1 } } }
])

第二步:在 Rails 中定义视图模型

class BookLikesStat
  include Mongoid::Document
  store_in collection: "book_likes_stats"
  field :likes_count, type: Integer
  belongs_to :book
end

class BookHistoriesStat
  include Mongoid::Document
  store_in collection: "book_histories_stats"
  field :histories_count, type: Integer
  belongs_to :book
end

第三步:查询前5图书

top_books = Book.collection.aggregate([
  {
    "$lookup" => {
      "from" => "book_likes_stats",
      "localField" => "_id",
      "foreignField" => "_id",
      "as" => "likes_stat"
    }
  },
  {
    "$lookup" => {
      "from" => "book_histories_stats",
      "localField" => "_id",
      "foreignField" => "_id",
      "as" => "histories_stat"
    }
  },
  {
    "$addFields" => {
      # 处理没有点赞/浏览记录的情况,默认给0
      "likes_count" => { "$ifNull" => [ { "$arrayElemAt" => [ "$likes_stat.likes_count", 0 ] }, 0 ] },
      "histories_count" => { "$ifNull" => [ { "$arrayElemAt" => [ "$histories_stat.histories_count", 0 ] }, 0 ] },
      "total_score" => { "$add" => [
        { "$ifNull" => [ { "$arrayElemAt" => [ "$likes_stat.likes_count", 0 ] }, 0 ] },
        { "$ifNull" => [ { "$arrayElemAt" => [ "$histories_stat.histories_count", 0 ] }, 0 ] }
      ] }
    }
  },
  { "$sort" => { "total_score" => -1 } },
  { "$limit" => 5 },
  { "$project" => { "likes_stat" => 0, "histories_stat" => 0 } }
]).to_a

优缺点:

  • 优点:统计逻辑复用性高,视图实时计算数据,不需要手动更新。
  • 缺点:视图无法创建索引,如果图书数量极多,查询速度可能不如预缓存方案。

方案三:预缓存统计数据(适合性能优先、实时性要求不高的场景)

如果你的数据量很大,实时聚合性能跟不上,可以用 MongoDB 的 $out(3.4+)或 $merge(4.2+)定期将统计结果写入一个缓存集合,查询时直接从缓存集合取数据,性能会好很多。

第一步:定期执行统计任务(比如用 Rails 定时任务)

# 统计所有图书的点赞、浏览数及总和,写入 book_stats 集合
Book.collection.aggregate([
  {
    "$lookup" => {
      "from" => "likes",
      "localField" => "_id",
      "foreignField" => "book_id",
      "as" => "likes_data"
    }
  },
  {
    "$lookup" => {
      "from" => "histories",
      "localField" => "_id",
      "foreignField" => "book_id",
      "as" => "histories_data"
    }
  },
  {
    "$addFields" => {
      "likes_count" => { "$size" => "$likes_data" },
      "histories_count" => { "$size" => "$histories_data" },
      "total_score" => { "$add" => [ { "$size" => "$likes_data" }, { "$size" => "$histories_data" } ] }
    }
  },
  { "$project" => { "likes_data" => 0, "histories_data" => 0 } },
  # 用 $out 覆盖整个集合,若要增量更新可用 $merge(MongoDB 4.2+)
  { "$out" => "book_stats" }
])

第二步:定义缓存模型

class BookStat
  include Mongoid::Document
  store_in collection: "book_stats"
  field :likes_count, type: Integer
  field :histories_count, type: Integer
  field :total_score, type: Integer
  belongs_to :book
end

第三步:快速查询前5图书

# 直接从缓存集合取排序后的前5,再关联图书信息
top_book_stats = BookStat.collection.find.sort(total_score: -1).limit(5).to_a
# 若需要完整图书信息,可以用聚合关联
top_books = Book.collection.aggregate([
  {
    "$lookup" => {
      "from" => "book_stats",
      "localField" => "_id",
      "foreignField" => "_id",
      "as" => "stat"
    }
  },
  { "$unwind" => "$stat" },
  { "$sort" => { "stat.total_score" => -1 } },
  { "$limit" => 5 }
]).to_a

优缺点:

  • 优点:查询性能极佳,适合大数据量场景。
  • 缺点:数据不是实时的,需要根据业务需求调整定时任务的执行频率(比如每小时、每天跑一次)。

内容的提问来源于stack exchange,提问作者ruby_dev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 09:18:07