MYSQL:数据库设计

文章目录

  • MYSQL:数据库设计
    • [1. 本节简述](#1. 本节简述)
    • [2. 范式](#2. 范式)
      • [2.1 第一范式 (1NF)](#2.1 第一范式 (1NF))
        • [2.1.1 定义](#2.1.1 定义)
        • [2.1.2 示例](#2.1.2 示例)
          • [2.1.2.1 反例](#2.1.2.1 反例)
          • [2.1.2.2 正例](#2.1.2.2 正例)
      • [2.2 第二范式 (2NF)](#2.2 第二范式 (2NF))
        • [2.2.1 定义](#2.2.1 定义)
        • [2.2.2 示例](#2.2.2 示例)
          • [2.2.2.1 反例](#2.2.2.1 反例)
          • [2.2.2.2 不满足第二范式可能带来的问题](#2.2.2.2 不满足第二范式可能带来的问题)
          • [2.2.2.3 正例](#2.2.2.3 正例)
      • [2.3 第三范式 (3NF)](#2.3 第三范式 (3NF))
        • [2.3.1 定义](#2.3.1 定义)
        • [2.3.2 示例](#2.3.2 示例)
          • [2.3.2.1 反例](#2.3.2.1 反例)
          • [2.3.2.2 正例](#2.3.2.2 正例)
    • [3. 设计过程](#3. 设计过程)
    • [4. 实体-关系图](#4. 实体-关系图)
      • [4.1 E-R图的基本组成](#4.1 E-R图的基本组成)
      • [4.2 关系的类型](#4.2 关系的类型)
        • [4.2.1 一对一关系 (1 : 1)](#4.2.1 一对一关系 (1 : 1))
        • [4.2.2 一对多关系 (1 : N)](#4.2.2 一对多关系 (1 : N))
        • [4.2.3 多对多关系 (M : N)](#4.2.3 多对多关系 (M : N))
    • [5. 练习:设计表](#5. 练习:设计表)
      • [5.1 用户与账户的一对一关系](#5.1 用户与账户的一对一关系)
      • [5.2 学生与班级的一对多关系](#5.2 学生与班级的一对多关系)
      • [5.3 学生、课程与成绩的多对多关系](#5.3 学生、课程与成绩的多对多关系)

MYSQL:数据库设计

大家好!在之前的学习中,我们已经掌握了数据库和表的基本操作。然而,仅仅会写SQL是远远不够的,如何设计出结构合理、性能高效的数据库,才是衡量一位开发者能力的关键。这就像盖房子,地基打得好,上层建筑才能稳固。

今天,我们就来深入探讨数据库设计的核心------范式理论,并学习如何将现实世界的需求,一步步转化为具体的数据库表结构。

1. 本节简述

  • 理解数据库设计的灵魂------范式 (Normal Form)
  • 掌握实体之间常见的几种关系
  • 梳理清晰的数据库设计流程
  • 搞懂类、实体、表这三者之间的映射关系。

数据库设计的核心思路

我们可以把数据库设计的核心过程,理解为一个从抽象到具体的过程,通常遵循一个清晰的思路:

  1. 识别实体 :首先,我们需要从业务需求中识别出关键的"角色"或"事物",在面向对象的思想里我们称之为类 (Class)
  2. 映射为表 :接着,这些类会映射成数据库中的实体 (Entity) ,每个实体最终会变成一张具体的数据表 (Table) 。而类所拥有的特征或属性,自然就成了表中的字段 (Column)
  3. 确定关系:理清各个实体之间的相互关系(比如一对一、一对多)。
  4. 物理实现:最后,在明确了所有设计细节后,我们就可以用 SQL 语言来将这些设计稿变为现实了。

2. 范式

谈到数据库设计,有一个概念我们绕不过去,那就是范式 (Normal Form)

我们可以把范式理解为一套设计数据库的"指导原则"或"最佳实践"。遵循这些规范,可以帮助我们设计出结构更合理、数据冗余更少的数据库。关系数据库的范式目前有六种,从低到高分别是:第一范式(1NF)、第二范式(2NF)、第三范式(3NF)、巴斯-科德范式(BCNF)、第四范式(4NF)和第五范式(5NF)。范式的级别越高,对数据关系的约束就越强,数据冗余也越小。

不过,在实际项目中,我们并不需要盲目追求最高的范式。过高的范式虽然能最大程度地减少数据冗余,但有时也会牺牲查询效率,导致数据库I/O操作变得更加频繁。因此,在大多数应用场景下,将数据库设计满足到第三范式(3NF),通常是一个兼顾了规范性和性能的理想选择。

2.1 第一范式 (1NF)

2.1.1 定义
  • 第一范式是所有关系型数据库设计的基础。它要求数据库表的每一列都必须是不可再分的原子数据项。也就是说,字段的值不能是集合、数组或对象这类复杂的数据结构。
  • 任何不满足第一范式的数据库,都不能严格地称之为关系型数据库。
2.1.2 示例
  • 假设我们要设计一个学生表,需要记录学生和其所在学校的信息。
2.1.2.1 反例
  • 下面的设计中,"学校信息"这一列存储的是一个复合对象,它还可以继续拆分为"学校名称"和"学校地址"。这就违反了原子性的要求,因此不满足第一范式
2.1.2.2 正例
  • 我们将学校信息拆分成独立的"学校名称"和"学校地址"列。现在,表中的每一列都是不可再分的原子数据,这就满足了第一范式。

一个简单的判断方法:在关系型数据库中,如果一张表的每一列都可以用基本数据类型(如 INT, VARCHAR, DATE 等)来表示,那么它天然就满足第一范式。

2.2 第二范式 (2NF)

2.2.1 定义

第二范式(2NF)是在满足第一范式的基础上,提出了更高的要求。它的核心思想是:表中的所有非主键字段,都必须完全依赖于整个主键,而不能只依赖于主键的一部分。

这个规则主要是为了解决当表使用复合主键(由多个列共同组成的主键)时可能出现的问题。如果表只有一个单列作为主键,那么它天然就满足第二范式。

2.2.2 示例
  • 需求:我们需要记录学生选修课程的成绩。一个学生可以选修多门课,每门课有固定的学分。
2.2.2.1 反例
  • 为了方便,我们可能会尝试用一张大表来记录所有信息。
  • 在这张表中,我们使用 (学号, 课程名) 作为复合主键,这确实可以唯一地标识一条成绩记录。但问题也随之而来:
    • 姓名年龄性别 这些信息,实际上只跟 学号 有关,和学生选了什么课没关系。
    • 同样,学分 这个信息只跟 课程名 有关,和哪个学生来选这门课也没关系。
  • 这种非主键字段(如 姓名学分)只依赖于复合主键中的一部分 (如 学号课程名)的现象,就叫做部分函数依赖。存在部分函数依赖的表,就不满足第二范式。
2.2.2.2 不满足第二范式可能带来的问题
  1. 数据冗余:最直观的问题。同一个学生的姓名、年龄、性别信息,以及同一门课程的学分,会随着选课记录的增加而大量重复存储。
  2. 更新异常:如果某门课程的学分需要调整(比如MySQL从2分改成3分),我们就必须更新所有包含这门课的记录。一旦操作中出现遗漏或中断,就会导致数据不一致。
  3. 插入异常:这个设计要求一条记录必须包含学生、课程和成绩。那么,如果我们要开设一门新课程,但还没有任何学生选修和考试,这门课程的信息(比如它的学分)就无法单独存入数据库中。
  4. 删除异常:如果某个学生毕业了,我们删除了他所有的选课成绩记录。那么,假如他是最后一个选修某门课程的学生,这门课程本身的信息(课程名和学分)也随之从数据库中消失了。
2.2.2.3 正例
  • 正确的做法是进行"拆表",将不同的实体信息存放在各自的表中。针对这个需求,我们应该设计三张表:学生表课程表成绩表

经过这样的拆分,每张表中的非主键字段都完全依赖于各自的主键,完美地满足了第二范式。

总结一下:第二范式主要解决的是"部分函数依赖"问题,通常在涉及复合主键时需要特别关注。如果一张表的主键只有一个字段,它就天然满足第二范式。

2.3 第三范式 (3NF)

2.3.1 定义

第三范式(3NF)是在第二范式的基础上更进一步。它要求:表中的所有非主键字段,都必须直接依赖于主键,而不能存在传递依赖。

什么是传递依赖 呢?简单来说,就是非主键字段 A 依赖于非主键字段 B,而非主键字段 B 又直接依赖于主键。这种 主键 -> B -> A 的依赖链条,就是传递依赖。

2.3.2 示例
  • 需求:在学生表中,我们还需要记录学生所属学院的相关信息。
2.3.2.1 反例
  • 我们在满足第二范式的学生表基础上,直接添加学院信息。
  • 在这个设计中,Id 是主键。学号姓名 等字段都直接依赖于 Id。但是,学院电话学院地址 这两个字段,它们直接依赖的是 学院,再由 学院 依赖于主键 Id
  • 这就形成了一个传递关系:Id -> 学院 -> (学院电话, 学院地址)。这种现象就是传递依赖,因此这个表设计不满足第三范式。它同样会引发数据冗余、更新异常等问题。
2.3.2.2 正例
  • 解决方案依然是拆表。我们将学院信息独立出来,形成一张学院表,然后在学生表中通过外键进行关联。

拆分之后,每个表中的非主键字段都直接依赖于主键,消除了传递依赖,满足了第三范式。

第三范式通过消除传递依赖,可以进一步解决数据冗余、更新异常、插入异常和删除异常等问题,让表结构更加清晰和健壮。

3. 设计过程

了解了范式理论后,我们来梳理一下一个完整的数据库设计流程是怎样的。通常可以分为三步走:

  1. 概念设计 :从现实世界的业务需求中抽象出概念类(即实体)。这个阶段主要搞清楚系统中有哪些核心对象,以及它们各自有什么属性。
  2. 逻辑设计 :确定实体与实体之间的关系 (一对一、一对多、多对多),并使用 E-R图(实体-关系图)将这些关系可视化地表达出来。这有助于团队成员之间的沟通和理解。
  3. 物理设计:根据设计好的E-R图,编写具体的SQL语句,在数据库中创建表、定义字段和约束,将设计蓝图变为现实。

4. 实体-关系图

实体-关系图(Entity-Relationship Diagram),简称E-R图,是数据库设计阶段非常重要的一个工具。它能帮助我们直观地展示数据模型。

4.1 E-R图的基本组成

E-R图主要由三种基本元素构成:

  • 实体 (Entity) :指数据中的对象,通常是现实世界中的一个"事物",比如用户、学生、班级。在图中用矩形框表示。
  • 属性 (Attribute) :指实体的特征或特性,比如学生的姓名、年龄。在图中用椭圆形圆角矩形表示。
  • 关系 (Relationship) :指实体之间的联系,比如"选修"、"属于"等。在图中用菱形框表示,并用直线连接相关的实体。

4.2 关系的类型

实体之间的关系,主要有以下三种:

4.2.1 一对一关系 (1 : 1)
  • 场景:一个用户实体对应一个账户实体。
  • E-R图示例
    • 用户实体属性:用户昵称、真实姓名、手机号等。
    • 账户实体属性:登录用户名、密码。
  • 实现方式
    1. 合并为一张表:如果两个实体的关系非常紧密,且总是一起被查询,可以考虑合并。
    2. 创建两张表,用外键关联:这是更常见的做法,可以保持实体独立性。外键可以放在任意一方的表中。
4.2.2 一对多关系 (1 : N)
  • 场景:一个班级实体可以包含多个学生实体。反过来看,一个学生实体只属于一个班级实体,这就是多对一关系。
  • E-R图示例
    • 学生实体属性:真实姓名、学号、年龄等。
    • 班级实体属性:班级名、学生人数。
  • 实现方式 :分别为两个实体创建表。关键在于,外键要添加在"多"的那一方 。在这个例子中,我们应该在学生表中添加一个 class_id 字段,用来关联班级表。
4.2.3 多对多关系 (M : N)
  • 场景:一个学生实体可以选修多门课程实体,同时一门课程实体也可以被多个学生选修。
  • E-R图示例
    • 学生实体属性:真实姓名、学号等。
    • 课程实体属性:课程名。
  • 实现方式 :对于多对多关系,我们无法直接在两个实体表之间建立关联。标准做法是引入一个中间表 (也叫关系表或连接表)。
    • 这个中间表至少包含两个外键,分别指向学生表和课程表的主键,从而将一个 M:N 关系拆解为两个 1:N 关系。
    • 如果关系本身也有属性(比如学生选修某门课的"成绩"),这个属性也应该放在中间表中。

5. 练习:设计表

现在,我们根据上面绘制的E-R图,来动手创建具体的数据库表。

5.1 用户与账户的一对一关系

实现思路:对于一对一关系,我们可以在任意一张表中添加一个外键字段,来关联另一张表。

sql 复制代码
-- 方案一:在用户表中添加对账户表的关联
DROP TABLE IF EXISTS users;
CREATE TABLE users (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(20) NOT NULL, 
  nickname VARCHAR(20),
  phone_num VARCHAR(11), 
  email VARCHAR(50),
  gender TINYINT(1),
  -- 外键,用于关联账户表
  account_id BIGINT
);

DROP TABLE IF EXISTS account;
CREATE TABLE account (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  username VARCHAR(20) NOT NULL,
  password VARCHAR(32) NOT NULL
);

-- 方案二:在账户表中添加对用户表的关联 (更常见)
DROP TABLE IF EXISTS users;
CREATE TABLE users (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(20) NOT NULL, 
  nickname VARCHAR(20),
  phone_num VARCHAR(11), 
  email VARCHAR(50),
  gender TINYINT(1)
);

DROP TABLE IF EXISTS account;
CREATE TABLE account (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  username VARCHAR(20) NOT NULL,
  password VARCHAR(32) NOT NULL,
  -- 外键,用于关联用户表
  users_id BIGINT
);

5.2 学生与班级的一对多关系

实现思路:分别创建学生表和班级表,在"多"的一方(学生表)中添加外键,关联"一"的一方(班级表)。

sql 复制代码
-- 班级表 ("一"的一方)
DROP TABLE IF EXISTS class;
CREATE TABLE class (
  class_id BIGINT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(20)
);

-- 学生表 ("多"的一方)
DROP TABLE IF EXISTS student;
CREATE TABLE student (
  student_id BIGINT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(20) NOT NULL, 
  sno VARCHAR(10) NOT NULL,
  age INT DEFAULT 18,
  gender TINYINT(1), 
  enroll_date DATE,
  -- 外键,关联班级表的id
  class_id BIGINT
);

5.3 学生、课程与成绩的多对多关系

实现思路:创建学生表和课程表,并额外创建一个中间表(成绩表)来连接它们,从而将多对多关系分解为两个一对多关系。

sql 复制代码
-- 学生表
DROP TABLE IF EXISTS student;
CREATE TABLE student (
  student_id BIGINT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(20) NOT NULL, 
  sno VARCHAR(10) NOT NULL,
  age INT DEFAULT 18,
  gender TINYINT(1), 
  enroll_date DATE,
  -- 学生与班级是一对多,外键放在学生表
  class_id BIGINT,
  FOREIGN KEY (class_id) REFERENCES class(id)
);

-- 课程表
DROP TABLE IF EXISTS course;
CREATE TABLE course (
  course_id BIGINT PRIMARY KEY AUTO_INCREMENT,
  name VARCHAR(20)
);

-- 成绩表 (中间表)
DROP TABLE IF EXISTS score;
CREATE TABLE score (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  -- 关系自身的属性:分数
  score FLOAT,
  -- 外键,关联学生表,构成了与学生表的"多对一"
  student_id BIGINT,
  -- 外键,关联课程表,构成了与课程表的"多对一"
  course_id BIGINT,
  FOREIGN KEY (student_id) REFERENCES student(id),
  FOREIGN KEY (course_id) REFERENCES course(id)
);
相关推荐
Java初学者小白1 小时前
秋招Day17 - Spring - 事务
java·数据库·spring
NineData1 小时前
NineData新增SQL Server到MySQL复制链路,高效助力异构数据库迁移
数据库·人工智能·mysql
极限实验室2 小时前
TDBC 2025 大会聚焦 AI 与数据库融合,极限科技发布新一代 Coco AI 搜索平台
数据库·人工智能
Neng_Miao3 小时前
MySql主从部署
mysql·adb
AWS官方合作商3 小时前
驾驭云端算力:在AWS上构建高性能计算(HPC)集群的完整解决方案
数据库·云计算·aws
泉城老铁4 小时前
千万级数据MySQL的极致优化
大数据·数据库·mysql
发仔1234 小时前
Neo4j 在 Spring Boot 中的使用详解
数据库·spring boot
GarsonW4 小时前
常用SQL语句
数据库·sql
fengye2071615 小时前
板凳-------Mysql cookbook学习 (十二--------5)
数据库·学习·mysql
I'mSQL5 小时前
C#文件操作(创建、读取、修改)
java·数据库·c#