如何设计数据库表存储用户搜索结果且避免产生过多冗余记录?
方案结论
完全可以实现。核心思路就是拆表做公共数据去重,别给每条搜索结果都存全量关联信息,把日新增原始数据量压到2000条毫无压力,还能全覆盖你要的几个统计维度。
具体表结构设计
1. 搜索行为表(search_record)
每次用户触发搜索只生成1条记录,日均1000次搜索对应日增1000条,字段如下:
search_id:bigint类型,主键,用雪花ID或自增ID均可search_user_id:bigint类型,触发本次搜索的用户IDsearch_condition_hash:char(32)类型,将本次搜索所有影响结果的参数(关键词、筛选条件、排序规则等)按固定规则序列化后计算的MD5值,加唯一索引用于快速判重search_condition_snapshot:json类型,存储本次搜索条件的完整快照,避免哈希碰撞导致无法追溯原始搜索参数result_set_id:bigint类型,关联结果集表的主键,标记本次搜索返回的结果对应哪一个去重后的结果集search_time:datetime类型,搜索触发的精确时间
2. 去重结果集表(unique_result_set)
只有当搜索返回的1000个用户结果和历史已存结果集完全不一致时,才新增1条记录,按普通内容平台的搜索重合度算,日均新增这类不重复结果集不会超过1000条,两张表加总日增稳定在2000条以内,比原来百万级的存储量降了99.8%,字段如下:
result_set_id:bigint类型,主键result_set_hash:char(32)类型,将本次返回的所有用户ID按升序排序后拼接计算MD5值,加唯一索引用于快速判重相同结果集compressed_user_list:blob类型,将1000个用户ID序列化后用zstd等压缩算法压缩存储,单条记录大小仅几KB,存储成本极低first_create_time:datetime类型,该结果集第一次被搜索触发的时间
写库逻辑
每次用户搜完拿到1000条用户结果,按下面的流程操作,完全避免冗余存储:
- 先给搜索参数算哈希,生成搜索行为记录的基础字段
- 把返回的1000个用户ID升序排序后算哈希,查
unique_result_set表是否已存在相同哈希的记录 - 如果存在,直接拿到对应的
result_set_id更新到搜索行为记录,插入搜索行为表即可,不需要新增结果集记录 - 如果不存在,先压缩用户列表插入结果集表拿到新生成的
result_set_id,再关联写入搜索行为记录
统计需求适配
所有要求的统计维度都可以低成本实现,性能完全满足业务要求:
- 统计单个用户被展示次数:每日业务低峰期做离线预聚合,遍历当日新增的结果集,关联对应绑定的所有搜索记录,判断目标用户是否在结果集的用户列表中,聚合后写入统计结果表,查询时直接走聚合表即可,不需要每次扫描原始数据
- 统计展示时间、触发搜索的用户:直接取关联的搜索行为表中对应的
search_time、search_user_id字段即可
注意事项
- 算搜索条件哈希的时候,别漏参数,但凡会影响返回结果的——不管是用户填的关键词、筛选项,还是后端藏的排序规则、灰度分组标识,全要纳入计算范围,不然会把不同搜索的结果串了
- 算结果集哈希之前,一定要把拿到的用户ID按固定顺序排序(比如直接升序),不然同一批用户只是返回顺序不一样,算出来哈希不同,白存重复数据
- 两个哈希字段都要建唯一索引,并发请求同时搜到同一个结果集的时候,靠唯一索引挡住重复插入,不会产生脏数据
内容的提问来源于stack exchange,提问作者lazyCoding
相关产品推荐
相关产品推荐

