手撸社区后端:一套可落地的用户 - 文章 - 互动体系 MySQL 表设计与优化思路

最近在搭 React + Nest.js 的全栈社区项目,地基阶段最头疼的就是数据库表设计 ------ 表拆多了关联复杂,表合少了后期扩展难,索引建错了查询慢,约束少了数据脏。踩了一圈坑之后,整理出一套可直接落地的业务表设计,涵盖用户、文章、点赞、评论、标签、文件等核心模块,连索引、外键、分表思路都给你捋明白。

一、先算总账:后端核心业务一共几张表?

整个社区后端的核心业务围绕「用户生产内容、用户互动内容」展开,一共设计 9 张核心业务表,分别是:

  1. 用户表(user):账号登录核心信息
  2. 头像表(avatar):用户头像元数据,与用户一对一关联
  3. 文章表(post):内容主体表
  4. 点赞表(user_like_post):用户 - 文章点赞关联表
  5. 收藏表(user_collect_post):用户 - 文章收藏关联表
  6. 评论表(comment):文章评论,支持树形回复
  7. 标签表(tag):全局标签字典
  8. 文章标签关联表(post_tag):文章 - 标签多对多关联
  9. 文件表(file):文章附件、图片等资源元数据

二、逐表拆解:建表语句 + 设计思路

2.1 用户表:精简至上,为分布式铺路

用户表是整个系统的核心,设计原则是 只存登录必备的核心字段,不塞冗余信息。表越小,越有利于分布式场景下的快速查询、分库分表。

sql 复制代码
CREATE TABLE `user`(
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `password` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4_unicode_ci

设计要点:

  • id 自增主键,作为全局用户唯一标识
  • name 加唯一索引(UNIQUE KEY),保证用户名不重复,同时支撑「按用户名搜索 / 登录」的高频查询
  • password 只存加密后的密文,绝对不存明文
  • 头像、个性签名、用户资料等扩展字段全部拆分到其他表,避免单表过宽

2.2 头像表:解耦扩展,静态资源独立存储

头像属于用户的扩展属性,且涉及文件资源管理,单独拆表更灵活。图片本身不存数据库,只存元数据,文件放在独立的静态资源服务器 / OSS。

sql 复制代码
CREATE TABLE `avatar` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `mimetype` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `filename` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `size` int(11) NOT NULL,
  `userId` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `userId` (`userId`),
  CONSTRAINT `avatar_ibfk_1` FOREIGN KEY (`userId`) REFERENCES `user` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4_unicode_ci

设计要点:

  • 通过 userId 与用户表一对一关联,加普通索引支撑「根据用户 ID 查头像」的查询
  • 存储文件类型、文件名、大小等元数据,实际文件托管在阿里云 OSS 等静态资源服务,返回 CDN 地址即可
  • 外键约束保证用户删除时,头像数据的一致性

2.3 文章表:内容主体,关联作者

文章是核心内容载体,关联作者用户 ID,大字段内容单独用 longtext 存储。

sql 复制代码
CREATE TABLE `post` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `title` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `content` longtext COLLATE utf8mb4_unicode_ci,
  `userId` int(11) DEFAULT NULL,
  PRIMARY KEY(`id`),
  KEY `userId` (`userId`),
  CONSTRAINT `post_ibfk_1` FOREIGN KEY (`userId`) REFERENCES `user` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8mb4_unicode_ci

设计要点:

  • userId 加普通索引,支撑「查看某个用户的所有文章」的查询
  • 外键关联用户表,用户删除时文章作者置空(DEFAULT NULL),保留内容
  • contentlongtext 适配长文场景

2.4 点赞表:联合主键,避免冗余索引

点赞是典型的「用户 - 文章」多对多关系,用联合主键即可,不需要额外自增 ID,还能天然去重。

go 复制代码
CREATE TABLE `user_like_post` (
  `userId` int(11) NOT NULL,
  `postId` int(11) NOT NULL,
  PRIMARY KEY (`userId`, `postId`),
  KEY `postId` (`postId`),
  CONSTRAINT `user_like_post_ibfk_1` FOREIGN KEY (`userId`) REFERENCES `user` (`id`),
  CONSTRAINT `user_like_post_ibfk_2` FOREIGN KEY (`postId`) REFERENCES `post` (`postId`) 
  ON DELETE CASCADE ON UPDATE CASCADE
)

设计要点:

  • (userId, postId) 作为联合主键,既保证一个用户不能重复点赞同一篇文章,又天然覆盖「查某个用户点赞了哪些文章」的查询(最左前缀原则)
  • 只需要额外建 postId 普通索引,支撑「查某篇文章有多少点赞」
  • 不用单独给 userId 建索引,联合主键已经覆盖,建了就是浪费空间
  • 文章删除时,级联删除对应点赞记录,保证数据一致

2.5 收藏表:同构设计,业务隔离

收藏和点赞业务逻辑类似,结构同构,但必须分表,方便后续业务独立扩展(比如收藏加分组、备注等字段)。

go 复制代码
CREATE TABLE `user_collect_post` (
  `userId` int(11) NOT NULL,
  `postId` int(11) NOT NULL,
  PRIMARY KEY (`userId`, `postId`),
  KEY `postId` (`postId`),
  CONSTRAINT `user_collect_post_ibfk_1` FOREIGN KEY (`userId`) REFERENCES `user` (`id`),
  CONSTRAINT `user_collect_post_ibfk_2` FOREIGN KEY (`postId`) REFERENCES `post` (`id`) 
  ON DELETE CASCADE ON UPDATE CASCADE
)

2.6 评论表:树形结构,自关联设计

评论支持盖楼回复,用 parentId 自关联实现树形结构,顶级评论的 parentId 为 NULL。

less 复制代码
CREATE TABLE `comment` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `content` longtext COLLATE utf8mb4_unicode_ci,
  `postId` int(11) NOT NULL,
  `userId` int(11) NOT NULL,
  `parentId` int(11) DEFAULT NULL,
  PRIMARY KEY(`id`),
  KEY `postId` (`postId`),
  KEY `userId` (`userId`),
  KEY `parentId` (`parentId`),
  CONSTRAINT `comment_ibfk_1` FOREIGN KEY (`userId`) REFERENCES `user` (`id`),
  CONSTRAINT `comment_ibfk_2` FOREIGN KEY (`parentId`) REFERENCES `comment` (`id`) 
  ON DELETE SET NULL ON UPDATE CASCADE,
  CONSTRAINT `comment_ibfk_3` FOREIGN KEY (`postId`) REFERENCES `post` (`id`) 
  ON DELETE CASCADE ON UPDATE CASCADE
)

设计要点:

  • 三个普通索引分别对应三个高频查询场景:查某篇文章的评论、查某个用户的评论、查某条评论的子回复
  • 父评论删除时,子评论的 parentIdNULLON DELETE SET NULL),保留子评论,避免连锁删除
  • 文章删除时,级联删除所有关联评论

2.7 标签体系:多对多关系的标准解法

标签和文章是多对多关系,必须拆成「标签字典表 + 关联表」,避免冗余存储。

标签表(字典)

less 复制代码
CREATE TABLE `tag` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  PRIMARY KEY(`id`),
  UNIQUE KEY `name` (`name`)
)

文章标签关联表

swift 复制代码
CREATE TABLE `post_tag` (
  `postId` int(11) NOT NULL,
  `tagId` int(11) NOT NULL,
  PRIMARY KEY(`postId`, `tagId`),
  KEY `tagId` (`tagId`),
  CONSTRAINT `post_tag_ibfk_1` FOREIGN KEY (`postId`) REFERENCES `post` (`id`) 
  ON DELETE CASCADE ON UPDATE CASCADE,
  CONSTRAINT `post_tag_ibfk_2` FOREIGN KEY (`tagId`) REFERENCES `tag` (`id`) 
  ON DELETE CASCADE ON UPDATE CASCADE
)

设计要点:

  • 标签名加唯一索引,保证全局标签不重复
  • 关联表用联合主键,避免重复打标签;左前缀覆盖「查某篇文章的所有标签」
  • 额外加 tagId 索引,支撑「查某个标签下的所有文章」

2.8 文件表:附件元数据全量存储

文章中的图片、附件等资源,统一在文件表管理元数据,支持关联文章和上传用户。

sql 复制代码
CREATE TABLE `file` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `originalname` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `mimetype` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `filename` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL,
  `size` int(11) NOT NULL,
  `postId` int(11) DEFAULT NULL,
  `userId` int(11) NOT NULL,
  `width` smallint(6) NOT NULL,
  `height` smallint(6) NOT NULL,
  `metadata` json DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `postId` (`postId`),
  KEY `userId` (`userId`),
  CONSTRAINT `file_ibfk_1` FOREIGN KEY (`userId`) REFERENCES `user` (`id`),
  CONSTRAINT `file_ibfk_2` FOREIGN KEY (`postId`) REFERENCES `post` (`id`) 
  ON DELETE SET NULL ON UPDATE CASCADE
)

设计要点:

  • 存原始文件名、存储文件名、类型、大小、图片宽高等完整元数据
  • metadata 用 JSON 类型存扩展信息,灵活扩展不用改表结构
  • 文章删除时,文件记录保留(置空 postId),方便资源复用和管理

三、索引设计:不是越多越好,要按需命中

很多人建索引的误区是 "每个字段都建一个",实际上索引占空间、增删改会变慢,必须按需建。总结四类常用索引:

表格

索引类型 作用 使用场景
主键索引 唯一标识一行数据,查询最快 每张表必须有,一般用自增 id
唯一索引 保证字段值不重复,同时加速查询 用户名、标签名等全局唯一字段
普通索引 加速普通查询 外键字段、高频 where 条件字段
联合主键 / 联合索引 多字段组合加速,同时左前缀覆盖 多对多关联表、多条件组合查询

