应建分类表categories与文章表posts,posts中用外键category_id(int unsigned not null)关联,加索引;多对多时新建中间表post_categories,禁用字符串存分类名。

文章表和分类表怎么建才不翻车
直接用外键约束,别图省事手写字符串存分类名。MySQL 8.0+ 默认启用严格模式,NO_ZERO_DATE 和 STRICT_TRANS_TABLES 开着时,如果分类字段设成 VARCHAR 又没加约束,后期关联查询容易返回空或隐式转换错误。
推荐结构:
— 文章表 posts:主键 id,外键 category_id(INT UNSIGNED NOT NULL)
— 分类表 categories:主键 id,唯一索引 slug(用于 URL),name(显示用)
-
category_id必须加索引,否则JOIN或WHERE category_id = ?会全表扫描 - 别把分类名直接塞进
posts.category_name字段——改名要批量 UPDATE,迁移成本高,也破坏范式 - 如果分类有层级(比如「前端 > JavaScript > React」),先别急着上闭包表或路径枚举,单级分类够用就别提前过度设计
查某分类下的所有文章,为什么慢得像卡住
常见原因是没在 category_id 上建索引,或者用了 SELECT * 拉回大量冗余字段(比如 content 文本字段),导致磁盘 I/O 和网络传输拖慢整体响应。
- 只查必要字段:
SELECT id, title, created_at FROM posts WHERE category_id = 5 - 确认执行计划:
EXPLAIN SELECT ...看type是否为ref或const,key列是否命中索引 - 如果经常按分类 + 时间排序,考虑联合索引:
ALTER TABLE posts ADD INDEX idx_cat_time (category_id, created_at DESC) - 注意 MySQL 5.7 和 8.0 对
ORDER BY使用索引的差异:8.0 支持多列方向混合(如(a ASC, b DESC)),5.7 要求方向一致
LEFT JOIN 分类名时,NULL 值怎么处理才安全
用 LEFT JOIN 是对的,但很多人忽略分类被误删后文章记录还在的情况,结果 categories.name 是 NULL,前端渲染直接报错或显示空白。
- SQL 层兜底:
COALESCE(c.name, '未分类')替换空值,比应用层判断更稳 - 加外键约束并设
ON DELETE RESTRICT(默认),避免误删分类导致数据不一致 - 如果业务允许“软删除”,分类表加
is_deleted TINYINT DEFAULT 0,JOIN 条件补上AND c.is_deleted = 0 - 别依赖
IFNULL()在 WHERE 里过滤(如WHERE IFNULL(c.name, '') != ''),这会让索引失效
想支持一篇文章属于多个分类,现在改结构还来得及吗
来得及,但不是改原表加字段,而是拆出中间表。硬塞多个 ID 到一个字段(比如 "1,3,7")是典型反模式:无法索引、不能原子更新、FIND_IN_SET 效率极低。
- 新建关联表:
post_categories,字段为post_id和category_id,联合主键 + 双向索引 - 查某文章的所有分类:
SELECT c.name FROM categories c JOIN post_categories pc ON c.id = pc.category_id WHERE pc.post_id = 123 - 查某分类下所有文章(多对多):逻辑不变,只是从
posts.category_id = ?变成JOIN post_categories pc ON p.id = pc.post_id WHERE pc.category_id = ? - 注意事务:新增文章+绑定分类,必须在同一个事务里完成,否则出现“文章写了但没绑上分类”的脏状态
外键约束、索引、NULL 处理、多对多拆表——这些点看着琐碎,但漏掉任意一个,上线后都会变成凌晨三点的告警来源。










