1
zjttfs 2020-12-08 11:26:04 +08:00
问题表加统计 回答数, 回答数做索引 如何?
|
2
zpfhbyx 2020-12-08 11:28:50 +08:00
问题表冗余一个字段 用 bitmap 的方式标记回答的用户,查询的时候 字段>>uid & 0 =0(应该是这样) 标记为未回答,后面的就好搞了
|
3
hyd8323268 OP @zjttfs 需求是查询出该顾问未回答过的问题,而不是未被人回答过的问题哦 orz
|
4
xuanbg 2020-12-08 11:44:33 +08:00
|
5
xuanbg 2020-12-08 11:49:21 +08:00
具体到顾问,on 后面加上 and a. adviser_id = 顾问 id 就行
|
6
taogen 2020-12-08 11:49:23 +08:00 via Android
表结构,查询 SQL 和 explain 贴一下。
|
7
xuanbg 2020-12-08 11:50:56 +08:00 1
你这个需求无法优化数据结构,肯定快不起来的……
|
10
treblex 2020-12-08 12:45:07 +08:00
给问题按难度 类型 分级,像多邻国那种形式会不会好一点,
必须查出用户没有答过的问题 感觉不是个强需求,就算随机也不见得用户会发现 |
11
whileFalse 2020-12-08 13:44:32 +08:00
把问题 ID 先缓存到内存里。然后查该用户的所有回答即可。
|
12
opengps 2020-12-08 13:51:59 +08:00 via Android
问题表下增加统计字段,小批量分时段维护
|
13
laminux29 2020-12-08 13:56:52 +08:00
你就缺那点硬盘空间嘛?这题显然是空间换时间的思路啊,给每个用户建个 2 分表,分别存储已回答与未回答的不就完事了。
|
14
Nillouise 2020-12-08 14:01:41 +08:00
每个用户储存回答过的问题,查询的时候在应用里跟全部问题过滤一下即可。
|
15
sunziren 2020-12-08 14:03:52 +08:00
先整一个 2TB 的内存,然后数据库放到内存里
|
16
sunziren 2020-12-08 14:04:50 +08:00
然后双路 CPU+集群,然后咱们再进一步探讨
|
17
I52Hertz 2020-12-08 16:11:20 +08:00
先查出已回答的问题,然后从全集里去掉
|
18
pkupyx 2020-12-08 16:30:29 +08:00
详细描述需求场景吧,都打了什么标签,推荐没做过的的题目应该也不是 50W 道题里面没做过的随机推吧。
|
19
vance123 2020-12-08 17:20:30 +08:00
转化成算法题的话就是:
对于序列 s = 00001010101..., 已知所有`1`的下标, 求第 k 个`0`的下标大小 解法应该是 O(1)吧, 当然问题 ID 要连续 |
20
stevenbipt 2020-12-08 17:39:41 +08:00
sql 的话像 4 楼那样直接一个 left join 就能出来
|
21
BigLion 2020-12-08 18:42:00 +08:00
直接 left join
|
22
CRVV 2020-12-08 20:20:43 +08:00
只有一种确定的顺序还是可以随意指定顺序?
1. 只有一种顺序 如果只有一种顺序,按这个顺序建一个索引,然后 Index Scan 就好了,一个用户回答过的问题通常不会很多吧。 跳转到第 x 页这种需求不可能快,不用考虑了。 如果在这种排序下,每个用户回答过的问题都没有聚集在一起,这个方法应该就够快了。 如果有聚集在一起,用 vance123 的方法 2. 顺序可以任意指定 2.1 给每一种顺序建一个索引,然后回到 1,这个通常不现实 2.2 如果不能给每种顺序建索引,就没有通用的 O(n) 以内的算法了。基本上都要 Sequential Scan 。当然有各种优化的方法,但这种事情依赖于非常具体的需求。 |
23
richard1122 2020-12-08 20:22:57 +08:00
你把表结构、索引和 sql 贴出来,再跑一下 explain 也贴出来。
900w 这个量对这个需求索引设计正常点不会慢的 |
24
debuggerx 2020-12-08 20:46:31 +08:00 8
说下我几年前的一个需求情况和解决思路,看看能不能有所启发吧
当时公司做一套答题,题库大约 5k 条,每轮答题给 10 条数据,同一个用户只要显示过的题目后面就不再出现。 我是先把题库入库,然后写了个算法,核心是利用用户的 uid 作为 seed 丢给 java 的 random 函数,从而给每个用户生成一个独一无二的随机序列,再利用这个随机序列对题库做映射,这样每个用户都能实际确定一个答题顺序,然后每次答题,只用记录答题的轮数,访问答题接口时只要传 uid 和轮数就能先快速在程序中算出需要的题目 id,然后数据库 select in [ids] 就可以了 |
25
ben1024 2020-12-08 20:50:38 +08:00
直接左连接取值查询后,进行子查询限制 limit
|
26
leeg810312 2020-12-08 21:11:26 +08:00 via Android
数据库查询需求中,所有不包含的需求都要转化为包含,性能才能提升
|
27
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 开始找。 |
28
ofooo 2020-12-08 21:42:02 +08:00
把问题表缓存到内存里感觉不错。50 万也不多
|
29
liuzhaowei55 2020-12-08 21:42:21 +08:00
首先,优化需求,去掉跳转指定页的这个需求,然后
1. 回答表排序 2. 拿出 1000 条数据,去答案表里边过滤掉一下,大概率留下的数据就可以返回前端使用了 3. 下次加载带上上次的最后一条有效数据标志作为条件 4. 想了下,这个只适合手机这种上拉刷新的方式加载数据 |
30
CoderGeek 2020-12-08 21:55:57 +08:00
1.将用户回答过的问题 id 存在 redis 中
2.选取需要用户回答的问题,都是为回答过的全部返回 3.有回答过的继续取 当然问题列表也可以做缓存也是存 id |
32
CoderGeek 2020-12-08 21:58:18 +08:00
没仔细看 有的用户回答过 10W ? 不允许重复 那你这个效率不会很快前端可以控制的话 从前端解决
|
33
ElmerZhang 2020-12-08 23:07:23 +08:00
如果我来做的话,这个场景我应该会直接上 ElasticSearch
|
34
szuwl 2020-12-08 23:50:42 +08:00 via iPhone
数据量都上千万了,直接分表就完事了
|
35
optional 2020-12-09 00:16:04 +08:00 via iPhone
用户回答过的相对问题总数基本可以忽略,可以不用考虑索引了,直接走主键索引就好。
|
36
c6h6benzene 2020-12-09 01:25:24 +08:00
直接 left join 的话可能没有办法拿到“没有回答”的问题。可能得先用户 ID 跟问题 ID 乘一下。
|
37
gyf304 2020-12-09 06:05:29 +08:00
可以考虑用一个 materialized view 实现一个 用户->问题 ID 的 adjacency list 。
|
38
justNoBody 2020-12-09 09:52:13 +08:00
@taogen 6# 说的对 说不定光看执行计划就可以优化了😂
|
39
nano91 2020-12-09 10:12:13 +08:00
我还是觉得应该反过来做,既然你要找特定人的未回答问题,那就直接找特定人回答过的问题,然后取反就行了,这一点应该要重新做设计,也就是说得有一个地方记录下来每个人回答了那些问题 person.id => anwser.question_id,因为每个人回答的问题数量相对于问题总数一定是很少的,这个方法相比直接按原始的 人-回答-问题 这样查询要更好
|
40
v2Geeker 2020-12-09 10:47:49 +08:00
上面很多方案都没算过成本。这个需求没有钱还是挺难快起来的。
|
42
v2Geeker 2020-12-09 14:25:01 +08:00
@zpfhbyx 哥哥,你自己先算算嘛。我来跟你先算一个吧,你的方案,50w 的 bitmap,即每条数据会多出 0.0596M 的大小。900w 条数据,一共多出 518.55G 的数据。。。这还没算用户增长和问题增长的情况。
|
45
todd7zhang 2020-12-09 14:43:39 +08:00
@zpfhbyx 不对吧,按你 "bitmap 字段>>uid & 0 =0" 的逻辑, 一个 int32 的 bitmap,uid 取值范围[0, 31]啊?用户数几十个才
|
47
zpfhbyx 2020-12-09 14:57:23 +08:00
@todd7zhang 嗯, 的确是
|
48
wellsc 2020-12-09 15:00:52 +08:00
bitmap 的话要考虑数据一致性问题的,要引入中间件同步数据
|
49
JimmyChange 2020-12-09 16:55:20 +08:00
感觉所有问题 id 放内存,从问题表查出所有已回答问题 id 后求个补集就可以吧
|
50
final7genesis 2020-12-09 17:02:29 +08:00
redis 用户为 key, 问题序号 setbit 存储, 每天重新计算可行不?
|
51
mazhan465 2020-12-09 18:20:30 +08:00
先查该用户回答的问题列表,一般来说一个用户也不会回答几千几万个问题。即便真的有这么多问题,无非也就是将问题表再全表拉下来,减去已回答问题。
这样直接在程序里做减法运算,比 db 做 join 或者 exists 快多了 |
52
keepeye 2020-12-09 18:27:02 +08:00
先查出该用户回答过的问题,然后再 not in 一下?
|
53
lishen226 2020-12-09 18:45:02 +08:00
10W 条查主键应该不慢吧,关键是要多快的速度啊?
|
54
skaly 2020-12-09 18:51:10 +08:00
在问题表里面加一个字段,标识是否有用户回答,这样就行了嘛,不要考虑实时聚合的查,没有意义的
|
55
byou 2020-12-09 19:29:52 +08:00
可以换一种思路,分页显示所有问题,在当前用户回答过的问题前边做个标记,表示此问题已回答。就像 leetcode 题库那样的。
感觉没有必要只显示未回答问题,试试说服产品? |
56
nuk 2020-12-09 20:20:34 +08:00
如果不是取全部数据的话,完全可以每个用户存分页的 index
50w 回答,假如一页 100,那么最多最多就是 5000 个 index 用户每次回答后更新 index,然后在 index 的范围内找没回答的问题 问题就是修改每页数量。。会很麻烦。。 |
57
faceRollingKB 2020-12-09 20:37:05 +08:00
50w 个问题再怎么检索都得每个人匹配 50w 次才能得出结果,量在这里我觉得快不到哪去,不如跑一个定时任务每天刷一遍数据存起来
|
58
faceRollingKB 2020-12-09 20:41:45 +08:00
每天刷有点浪费资源,就谁查给谁刷一遍做缓存吧
|
59
kinXdle 2020-12-09 23:00:06 +08:00 via iPhone
用户表加个标示列,回答了就更新
|
60
hyd8323268 OP @faceRollingKB 最终方案是如此
|
61
hyd8323268 OP @kinXdle 标识列存什么呢,已回答 id ?十几万 id 集?...
|
62
hyd8323268 OP @byou 如果用户回答过多,就会导致翻很多页都是已回答,并且排序是时间,所有页码也不固定,最后 pass 了
|
63
hyd8323268 OP @keepeye 先查后 not in 效率还不如 not in 子查询
|
64
faceRollingKB 2020-12-10 10:04:41 +08:00
@hyd8323268 只能看看业务上能不能做一些优化,比如其他人提到的给问题编组,组与组之间有一定规律,然后只记录用户答题所在的组,业务上就可以不匹配直接得出问题答了没有
|
65
hyd8323268 OP @szuwl 这个好像和分表没有关系吧
|
66
hyd8323268 OP @faceRollingKB 现在的方案是不显示的全部问答,每天凌晨跑脚本,给活跃用户并且回答数超过 5 万级批量生成 1000 个问题 id,放到临时表中,未答条件的直接查库。
|