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)
);
相关推荐
小陈工1 天前
Python Web开发入门(十七):Vue.js与Python后端集成——让前后端真正“握手言和“
开发语言·前端·javascript·数据库·vue.js·人工智能·python
0xDevNull1 天前
MySQL数据冷热分离详解
后端·mysql
科技小花1 天前
数据治理平台架构演进观察:AI原生设计如何重构企业数据管理范式
数据库·重构·架构·数据治理·ai-native·ai原生
一江寒逸1 天前
零基础从入门到精通MySQL(中篇):进阶篇——吃透多表查询、事务核心与高级特性,搞定复杂业务SQL
数据库·sql·mysql
D4c-lovetrain1 天前
linux个人心得22 (mysql)
数据库·mysql
阿里小阿希1 天前
CentOS7 PostgreSQL 9.2 升级到 15 完整教程
数据库·postgresql
荒川之神1 天前
Oracle 数据仓库雪花模型设计(完整实战方案)
数据库·数据仓库·oracle
做个文艺程序员1 天前
MySQL安全加固十大硬核操作
数据库·mysql·安全
不吃香菜学java1 天前
Redis简单应用
数据库·spring boot·tomcat·maven
一个天蝎座 白勺 程序猿1 天前
Apache IoTDB(15):IoTDB查询写回(INTO子句)深度解析——从语法到实战的ETL全链路指南
数据库·apache·etl·iotdb