核心原则:

  1. 高频查询字段才建索引,低频筛选字段不建
  2. 联合索引遵循最左前缀原则,能覆盖就不单独建
  3. 小表(比如几千行的字典表)可以不建索引,全表扫描更快

四、约束设计:数据一致性的最后一道防线

MySQL 的约束是保证数据干净的低成本方案,核心用好三种:

  1. 主键约束:每行唯一标识,非空不重复
  2. 唯一约束:业务字段全局唯一,防止脏数据
  3. 外键约束 :保证关联表数据一致性,配合 ON DELETE / ON UPDATE 策略

级联策略选型建议:

  • 文章删除 → 点赞、评论、标签关联全部删除:用 ON DELETE CASCADE
  • 父评论删除 → 子评论保留,父 ID 置空:用 ON DELETE SET NULL
  • 用户删除 → 文章、评论保留,作者置空:用 ON DELETE SET NULL

五、性能与分布式:从小型到集群的演进思路

用户表的分表思路

用户表是核心瓶颈,当用户量上来后,按 id 范围或哈希分表是常见方案。正因为用户表设计得足够精简,字段少、行长度短,分表后查询性能提升非常明显。

访问链路的分层加速

整套系统部署在集群中时,访问链路是层层递进的:

  1. 浏览器缓存:静态资源、接口数据本地缓存
  2. CDN 节点:头像、图片、JS/CSS 就近访问
  3. Nginx 负载均衡:反向代理,分发到健康的后端服务器
  4. 本地缓存 / Redis:热点数据(文章列表、用户信息)内存缓存
  5. 数据库集群:主从读写分离,逐级递归查找

DNS 解析流程

用户访问 juejin.cn 这类域名时,会经过本地 DNS → 运营商 DNS → 根服务器 → 顶级域服务器,最终返回离你最近的 Nginx 节点 IP,Nginx 再把请求转发到后端真实服务集群。

六、工程化落地:从 SQL 文件到 Docker 部署

项目目录管理

在 Nest.js 项目根目录建 database 文件夹,把 blog.sql 表结构文件放进去,作为数据库设计文档,版本化管理。

Docker 容器化部署

Dockerfile 就是镜像的 "配方文件",里面写好构建步骤,就能一键打出一致的运行环境:

sql 复制代码
# 简单示例
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run build
EXPOSE 3000
CMD ["node", "dist/main.js"]

常用部署命令:

perl 复制代码
# 构建镜像
docker build -t my-blog-backend .

# 登录镜像仓库
docker login

# 推送镜像
docker push my-blog-backend

# 服务器拉取运行
docker pull my-blog-backend
docker run -p 3000:3000 my-blog-backend

Nginx 反向代理

前端跑 80 端口,后端跑 3000 端口,用 Nginx 做反向代理,顺便解决跨域、负载均衡:

bash 复制代码
server {
    listen 80;
    server_name shturl.cc/nr7Qe;

    # 前端静态资源
    location / {
        root /usr/share/nginx/html;
        try_files $uri $uri/ /index.html;
    }

    # 后端接口代理
    location /api/ {
        proxy_pass http://localhost:3000/;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

七、总结

一套好的后端表设计,不是字段越多越全越好,而是:

  • 核心表精简:用户、文章等主表只留核心字段,扩展属性拆表
  • 关联关系清晰:一对多用外键,多对多用中间表,树形结构用自关联
  • 索引按需建设:跟着查询场景走,不盲目建索引
  • 约束保障一致:用好主键、唯一、外键,减少脏数据
  • 预留扩展空间:JSON 字段、分表思路,支撑业务从小到大规模演进

这套表结构可以直接拿去做博客、社区、资讯类项目的后端底座,配合 Nest.js 的模块化开发,很快就能搭出一套可用的全栈系统。

相关推荐
jsl_jsl_jsl1 小时前
LLM工具调用速记
后端
ThinkerQAQ_1 小时前
并发编程(一):先谈硬件——从 count++ 到原子性、可见性与有序性
后端
码农学院1 小时前
零售电商GEO踩坑复盘:把 MySQL 商品库自动映射成 Product Schema 的完整方案
人工智能·mysql·零售·geo
岁月如歌77861 小时前
分布式锁完全指南:从数据库到 Redisson 的演进
java·后端·架构
技术猫1 小时前
记一次因线程池不合理使用导致生产拨测告警
后端
写后端的胖头鱼2 小时前
【高频面试题】spring事物失效场景(带原理 + 代码示例)
java·后端·spring·事务·事物失效
JohnCarter20212 小时前
双卡 Atlas 300I Duo 部署 Qwen3-VL-32B:血泪实录
后端
码外生活2 小时前
🎥 手搓一套直播高并发环境!一个后端小白从 0 到 1 的搭建笔记
后端·spring cloud
Lyra_Infra2 小时前
记一次麒麟 Linux 下达梦数据库 (DM8) 部署与 MySQL 命令行无界面迁移踩坑指南
数据库·后端·dba