EF Core中高效统计一对多关联数量的最优方案探讨
问题背景
在Entity Framework Core(EF Core)中,我有两个模型:CreatorPost和Like。每个帖子可被无限点赞,但每个点赞仅属于一个帖子,为一对多关联:
public class CreatorPost { ... }
public class Like { ... public CreatorPost CreatorPost { get; set; } }
在展示帖子时,我通过以下方式获取点赞数:
int likes = _context.Likes.Where(l => l.CreatorPost == creatorPost).Count();
由于所有帖子的点赞都存储在同一张表中,随着点赞数量增加,查询点赞数的耗时会越来越长。
尝试的解决方案
我尝试的方案是为CreatorPost新增一个字段,用于存储点赞数:
public class CreatorPost { ... public uint Likes { get; set; } // Added this }
此后,获取帖子点赞数时无需查询所有点赞记录,直接读取该字段即可。同时在添加点赞的LikeCreatorPost()方法中,增加代码使该字段值加1。
目前获取点赞数的逻辑正常,但在LikeCreatorPost()方法中,为防止用户重复点赞,仍需查询所有点赞记录进行校验。
问题解答
1. 新增字段的性能提升幅度
原方案的Count()查询需要扫描Like表中对应目标帖子的所有记录,数据量越大(比如百万级点赞数据),查询耗时会呈线性增长,可能从几毫秒涨到几十甚至上百毫秒。而新增Likes字段后,读取点赞数是直接从CreatorPost表读取单条记录的字段,属于O(1)的常量级操作,耗时可以控制在1毫秒以内,性能提升非常显著,尤其是高频展示帖子的场景。
2. 此举是否值得
完全值得。点赞数是页面展示的高频读取字段,优化这个场景能直接降低数据库的查询负载,提升页面响应速度。虽然需要在点赞/取消点赞时同步更新Likes字段,但这个额外的写操作开销极小,和读操作的性能收益相比可以忽略不计。唯一需要注意的是保证数据一致性,比如要在同一个事务中完成添加/删除Like记录和更新Likes字段的操作,避免出现数据不一致的情况。
3. 更高效的解决方案
- 优化重复点赞校验:给
Like表添加UserId(假设点赞关联用户)和CreatorPostId的联合唯一索引。这样校验用户是否已点赞时,查询会直接走索引查找,而非全表扫描,速度会大幅提升,同时数据库层面也能避免重复数据插入。 - 异步更新点赞数:如果业务允许点赞数存在短暂的延迟,可以使用消息队列异步更新
Likes字段。用户点赞后先写入Like记录,再发送消息到队列,后台服务消费消息批量更新帖子的点赞数,能减少实时写操作的数据库压力,适合高并发场景。 - 缓存层优化:即使有了
Likes字段,也可以将帖子的点赞数缓存到Redis等缓存服务中。展示页面时优先读取缓存,缓存失效后再从数据库读取并更新缓存,进一步降低数据库的查询压力。
内容的提问来源于stack exchange,提问作者Matin

