MySQL数据库规范化是指通过一系列既定的规则来优化数据库表结构的过程,目的是让数据存储更合理,减少不必要的数据重复,同时避免因为数据结构不合理导致的各种操作异常。它是关系型数据库设计阶段非常重要的环节,直接影响后续数据存储和查询的效率。

数据库规范化的核心作用
在没有进行规范化设计的数据库中,经常会出现同一个数据在多个地方重复存储的情况,比如用户表中同时存储了用户的所属部门名称和部门地址,每次部门地址变更都需要修改所有属于该部门的用户记录,很容易出现部分记录未修改的数据不一致问题。规范化的主要作用就是解决这类问题,具体包括:
- 减少数据冗余,相同的数据只存储一次,节省存储空间
- 避免数据操作异常,包括插入异常、更新异常、删除异常
- 让表结构更清晰,降低后续维护和数据扩展的难度
- 提升数据一致性,减少因为重复存储导致的数据冲突问题
常见的规范化范式
规范化是通过不同的范式级别来递进实现的,常用的范式包括第一范式、第二范式、第三范式,还有更高级的BCNF范式等,下面分别介绍这几个常用范式的核心要求。
第一范式(1NF)
第一范式是最基础的规范化要求,核心是所有字段的值都必须是原子性的,不可再拆分。比如如果有一个用户地址字段,里面存储了"XX省XX市XX区XX路"这样的完整地址,就不符合第一范式,因为地址还可以拆分成省份、城市、区县、详细地址多个部分。符合第一范式的表结构应该把地址拆分成多个独立的字段。
下面是一个不符合1NF的表结构示例:
-- 不符合1NF的用户表
CREATE TABLE user_1nf_fail (
user_id INT PRIMARY KEY,
user_name VARCHAR(50),
-- 地址字段包含多个信息,可再拆分,不符合1NF
user_address VARCHAR(200)
);
改造后符合1NF的表结构:
-- 符合1NF的用户表
CREATE TABLE user_1nf_ok (
user_id INT PRIMARY KEY,
user_name VARCHAR(50),
province VARCHAR(20),
city VARCHAR(20),
district VARCHAR(20),
detail_address VARCHAR(100)
);
第二范式(2NF)
第二范式建立在第一范式的基础上,核心要求是表中的所有非主键字段都必须完全依赖于整个主键,而不能只依赖主键的一部分。这个要求只针对主键是联合主键的表,如果主键是单个字段,自动满足第二范式。
比如有一个学生选课表,主键是学生ID和课程ID的联合主键,表中还有学生姓名和课程名称字段,其中学生姓名只依赖学生ID,课程名称只依赖课程ID,都不完全依赖整个联合主键,所以不符合第二范式。
不符合2NF的表结构示例:
-- 不符合2NF的学生选课表
CREATE TABLE student_course_2nf_fail (
student_id INT,
course_id INT,
student_name VARCHAR(50),
course_name VARCHAR(50),
score DECIMAL(5,2),
PRIMARY KEY (student_id, course_id)
);
改造后符合2NF的表结构,需要拆分成三个表:
-- 学生表
CREATE TABLE student (
student_id INT PRIMARY KEY,
student_name VARCHAR(50)
);
-- 课程表
CREATE TABLE course (
course_id INT PRIMARY KEY,
course_name VARCHAR(50)
);
-- 学生选课成绩表,只存储关联关系和学生成绩
CREATE TABLE student_course (
student_id INT,
course_id INT,
score DECIMAL(5,2),
PRIMARY KEY (student_id, course_id),
FOREIGN KEY (student_id) REFERENCES student(student_id),
FOREIGN KEY (course_id) REFERENCES course(course_id)
);
第三范式(3NF)
第三范式建立在第二范式的基础上,核心要求是表中的所有非主键字段都只能直接依赖于主键,不能存在传递依赖。也就是不能有一个非主键字段依赖于另一个非主键字段,再间接依赖于主键。
比如用户表中存储了用户ID、用户姓名、部门ID、部门名称字段,其中部门名称依赖于部门ID,部门ID依赖于用户ID,就形成了传递依赖,不符合第三范式。
不符合3NF的表结构示例:
-- 不符合3NF的用户表
CREATE TABLE user_3nf_fail (
user_id INT PRIMARY KEY,
user_name VARCHAR(50),
dept_id INT,
-- 部门名称依赖于dept_id,存在传递依赖,不符合3NF
dept_name VARCHAR(50)
);
改造后符合3NF的表结构,需要把部门信息拆分到独立的部门表中:
-- 部门表
CREATE TABLE department (
dept_id INT PRIMARY KEY,
dept_name VARCHAR(50)
);
-- 用户表,只存储部门ID关联部门表
CREATE TABLE user_3nf_ok (
user_id INT PRIMARY KEY,
user_name VARCHAR(50),
dept_id INT,
FOREIGN KEY (dept_id) REFERENCES department(dept_id)
);
规范化设计的注意事项
虽然规范化能带来很多好处,但也不是范式级别越高越好,需要根据实际业务场景平衡。如果过度规范化,表会被拆分的非常多,查询数据的时候需要关联很多张表,反而会降低查询效率。比如一些对查询性能要求很高的统计分析类场景,适当保留一定的冗余反而能提升查询速度。
一般来说,大部分业务场景做到第三范式就已经足够满足需求,不需要追求更高的范式级别。设计表结构的时候,先按照范式要求做规范化,再根据实际查询需求做适当的反规范化调整,才是最合理的做法。
简单的规范化实践示例
假设我们要设计一个电商系统的订单相关表,初始需求是存储订单信息,包括订单ID、用户姓名、用户手机号、商品名称、商品单价、购买数量、订单总金额。如果不做规范化,可能会设计成一张表:
-- 未规范化的订单表
CREATE TABLE order_no_norm (
order_id INT PRIMARY KEY,
user_name VARCHAR(50),
user_phone VARCHAR(20),
product_name VARCHAR(100),
product_price DECIMAL(10,2),
buy_num INT,
total_amount DECIMAL(10,2)
);
这个表存在很多问题,比如用户姓名和手机号重复存储,商品名称和单价重复存储,不符合第三范式。按照规范化要求改造后,应该拆分成用户表、商品表、订单表、订单详情表四张表:
-- 用户表
CREATE TABLE order_user (
user_id INT PRIMARY KEY,
user_name VARCHAR(50),
user_phone VARCHAR(20)
);
-- 商品表
CREATE TABLE order_product (
product_id INT PRIMARY KEY,
product_name VARCHAR(100),
product_price DECIMAL(10,2)
);
-- 订单表
CREATE TABLE order_info (
order_id INT PRIMARY KEY,
user_id INT,
total_amount DECIMAL(10,2),
order_time DATETIME,
FOREIGN KEY (user_id) REFERENCES order_user(user_id)
);
-- 订单详情表
CREATE TABLE order_detail (
detail_id INT PRIMARY KEY,
order_id INT,
product_id INT,
buy_num INT,
FOREIGN KEY (order_id) REFERENCES order_info(order_id),
FOREIGN KEY (product_id) REFERENCES order_product(product_id)
);
这样改造后,用户信息、商品信息都只存储一次,避免了数据冗余,也避免了后续用户信息或商品信息变更时需要修改大量订单记录的问题,符合第三范式的要求。