MySQL存储投票点赞数据量大查询慢,如何优化Feed流用户交互状态查询效率
现有查询性能问题根源
你当前的SQL写法是拉取多少条Feed,就会对应执行多少次点赞、投票子查询,相当于N条Feed就要跑2*N次关联查询,数据量上来后耗时必然会线性上涨,再加上不必要的关联表和分组操作,进一步放大了性能开销。
优化方案
1. 先做MySQL层面的低成本优化
不需要立刻换存储,先做以下调整就能获得数倍性能提升:
- 加联合索引覆盖查询:给
core_like加(user_id, question_id)联合索引,core_voting加(user_id, choice_id)联合索引,core_choice加(question_id, id)联合索引,oauth_following加(follower_id, target_id)联合索引,所有子查询直接走索引不需要回表。 - 改掉每行子查询的逻辑:先拉取当前页的Question列表(比如每页20条),拿到所有question_id后,做两次批量查询就能拿到当前用户对这批内容的点赞、投票状态,最后在业务层做数据拼接即可,示例逻辑:
-- 先拉Feed基础数据 SELECT id, user_id, status, total_votes, like_count, comment_count, created_at, slug, flag, spam_flag FROM core_question WHERE user_id NOT IN (4,5,6,7) ORDER BY id DESC LIMIT 0,20; -- 批量查当前用户的点赞状态 SELECT question_id FROM core_like WHERE user_id = 1 AND question_id IN (刚才拿到的20个ID); -- 批量查当前用户的投票状态 SELECT U1.question_id, U0.choice_id FROM core_voting U0 INNER JOIN core_choice U1 ON U0.choice_id = U1.id WHERE U0.user_id =1 AND U1.question_id IN (刚才拿到的20个ID); -- 批量查关注状态同理 - 主查询去掉不必要的join和group by,所有关联状态都后置批量查询,主查询的耗时会降到原来的1/10甚至更低。
2. Redis方案的优化调整
你原本设想的数组存储+遍历查询的效率很低,用户点赞量越大查询越慢,改成以下结构即可:
- 用Redis的Set结构存储每个用户的点赞、投票ID,键名设置为
user:{{用户ID}}:likes、user:{{用户ID}}:votes,查询某个内容是否在用户的交互列表里直接用SISMEMBER命令,时间复杂度为O(1),远高于数组遍历的O(n)。 - 如果用户的交互量特别大,Set占用内存过高,可以改用**Bitmap(位图)**存储:因为question_id是自增整数,每一位对应一个question_id的交互状态,1表示已操作,0表示未操作,百万级ID只需要128KB左右的存储空间,查询用
GETBIT命令也是O(1)耗时,内存占用比Set低一个数量级。 - 缓存更新策略:用户点赞/投票写完MySQL后,异步更新Redis对应结构,缓存可以设置30天过期时间,冷用户数据自动淘汰节省空间,访问时再从数据库回写缓存即可。
主流社交平台的通用实现逻辑
Facebook、Instagram、Twitter这类平台处理Feed交互状态都是分层设计:
- 近期的热门Feed数据和用户交互状态全部存在内存缓存层,优先走缓存查询,只有缓存miss时才回查数据库。
- 用户交互记录做冷热分离,近1-2年的交互数据存在高性能存储,更早的历史数据归档到低成本存储,Feed流基本只会拉取近期内容,几乎不会碰到查归档数据的场景。
- 部分平台会在预聚合用户Feed流的时候,直接把当前用户的交互状态一并计算好,用户拉取Feed时直接返回拼接完状态的结果,不需要接口层再单独查询状态。
内容的提问来源于stack exchange,提问作者user2896120
相关产品推荐
相关产品推荐

