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
相关产品推荐
相关产品推荐

