分类表要不要用自增主键
要用,但别只用自增 ID 做业务标识。分类的
id作为主键必须是自增整型(
BIGINT UNSIGNED更稳妥),方便关联和索引;但对外展示或 URL 中的分类标识,应该另加一个
slug字段,比如
tech、
life,类型设为
VARCHAR(64)并加唯一索引。
常见错误是直接用
id拼在 URL 里(如
/category/3),后期改名或合并分类时 URL 失效;也有人用中文名做主键或 slug,结果插入失败或排序错乱。
slug必须小写 + 英文字母/数字/短横线,入库前强制转换 避免用
name字段做唯一约束——同名分类(如“随笔”和“随笔杂谈”)可能语义不同 如果支持多语言站点,
slug应按语言拆分(比如加
lang字段),不建议靠翻译表硬关联
文章和分类之间用什么关系模型
博客文章通常属于**一个主分类**,但常需支持**多个标签或二级分类**。所以不要用简单的外键字段
category_id硬绑定到文章表——这会锁死扩展性。
正确做法是建一张中间表
post_categories,结构至少包含:
post_id、
category_id、
is_primary(
TINYINT(1))、
sort_order(
SMALLINT)。这样既能支持单主分类,也能支持多分类归档,还能控制显示顺序。 文章表
posts里删掉任何
category_id字段,避免数据冗余和不一致
post_categories的联合索引要覆盖查询高频路径:比如
(post_id, is_primary)用于查主分类,
(category_id, is_primary)用于查某分类下所有文章 别用 JSON 字段存分类 ID 列表——没法走索引,连
COUNT()都慢
分类层级怎么处理才不踩坑
MySQL 不适合递归查询,所以别用「父 ID」字段(
parent_id)硬搞无限级分类。真需要多级(比如「编程 > 后端 > MySQL」),就用闭包表(Closure Table)或物化路径(Materialized Path)。
更现实的选择是:限制最多两级,并在分类表里加
parent_id+
level字段(
TINYINT,值为 1 或 2),然后靠应用层校验层级关系。既保持简单,又避免
JOIN套娃和查询爆炸。 加触发器或应用逻辑确保
level = 1时
parent_id为
NULL,
level = 2时
parent_id必须指向
level = 1的记录 查某一级分类下的所有二级分类时,用
WHERE parent_id = ? AND level = 2,别写子查询找子孙 如果未来真要三级以上,优先考虑把分类维度拆出去用 Elasticsearch 或专用图数据库,别在 MySQL 里死磕
分类统计数(如“该分类下有 42 篇文章”)怎么维护
别每次查的时候用
COUNT(*)聚合计算——高并发列表页会拖垮性能。应该在
categories表里加一个
post_count字段,由业务逻辑或数据库触发器维护。
推荐用应用层更新:当文章状态变更(发布/撤回/修改分类)时,在事务中同步增减对应分类的
post_count。比触发器更可控,也避免跨表触发的死锁风险。 初始化时用
UPDATE categories c JOIN (SELECT category_id, COUNT(*) cnt FROM post_categories GROUP BY category_id) t ON c.id = t.category_id SET c.post_count = t.cnt一次性补全 注意并发场景:用
UPDATE categories SET post_count = post_count + 1 WHERE id = ?这种原子操作,别先
SELECT再
UPDATE如果分类数量极少( 实际部署时最容易被忽略的是
slug的生成逻辑和
post_categories表的索引覆盖是否匹配真实查询模式——这两处一出问题,URL 404 和分类页加载慢就会同时出现。
