实体是业务中真实存在的对象,需拆分为符合范式的表;外键必须用InnoDB引擎并严格匹配类型;一对多外键置于“多”方,多对多必用联合主键中间表。

实体就是现实中的业务对象,表是它的结构化快照
“学生”“班级”“订单”这些不是数据库概念,而是你业务里真实存在的东西——它们就是实体。MySQL 里没有 ENTITY 关键字,所谓“实体”最终必须落地为一张张表。一张表对应一个实体集(比如所有学生的集合),一行记录对应一个具体实体(比如张三这个学生),一列字段对应该实体的一个属性(比如 stu_name、age)。
常见误区是把“一份 Excel 表格”直接当实体建表:比如把「学生+班级+任课老师+成绩」全塞进一张 student_all_info 表。这看似方便,实则违反第一范式(字段可再分)、第二范式(部分依赖)、第三范式(传递依赖),后续改字段、查数据、加索引都会踩坑。
- 学生和班级是两个独立实体 → 应拆成
students和classes两张表 - 学生和课程是多对多关系 → 必须引入中间表
student_courses,不能在任一主表里硬加逗号分隔的课程 ID 字符串 - 学生身份证号、家庭住址、紧急联系人等低频访问字段 → 可垂直拆分到
students_profile表,用相同stu_id主键关联,而非堆在主表里拖慢常用查询
外键不是装饰,是关系约束的执行器
外键(FOREIGN KEY)不是为了画 ER 图好看,而是让 MySQL 帮你守住数据一致性底线。它强制子表中每条记录的外键值,必须存在于父表的主键中;删除/更新父表记录时,还能自动级联或置空子表关联项。
但很多人建了外键却没生效——原因通常是:ENGINE=MyISAM 不支持外键(必须用 InnoDB),或建表时漏写 CONSTRAINT 名称导致无法管理,或外键字段类型与父表主键不严格一致(比如一个是 INT,另一个是 SMALLINT)。
CREATE TABLE students (
stu_id INT PRIMARY KEY,
class_id INT NOT NULL,
stu_name VARCHAR(20),
FOREIGN KEY (class_id) REFERENCES classes(class_id)
ON DELETE CASCADE
ON UPDATE CASCADE
) ENGINE=InnoDB;
- 必须显式指定
ENGINE=InnoDB,否则外键语句被静默忽略 -
class_id在子表和父表中类型、符号(SIGNED/UNSIGNED)、长度需完全一致 -
ON DELETE CASCADE要慎用:删一个班级,所有学生记录也被删,业务上可能不合理;更安全的是ON DELETE SET NULL(前提是字段允许NULL)
一对多最常用,但外键必须落在“多”的那张表
班级 → 学生 是典型一对多。设计错误常出现在“把外键加在班级表里”,比如给 classes 表加个 student_ids 字段存 “1,5,8” —— 这彻底放弃关系型数据库能力,变成字符串解析游戏,无法索引、无法 JOIN、无法保证原子性。
正确做法永远只有一条:外键字段放在“多”的一方,即 students 表里加 class_id,并指向 classes.class_id。这样一条学生记录只属于一个班级,而一个班级 ID 可在学生表中出现多次。
- 不要试图在班级表里维护学生列表(反向冗余)
- 如果真需要快速查某班所有学生,建索引:
CREATE INDEX idx_class_id ON students(class_id); - 若业务要求“一个学生可属多个班级”,那就不再是 1:N,而是 M:N,必须上中间表
student_classes(stu_id, class_id)
多对多必须用中间表,且组合主键是默认选择
老师 ↔ 班级、用户 ↔ 权限、商品 ↔ 标签……这类关系绝不能用字段拼接、JSON 字符串或双外键字段硬塞进主表。唯一合规解法是新建一张中间表,每行代表一个有效关联。
中间表通常以两个外键组成联合主键,既避免重复关系(如老师 A 已教班级 B,再插一次会被主键冲突拦截),又天然支持高效双向查询。
CREATE TABLE teacher_classes ( teacher_id INT NOT NULL, class_id INT NOT NULL, PRIMARY KEY (teacher_id, class_id), FOREIGN KEY (teacher_id) REFERENCES teachers(teacher_id) ON DELETE CASCADE, FOREIGN KEY (class_id) REFERENCES classes(class_id) ON DELETE CASCADE ) ENGINE=InnoDB;
- 别给中间表加无意义的自增
id主键——它不表达业务含义,还浪费空间和索引开销 - 两个外键都建议加索引:联合主键已覆盖
(teacher_id, class_id),但若常查“某班级有哪些老师”,需额外建INDEX idx_class_id ON teacher_classes(class_id) - 中间表名推荐用复数 + 下划线命名(如
user_roles),清晰表明其桥梁性质
class_teachers 里,而不是强行塞进任一主表。










