![]() | 1 zjttfs 2020-12-08 11:26:04 +08:00 问题表加统计 回答数, 回答数做索引 如何? |
![]() | 2 justseemore 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 ![]() 你这个需求无法优化数据结构,肯定快不起来的…… |
![]() | 9 justseemore 2020-12-08 11:59:39 +08:00 @aeli 放 redis 做检索分页不好搞吧 |
![]() | 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 u2r1Hqo6HExmNsrt 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 ![]() 说下我几年前的一个需求情况和解决思路,看看能不能有所启发吧 当时公司做一套答题,题库大约 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 上面很多方案都没算过成本。这个需求没有钱还是挺难快起来的。 |
![]() | 41 justseemore 2020-12-09 12:30:10 +08:00 @v2Geeker 我很好奇。问题表冗余一个字段。。有啥成本。。 |
42 v2Geeker 2020-12-09 14:25:01 +08:00 @zpfhbyx 哥哥,你自己先算算嘛。我来跟你先算一个吧,你的方案,50w 的 bitmap,即每条数据会多出 0.0596M 的大小。900w 条数据,一共多出 518.55G 的数据。。。这还没算用户增长和问题增长的情况。 |
![]() | 44 justseemore 2020-12-09 14:38:09 +08:00 @v2Geeker bitmap 转十进制落库啊,这么算的话只会出一个 int 或者 bigint 啊,然后按位操作就行了 |
45 todd7zhang 2020-12-09 14:43:39 +08:00 @zpfhbyx 不对吧,按你 "bitmap 字段>>uid & 0 =0" 的逻辑, 一个 int32 的 bitmap,uid 取值范围[0, 31]啊?用户数几十个才 |
![]() | 46 justseemore 2020-12-09 14:56:49 +08:00 @v2Geeker 老哥,我错了。光想着行了。。没想列。 |
![]() | 47 justseemore 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,放到临时表中,未答条件的直接查库。 |