问题表有 50w,回答表 900w,需求是查出指定用户没有回答过的问题,怎么能快一点,有大佬支个招不。

2020-12-08 11:08:59 +08:00
 hyd8323268
现在是子查询,但是如果用户回答超过 10w 并且问题表进行分页的话会更慢。需要考虑到分页和跳转指定页的实现。
7154 次点击
所在节点    MySQL
66 条回复
BigLion
2020-12-08 18:42:00 +08:00
直接 left join
CRVV
2020-12-08 20:20:43 +08:00
只有一种确定的顺序还是可以随意指定顺序?


1. 只有一种顺序
如果只有一种顺序,按这个顺序建一个索引,然后 Index Scan 就好了,一个用户回答过的问题通常不会很多吧。
跳转到第 x 页这种需求不可能快,不用考虑了。

如果在这种排序下,每个用户回答过的问题都没有聚集在一起,这个方法应该就够快了。
如果有聚集在一起,用 vance123 的方法

2. 顺序可以任意指定
2.1 给每一种顺序建一个索引,然后回到 1,这个通常不现实
2.2 如果不能给每种顺序建索引,就没有通用的 O(n) 以内的算法了。基本上都要 Sequential Scan 。当然有各种优化的方法,但这种事情依赖于非常具体的需求。
richard1122
2020-12-08 20:22:57 +08:00
你把表结构、索引和 sql 贴出来,再跑一下 explain 也贴出来。
900w 这个量对这个需求索引设计正常点不会慢的
debuggerx
2020-12-08 20:46:31 +08:00
说下我几年前的一个需求情况和解决思路,看看能不能有所启发吧
当时公司做一套答题,题库大约 5k 条,每轮答题给 10 条数据,同一个用户只要显示过的题目后面就不再出现。
我是先把题库入库,然后写了个算法,核心是利用用户的 uid 作为 seed 丢给 java 的 random 函数,从而给每个用户生成一个独一无二的随机序列,再利用这个随机序列对题库做映射,这样每个用户都能实际确定一个答题顺序,然后每次答题,只用记录答题的轮数,访问答题接口时只要传 uid 和轮数就能先快速在程序中算出需要的题目 id,然后数据库 select in [ids] 就可以了
ben1024
2020-12-08 20:50:38 +08:00
直接左连接取值查询后,进行子查询限制 limit
leeg810312
2020-12-08 21:11:26 +08:00
数据库查询需求中,所有不包含的需求都要转化为包含,性能才能提升
makdon
2020-12-08 21:33:36 +08:00
用布隆过滤器应该可以搞得比较快。
新增一个用户-布隆的 bitmap 表,主键用户 id,另一列一个大 bitmap
然后
select *
from 问题表
where
!( 取布隆 bit 结果(问题 id) & 用户 bitmap)
# 假定 id 连续单调递增
order by 问题 id
offset 页数*页大小

没有具体 benchmark,不过我想这大概能用,线性遍历问题表够了就可以返回的算法

不过其实算一下,9,000,000 条问题,一条算 1KB,内存占用也就 9GB 这个数量级,如果业务允许(例如增删改不不频繁),我会搞两台内存大的服务器,直接在内存里面玩上面的解法;
如果“用户回答超过 10w”指的是一个用户的话,那就改成随机从问题库里面挑然后位与康康有没有回答过,分页按钮改成“换一批问题”(不然每次都要遍历 10w 个问题)
一定要分页的话,可以给用户记录一个“上一次回答的最后一个问题的 id”,下次找的时候从那个 id 开始找。
ofooo
2020-12-08 21:42:02 +08:00
把问题表缓存到内存里感觉不错。50 万也不多
liuzhaowei55
2020-12-08 21:42:21 +08:00
首先,优化需求,去掉跳转指定页的这个需求,然后
1. 回答表排序
2. 拿出 1000 条数据,去答案表里边过滤掉一下,大概率留下的数据就可以返回前端使用了
3. 下次加载带上上次的最后一条有效数据标志作为条件
4. 想了下,这个只适合手机这种上拉刷新的方式加载数据
CoderGeek
2020-12-08 21:55:57 +08:00
1.将用户回答过的问题 id 存在 redis 中
2.选取需要用户回答的问题,都是为回答过的全部返回
3.有回答过的继续取

当然问题列表也可以做缓存也是存 id
CoderGeek
2020-12-08 21:57:01 +08:00
@CoderGeek 未回答过的全部返回
CoderGeek
2020-12-08 21:58:18 +08:00
没仔细看 有的用户回答过 10W ? 不允许重复 那你这个效率不会很快前端可以控制的话 从前端解决
ElmerZhang
2020-12-08 23:07:23 +08:00
如果我来做的话,这个场景我应该会直接上 ElasticSearch
szuwl
2020-12-08 23:50:42 +08:00
数据量都上千万了,直接分表就完事了
optional
2020-12-09 00:16:04 +08:00
用户回答过的相对问题总数基本可以忽略,可以不用考虑索引了,直接走主键索引就好。
c6h6benzene
2020-12-09 01:25:24 +08:00
直接 left join 的话可能没有办法拿到“没有回答”的问题。可能得先用户 ID 跟问题 ID 乘一下。
gyf304
2020-12-09 06:05:29 +08:00
可以考虑用一个 materialized view 实现一个 用户->问题 ID 的 adjacency list 。
justNoBody
2020-12-09 09:52:13 +08:00
@taogen 6# 说的对 说不定光看执行计划就可以优化了😂
nano91
2020-12-09 10:12:13 +08:00
我还是觉得应该反过来做,既然你要找特定人的未回答问题,那就直接找特定人回答过的问题,然后取反就行了,这一点应该要重新做设计,也就是说得有一个地方记录下来每个人回答了那些问题 person.id => anwser.question_id,因为每个人回答的问题数量相对于问题总数一定是很少的,这个方法相比直接按原始的 人-回答-问题 这样查询要更好
v2Geeker
2020-12-09 10:47:49 +08:00
上面很多方案都没算过成本。这个需求没有钱还是挺难快起来的。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/733228

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX