MySQL复合索引与Druid优化仿小红书源码个人主页查询链路 仿小红书源码中的个人主页通常需要展示头像、昵称、简介、主页封面、作品数量、获赞数、关注数、粉丝数以及图文和短视频组成的双列瀑布流。进入自己的个人中心后查询内容还会扩展到作品管理、收藏管理、浏览记录、关注列表、粉丝列表和账号设置。页面结构看起来并不复杂后台却可能同时访问用户表、内容表、媒体表、点赞表、收藏表、浏览记录表和关注关系表。如果每个区域都单独查询并在循环中继续补充封面、话题和互动状态一次主页请求很容易产生几十条 SQL。更合理的方式是先拆分查询职责再通过复合索引、游标分页和批量组装控制数据库访问最后结合 Druid 观察 SQL 执行时间与连接占用。个人主页请求应拆成固定查询步骤一次主页请求大致包含四类数据用户基础资料 ↓ 作品、获赞、关注和粉丝统计 ↓ 公开作品分页数据 ↓ 当前访问者的关注、点赞和收藏状态用户资料和统计数据只需读取一次作品列表通过索引分页获取封面、话题和互动状态则根据作品 ID 批量补充。宠友社区这种方式虽然不是只执行一条 SQL但查询次数相对固定不会随着作品数量不断增加。相反如果把用户、内容、媒体、话题、点赞和收藏全部写进一条关联 SQL一篇包含多张图片和多个话题的作品可能产生大量重复行还会影响分页与排序。内容表复合索引要贴合查询条件个人主页作品列表通常按照作者、内容状态和发布时间筛选user_id 主页作者 status PUBLISHED publish_time 倒序如果只为这些字段分别建立单列索引MySQL 不一定能同时高效利用。对于固定的高频查询可以建立复合索引CREATE INDEX idx_content_user_status_time ON content ( user_id, status, publish_time DESC, id DESC ); SELECT id, content_type, title, cover_url, cover_width, cover_height, like_count, comment_count, collect_count, publish_time FROM content WHERE user_id :profileUserId AND status PUBLISHED AND ( :cursorTime IS NULL OR publish_time :cursorTime OR ( publish_time :cursorTime AND id :cursorId ) ) ORDER BY publish_time DESC, id DESC LIMIT :pageSize;这是第一处代码示例。user_id和status负责缩小查询范围publish_time和id负责排序与分页。ID 作为第二排序字段可以避免多篇作品发布时间相同时出现顺序不稳定。复合索引并不是字段越多越好。个人主页、内容分类页和话题页的筛选条件不同应分别根据实际查询建立索引而不是让一条索引承担所有列表接口。游标分页比深分页更适合作品列表传统分页通常使用LIMIT offset, pageSize页码增加后数据库需要跳过越来越多的数据。例如查询LIMIT 20000, 20时前面的记录虽然不会返回但仍需要被定位。仿小红书源码中的作品列表、收藏记录、浏览记录和粉丝列表更适合游标分页。客户端第一次请求不传游标接口返回当前批次数据和下一页位置cursorTime 2026-07-18 10:15:30 cursorId 28610后续查询从该记录之后继续读取不再依赖持续增大的页码偏移量。不同列表可以采用不同游标作品列表使用发布时间和内容 ID收藏列表使用收藏时间和记录 ID浏览记录使用最近浏览时间和记录 ID粉丝列表使用关注时间和关系 ID。接口返回结构可以统一为records、nextCursor和hasMore。批量组装解决N1查询作品列表查询到二十条内容后还需要补充媒体封面、话题标签、点赞状态和收藏状态。如果在 Java 循环中逐条查询调用过程可能变成1次作品查询 20次媒体查询 20次话题查询 20次点赞状态查询 20次收藏状态查询这类 N1 查询会快速占用数据库连接。更合适的方式是先收集作品 ID再按照数据类型批量读取。public ListContentCardVO assembleCards( ListContent contents, Long currentUserId) { if (contents null || contents.isEmpty()) { return Collections.emptyList(); } ListLong contentIds contents.stream() .map(Content::getId) .toList(); MapLong, ContentMedia coverMap contentMediaService.queryCoverMap(contentIds); MapLong, ListString topicMap contentTopicService.queryTopicNameMap(contentIds); SetLong likedIds contentLikeService.queryLikedContentIds( currentUserId, contentIds ); SetLong collectedIds contentCollectService.queryCollectedContentIds( currentUserId, contentIds ); return contents.stream().map(content - { ContentCardVO card new ContentCardVO(); card.setContentId(content.getId()); card.setTitle(content.getTitle()); card.setContentType(content.getContentType()); card.setLikeCount(content.getLikeCount()); card.setCommentCount(content.getCommentCount()); card.setCollectCount(content.getCollectCount()); card.setCover(coverMap.get(content.getId())); card.setTopicNames( topicMap.getOrDefault( content.getId(), Collections.emptyList() ) ); card.setLiked(likedIds.contains(content.getId())); card.setCollected( collectedIds.contains(content.getId()) ); return card; }).toList(); }这是第二处代码示例。批量查询并不是把所有数据关联到一条 SQL而是让每类数据只读取一次。无论一页返回十条还是二十条SQL 数量都能保持稳定。批量查询也要控制IN条件长度。列表接口按照正常分页大小处理即可大规模数据修复和定时任务则应分批执行。双列瀑布流需要返回封面宽高个人主页作品通常采用双列瀑布流。图片比例不同卡片高度也不同。如果后台只返回图片地址客户端必须等图片加载完成后才能确定高度容易出现卡片跳动和页面重新排版。媒体表建议保存file_url width height media_type sort_number cover_flag图文内容可以选取第一张图片作为封面短视频则使用独立封面。客户端根据列宽与原始比例提前计算展示高度展示高度 卡片宽度 × 原图高度 ÷ 原图宽度多图内容还可以返回图片数量短视频返回播放时长。完整媒体列表只在内容详情页加载主页瀑布流不需要返回全部图片。用户统计不应每次实时计算个人主页中的作品数、获赞数、关注数和粉丝数如果每次都执行COUNT或SUM会频繁扫描内容表和关系表。可以维护独立的用户统计表字段含义content_count公开作品数received_like_count累计获赞数following_count关注数follower_count粉丝数collect_count收藏数发布、隐藏、点赞、取消点赞、关注和取消关注成功后更新对应统计字段。统计表只负责提高主页查询效率内容表、点赞表和关注表仍然保存真实关系。Quartz 可以定期分批核对统计值修正少量异常。统计更新还需要避免并发覆盖。例如多个点赞请求同时读取旧值再写回可能出现数量丢失。更适合直接执行数据库原子更新UPDATE user_statistics SET received_like_count received_like_count 1 WHERE user_id ?关注与粉丝可以共用一张关系表关注和粉丝只是同一条关系的两个方向。关系表可以保存user_id 发起关注用户 target_id 被关注用户 status create_time id查询关注列表时使用user_id查询粉丝列表时使用target_id并分别建立复合索引(user_id, status, create_time, id) (target_id, status, create_time, id)不需要额外维护一张粉丝表否则关注和取消关注时需要同步两份数据。同一用户不能重复关注同一目标可以增加唯一约束UNIQUE KEY uk_user_target (user_id, target_id)应用层可以提前判断关系是否存在数据库唯一约束则负责处理并发请求下的重复写入。粉丝列表需要展示是否已经回关时可以先查询当前页粉丝 ID再批量判断反向关注关系不能逐条执行查询。收藏与内容状态必须分开判断收藏记录表示用户收藏过某篇作品并不代表作品始终公开。内容可能被作者隐藏、删除或后台下架。收藏关系可以继续保留但列表返回时要检查内容当前状态可以过滤失效记录也可以显示为不可访问状态。收藏列表可以建立(user_id, status, collect_time, id)用户执行收藏时后台还要确认作品仍处于公开状态。已删除或下架内容不能新增收藏关系。点赞、评论和回复也应遵循同样规则不能只根据前端页面是否显示按钮决定能否操作。浏览记录避免重复插入用户可能多次打开同一篇图文或短视频。如果每次访问都新增一行浏览记录表会快速增长个人历史列表也会出现大量重复内容。可以为user_id content_id建立唯一约束重复浏览时更新访问次数和最近浏览时间INSERT INTO content_browse_record ( user_id, content_id, browse_count, last_browse_time ) VALUES ( :userId, :contentId, 1, NOW() ) ON DUPLICATE KEY UPDATE browse_count browse_count 1, last_browse_time NOW();这是第三处代码示例。个人浏览记录按照last_browse_time倒序展示。内容推荐可以读取近期浏览分类和话题但浏览历史仍然作为独立用户数据保存。自己的作品管理与公开主页查询不同访问他人主页时只能查询公开作品user_id 目标用户 status PUBLISHED进入自己的作品管理时才可以读取草稿、审核中、已隐藏或未通过内容。这种数据范围必须放在服务端查询条件中不能只依赖前端隐藏页面。Shiro 可以控制用户是否具有作品管理权限业务层仍要确认作品作者与当前登录用户一致。收藏管理、浏览记录、资料编辑和账号设置也只能操作当前用户的数据。内容搜索不需要复用主页SQL个人主页已经明确知道作者 ID使用 MySQL 复合索引即可。用户输入关键词搜索标题、正文、话题或商品时更适合交给 EasyES。两类查询的职责应保持清晰查询场景处理方式作者公开作品MySQL复合索引个人作品管理MySQL状态索引收藏与浏览记录MySQL关系表关注与粉丝列表MySQL关系索引内容关键词搜索EasyES商品关键词搜索EasyES搜索结果返回内容 ID 后仍然需要检查数据库中的内容状态和访问权限。搜索索引不能代替数据库中的最终业务判断。Redis缓存公共数据不混入个性化状态用户资料、主页统计和公开作品卡片属于公共数据可以使用 Redis 缓存。但以下状态与当前访问者有关是否关注主页作者是否点赞某篇作品是否收藏某篇作品。这些字段不能直接写进公共主页缓存否则不同用户可能读取到错误状态。更适合的组装方式是公共主页缓存 当前用户关系状态 最终返回数据缓存用于降低重复读取不能代替复合索引和批量查询。SQL 本身存在全表扫描或 N1 查询时增加缓存只能暂时掩盖问题。Druid需要同时观察速度和查询数量Druid 可以记录 SQL 执行时间、调用次数、连接等待和活跃连接数量。个人主页链路可以重点关注单条 SQL 平均耗时单次请求执行的 SQL 数量是否出现全表扫描是否使用临时表和文件排序连接是否及时归还事务持续时间相同 SQL 的调用频率。有些接口没有明显慢 SQL但一次请求执行几十条短 SQL总耗时仍然较高。因此排查时应同时确认SQL是否命中索引 SQL数量是否合理 数据库连接是否及时释放连接池最大连接数也不能盲目调大。若接口存在长事务或循环查询扩大连接池只会让数据库同时处理更多低效请求。优化后的个人主页执行轨迹经过索引、分页和数据组装调整后一次主页请求可以按照以下顺序运行读取用户资料 ↓ 读取用户统计表 ↓ 判断当前访问者关注状态 ↓ 通过复合索引查询作品列表 ↓ 批量查询封面与话题 ↓ 批量查询点赞和收藏状态 ↓ 组装双列瀑布流卡片 ↓ 返回下一页游标个人主页、作品管理、收藏列表、浏览记录、关注列表和粉丝列表虽然展示内容不同但数据库层遵循相似原则先确定数据范围再建立匹配查询条件的复合索引先分页获取主数据再批量补充关联信息公共内容可以缓存个性化关系单独查询。MySQL保存作品、用户与互动关系Redis缓存高频公共数据EasyES处理关键词搜索Druid观察 SQL 和连接状态Quartz负责统计校验Shiro控制接口权限与数据归属。职责划分清楚后仿小红书源码中的个人主页查询链路才能在作品、收藏和粉丝数据持续增长时保持稳定。技术实现思路的简要归纳湖南宠友信息技术有限公司是一家专注社区交友类产品、企业即时通信软件开发为企业提供即时通信工具、垂直类内容圈子自主研发的业界知名友猫产品拥有广大的企业用户群体https://chongyou.info/1/product/xhs.html

本月热点