文章目录
-
- 表的约束:保证数据完整性
- 一、前言
- [二、NULL 和 NOT NULL](#二、NULL 和 NOT NULL)
- [三、DEFAULT 默认值](#三、DEFAULT 默认值)
-
- [3.1 默认值的概念](#3.1 默认值的概念)
- [3.2 DEFAULT 示例](#3.2 DEFAULT 示例)
- [3.3 DEFAULT 的应用场景](#3.3 DEFAULT 的应用场景)
- [四、COMMENT 列注释](#四、COMMENT 列注释)
-
- [4.1 COMMENT 的用途](#4.1 COMMENT 的用途)
- [4.2 使用 COMMENT](#4.2 使用 COMMENT)
- [4.3 查看注释](#4.3 查看注释)
- [五、ZEROFILL 零填充](#五、ZEROFILL 零填充)
-
- [5.1 ZEROFILL 的作用](#5.1 ZEROFILL 的作用)
- [5.2 ZEROFILL 示例](#5.2 ZEROFILL 示例)
- [5.3 验证存储值](#5.3 验证存储值)
- [5.4 ZEROFILL 的使用场景](#5.4 ZEROFILL 的使用场景)
- [六、PRIMARY KEY 主键](#六、PRIMARY KEY 主键)
-
- [6.1 主键的定义](#6.1 主键的定义)
- [6.2 创建主键的方式](#6.2 创建主键的方式)
- [6.3 修改主键](#6.3 修改主键)
- [6.4 复合主键](#6.4 复合主键)
- [6.5 主键选择建议](#6.5 主键选择建议)
- [七、AUTO_INCREMENT 自增长](#七、AUTO_INCREMENT 自增长)
-
- [7.1 AUTO_INCREMENT 的概念](#7.1 AUTO_INCREMENT 的概念)
- [7.2 AUTO_INCREMENT 的限制](#7.2 AUTO_INCREMENT 的限制)
- [7.3 AUTO_INCREMENT 示例](#7.3 AUTO_INCREMENT 示例)
- [7.4 获取最后插入的自增值](#7.4 获取最后插入的自增值)
- [7.5 手动设置自增值](#7.5 手动设置自增值)
- [八、UNIQUE KEY 唯一键](#八、UNIQUE KEY 唯一键)
-
- [8.1 唯一键的定义](#8.1 唯一键的定义)
- [8.2 主键 vs 唯一键](#8.2 主键 vs 唯一键)
- [8.3 UNIQUE KEY 示例](#8.3 UNIQUE KEY 示例)
- [8.4 UNIQUE KEY 的使用场景](#8.4 UNIQUE KEY 的使用场景)
- [九、FOREIGN KEY 外键](#九、FOREIGN KEY 外键)
-
- [9.1 外键的定义](#9.1 外键的定义)
- [9.2 外键的语法](#9.2 外键的语法)
- [9.3 外键示例](#9.3 外键示例)
- [9.4 外键的作用](#9.4 外键的作用)
- [9.5 外键的级联操作](#9.5 外键的级联操作)
- 十、综合案例:电商数据库设计
-
- [10.1 需求分析](#10.1 需求分析)
- [10.2 表结构设计](#10.2 表结构设计)
- [10.3 插入测试数据](#10.3 插入测试数据)
- [10.4 关联查询示例](#10.4 关联查询示例)
- 十一、约束的最佳实践
-
- [11.1 设计表时的约束建议](#11.1 设计表时的约束建议)
- [11.2 常见设计错误](#11.2 常见设计错误)
- [11.3 性能考虑](#11.3 性能考虑)
- [11.4 修改约束的常用操作](#11.4 修改约束的常用操作)
- 十二、约束与数据完整性
-
- [12.1 数据完整性的三个层面](#12.1 数据完整性的三个层面)
- [12.2 约束的执行顺序](#12.2 约束的执行顺序)
- 十三、总结与下一步
表的约束:保证数据完整性
一、前言
💬 这一篇讲什么:数据库表的各种约束
🚀 核心内容:
- 如何保证某个字段不为空?
- 主键、唯一键、外键有什么区别?
- 如何建立表与表之间的关系?
在前一篇中,我们学会了选择合适的数据类型。但数据类型的约束很单一,只能限制数据的格式。为了从业务逻辑角度保证数据的正确性,MySQL 提供了多种约束机制。比如,要求某个字段不能为空,或者某个字段必须唯一,或者建立两个表之间的关系。这些都通过约束来实现。
二、NULL 和 NOT NULL
2.1 NULL 的特点
在 MySQL 中,NULL 代表"未知"或"无值",而不是空字符串或 0。
NULL 的运算特性:
sql
SELECT NULL;
输出:
bash
+------+
| NULL |
+------+
| NULL |
+------+
任何数值与 NULL 的运算结果都是 NULL:
sql
SELECT 1 + NULL;
输出:
bash
+----------+
| 1 + NULL |
+----------+
| NULL |
+----------+
这是因为 1 加上"未知",结果仍然是"未知"。
2.2 NOT NULL 约束
在创建表时,默认所有字段都允许为 NULL。使用 NOT NULL 约束字段不能为空。
示例:班级表
sql
CREATE TABLE myclass (
class_name VARCHAR(20) NOT NULL,
class_room VARCHAR(10) NOT NULL
);
查看表结构:
sql
DESC myclass;
输出:
bash
+------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+-------------+------+-----+---------+-------+
| class_name | varchar(20) | NO | | NULL | |
| class_room | varchar(10) | NO | | NULL | |
+------------+-------------+------+-----+---------+-------+
Null 列显示 NO,说明这两个字段都不能为空。
插入数据的验证
正常插入:
sql
INSERT INTO myclass VALUES ('一班', '101教室');
成功。
缺少必需字段:
sql
INSERT INTO myclass (class_name) VALUES ('二班');
输出:
bash
ERROR 1364 (HY000): Field 'class_room' doesn't have a default value
因为 class_room 没有值且不能为 NULL,所以插入失败。
2.3 NOT NULL 的使用建议
- 一般原则 :除非有明确原因允许为空,否则都应该设置
NOT NULL。 - 原因:NULL 值无法参与运算、比较复杂、占用额外存储空间。
- 配合 DEFAULT:如果字段可能为空,通常会设置一个合理的默认值而不是允许 NULL。
三、DEFAULT 默认值
3.1 默认值的概念
如果某个字段经常出现某个具体的值,可以设置默认值。插入数据时如果不给该字段赋值,就使用默认值。
3.2 DEFAULT 示例
sql
CREATE TABLE tt10 (
name VARCHAR(20) NOT NULL,
age TINYINT UNSIGNED DEFAULT 0,
sex CHAR(2) DEFAULT '男'
);
查看表结构:
sql
DESC tt10;
输出:
bash
+-------+---------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------------------+------+-----+---------+-------+
| name | varchar(20) | NO | | NULL | |
| age | tinyint(3) unsigned | YES | | 0 | |
| sex | char(2) | YES | | 男 | |
+-------+---------------------+------+-----+---------+-------+
Default 列显示了默认值。
插入数据,省略有默认值的字段:
sql
INSERT INTO tt10 (name) VALUES ('张三');
SELECT * FROM tt10;
输出:
bash
+-------+-----+-----+
| name | age | sex |
+-------+-----+-----+
| 张三 | 0 | 男 |
+-------+-----+-----+
age 和 sex 字段自动使用了默认值。
插入数据,显式给出值:
sql
INSERT INTO tt10 (name, age, sex) VALUES ('李四', 25, '女');
SELECT * FROM tt10;
输出:
bash
+-------+-----+-----+
| name | age | sex |
+-------+-----+-----+
| 张三 | 0 | 男 |
| 李四 | 25 | 女 |
+-------+-----+-----+
3.3 DEFAULT 的应用场景
| 场景 | 默认值 | 说明 |
|---|---|---|
| 状态字段 | 1 或 0 | 1=启用,0=禁用 |
| 数值字段 | 0 | 不影响计算 |
| 字符字段 | 空字符串 | 避免 NULL |
| 日期字段 | CURRENT_TIMESTAMP | 记录当前时间 |
| 性别字段 | 未知 | 避免假设 |
四、COMMENT 列注释
4.1 COMMENT 的用途
COMMENT 是对字段的文字说明,没有实际的数据约束功能,只是用来文档化表的结构。注释会保存在表的创建语句中,供程序员或 DBA 理解。
4.2 使用 COMMENT
sql
CREATE TABLE tt12 (
name VARCHAR(20) NOT NULL COMMENT '姓名',
age TINYINT UNSIGNED DEFAULT 0 COMMENT '年龄',
sex CHAR(2) DEFAULT '男' COMMENT '性别'
);
4.3 查看注释
DESC 命令看不到注释信息:
sql
DESC tt12;
输出:
bash
+-------+---------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------------------+------+-----+---------+-------+
| name | varchar(20) | NO | | NULL | |
| age | tinyint(3) unsigned | YES | | 0 | |
| sex | char(2) | YES | | 男 | |
+-------+---------------------+------+-----+---------+-------+
要查看注释,使用 SHOW CREATE TABLE:
sql
SHOW CREATE TABLE tt12\G
输出:
bash
*************************** 1. row ***************************
Table: tt12
Create Table: CREATE TABLE `tt12` (
`name` varchar(20) NOT NULL COMMENT '姓名',
`age` tinyint(3) unsigned DEFAULT '0' COMMENT '年龄',
`sex` char(2) DEFAULT '男' COMMENT '性别'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
1 row in set (0.00 sec)
五、ZEROFILL 零填充
5.1 ZEROFILL 的作用
ZEROFILL 是整数类型的属性。当字段值的位数小于指定的宽度时,自动用 0 补齐。
重要:这只影响显示格式,实际存储的值不变。
5.2 ZEROFILL 示例
创建无 ZEROFILL 的表:
sql
CREATE TABLE tt3 (
a INT(10) UNSIGNED,
b INT(10) UNSIGNED
);
INSERT INTO tt3 VALUES (1, 2);
SELECT * FROM tt3;
输出:
bash
+------+------+
| a | b |
+------+------+
| 1 | 2 |
+------+------+
修改列添加 ZEROFILL:
sql
ALTER TABLE tt3 CHANGE a a INT(5) UNSIGNED ZEROFILL;
SELECT * FROM tt3;
输出:
bash
+-------+------+
| a | b |
+-------+------+
| 00001 | 2 |
+-------+------+
a 的值显示为 00001,因为 1 的位数小于 5,被 0 填充了。
5.3 验证存储值
虽然显示为 00001,但实际存储的值仍然是 1。用 HEX() 函数验证:
sql
SELECT a, HEX(a) FROM tt3;
输出:
bash
+-------+--------+
| a | HEX(a) |
+-------+--------+
| 00001 | 1 |
+-------+--------+
HEX(a) 返回 1,说明内部存储的确实是 1,00001 只是格式化显示。
5.4 ZEROFILL 的使用场景
- 订单号、单据号 :
INT(6) ZEROFILL显示为000001、000002等。 - 编码字段:确保编码位数一致。
- 很少使用:现代应用通常在应用层处理格式化,不在数据库层。
六、PRIMARY KEY 主键
6.1 主键的定义
主键用来唯一标识表中的每一行数据。
主键的特点:
- 唯一性:主键列中的每个值都必须唯一,不能重复。
- 非空性:主键列不能为 NULL。
- 单一性:一个表最多只能有一个主键。
- 数据类型:主键列通常是整数类型(INT、BIGINT)。
6.2 创建主键的方式
方式一:在字段定义时指定
sql
CREATE TABLE tt13 (
id INT UNSIGNED PRIMARY KEY COMMENT '学号不能重复',
name VARCHAR(20) NOT NULL
);
查看表结构:
sql
DESC tt13;
输出:
bash
+-------+------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+------------------+------+-----+---------+-------+
| id | int(10) unsigned | NO | PRI | NULL | |
| name | varchar(20) | NO | | NULL | |
+-------+------------------+------+-----+---------+-------+
Key 列显示 PRI(Primary),说明 id 是主键。
主键约束的验证
插入正常数据:
sql
INSERT INTO tt13 VALUES (1, '张三');
成功。
尝试插入重复的主键:
sql
INSERT INTO tt13 VALUES (1, '李四');
输出:
bash
ERROR 1062 (23000): Duplicate entry '1' for key 'PRIMARY'
主键重复,插入失败。
6.3 修改主键
添加主键
如果表创建时没有主键,之后可以添加:
sql
ALTER TABLE table_name ADD PRIMARY KEY (column_name);
删除主键
sql
ALTER TABLE tt13 DROP PRIMARY KEY;
验证:
sql
DESC tt13;
输出中 Key 列变为空,说明主键被删除。
6.4 复合主键
有时一个字段不足以唯一标识行,需要多个字段的组合作为主键。这称为复合主键。
复合主键示例
假设有学生选课系统,需要记录哪个学生选了哪门课程及成绩。同一个学生可以选多门课,同一门课可以被多个学生选,但一个学生选一门课只有一条记录。此时 (学生ID, 课程ID) 的组合应该是唯一的。
sql
CREATE TABLE tt14 (
id INT UNSIGNED,
course CHAR(10) COMMENT '课程代码',
score TINYINT UNSIGNED DEFAULT 60 COMMENT '成绩',
PRIMARY KEY (id, course) -- 复合主键
);
查看表结构:
sql
DESC tt14;
输出:
bash
+--------+---------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+---------------------+------+-----+---------+-------+
| id | int(10) unsigned | NO | PRI | 0 | |
| course | char(10) | NO | PRI | | |
| score | tinyint(3) unsigned | YES | | 60 | |
+--------+---------------------+------+-----+---------+-------+
id 和 course 两列的 Key 都显示 PRI,说明它们组成复合主键。
插入数据:
sql
-- 第一个学生选第一门课
INSERT INTO tt14 (id, course) VALUES (1, '123');
-- 同一学生选不同的课,可以
INSERT INTO tt14 (id, course) VALUES (1, '456');
-- 不同学生选同一门课,可以
INSERT INTO tt14 (id, course) VALUES (2, '123');
-- 同一学生选同一门课,重复,不可以
INSERT INTO tt14 (id, course) VALUES (1, '123');
输出:
bash
ERROR 1062 (23000): Duplicate entry '1-123' for key 'PRIMARY'
6.5 主键选择建议
- 使用自增整数作为主键:而不是业务字段(如身份证号)。这样当业务调整时,主键不需要改变。
- 避免过长的主键:主键会被建立索引,过长会影响性能。
- 避免复合主键:除非确实需要。单一整数主键更简单高效。
七、AUTO_INCREMENT 自增长
7.1 AUTO_INCREMENT 的概念
AUTO_INCREMENT 属性让字段自动递增。当插入数据时不给该字段赋值,系统会自动从当前字段的最大值加 1。
7.2 AUTO_INCREMENT 的限制
- 必须是索引:字段必须是主键或唯一键。
- 必须是整数:字段类型必须是整数类型。
- 每表一个:一个表最多只能有一个自增长字段。
7.3 AUTO_INCREMENT 示例
sql
CREATE TABLE tt21 (
id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(10) NOT NULL DEFAULT ''
);
-- 不给 id 赋值,自动递增
INSERT INTO tt21 (name) VALUES ('张三');
INSERT INTO tt21 (name) VALUES ('李四');
INSERT INTO tt21 (name) VALUES ('王五');
SELECT * FROM tt21;
输出:
bash
+----+------+
| id | name |
+----+------+
| 1 | 张三 |
| 2 | 李四 |
| 3 | 王五 |
+----+------+
id 字段自动从 1 开始递增。
7.4 获取最后插入的自增值
sql
SELECT LAST_INSERT_ID();
输出:
bash
+------------------+
| LAST_INSERT_ID() |
+------------------+
| 3 |
+------------------+
返回最后一次插入的自增值。如果是批量插入,返回的是第一个插入的值。
7.5 手动设置自增值
可以手动指定自增字段的值,下次自增时会从这个值加 1:
sql
INSERT INTO tt21 (id, name) VALUES (100, '赵六');
-- 下次不指定 id 时,会从 101 开始
INSERT INTO tt21 (name) VALUES ('孙七');
SELECT * FROM tt21;
输出:
bash
+-----+------+
| id | name |
+-----+------+
| 1 | 张三 |
| 2 | 李四 |
| 3 | 王五 |
| 100 | 赵六 |
| 101 | 孙七 |
+-----+------+
八、UNIQUE KEY 唯一键
8.1 唯一键的定义
唯一键保证字段中的值不重复。与主键的主要区别是:
- 一个表可以有多个唯一键,但只能有一个主键。
- 唯一键允许为 NULL,且可以有多个 NULL 值。
- 唯一键不必非空。
8.2 主键 vs 唯一键
| 特性 | 主键 | 唯一键 |
|---|---|---|
| 数量 | 每表最多 1 个 | 每表可多个 |
| NULL 值 | 不允许 | 允许(可多个) |
| 用途 | 标识行的唯一性 | 业务上的唯一约束 |
| 通常用作 | 逻辑 ID(自增) | 业务字段(邮箱、用户名) |
8.3 UNIQUE KEY 示例
sql
CREATE TABLE student (
id CHAR(10) UNIQUE COMMENT '学号,不能重复但可为空',
name VARCHAR(20) NOT NULL
);
-- 正常插入
INSERT INTO student (id, name) VALUES ('001', '张三');
-- 尝试插入重复的学号
INSERT INTO student (id, name) VALUES ('001', '李四');
输出:
bash
ERROR 1062 (23000): Duplicate entry '001' for key 'id'
NULL 值的特殊性:
sql
-- 唯一键允许 NULL 值
INSERT INTO student (id, name) VALUES (NULL, '李四');
-- 可以插入多个 NULL
INSERT INTO student (id, name) VALUES (NULL, '王五');
SELECT * FROM student;
输出:
bash
+------+------+
| id | name |
+------+------+
| 001 | 张三 |
| NULL | 李四 |
| NULL | 王五 |
+------+------+
唯一键约束不适用于 NULL 值,所以可以有多个 NULL。
8.4 UNIQUE KEY 的使用场景
| 字段 | 约束类型 | 说明 |
|---|---|---|
| 用户ID | PRIMARY KEY | 唯一标识用户 |
| 用户名 | UNIQUE KEY | 注册时不能重复 |
| 邮箱 | UNIQUE KEY | 注册时不能重复 |
| 身份证号 | UNIQUE KEY | 业务上不能重复 |
| 电话号码 | UNIQUE KEY | 业务上不能重复 |
九、FOREIGN KEY 外键
9.1 外键的定义
外键用来建立两个表之间的关系。通过外键约束,确保数据的引用完整性。
外键的组成:
- 主表(父表):包含被引用的列,该列通常是主键。
- 从表(子表):包含外键列,引用主表的数据。
9.2 外键的语法
在从表中定义外键:
sql
FOREIGN KEY (外键列) REFERENCES 主表名(主表的列)
9.3 外键示例
场景:班级和学生。每个学生属于某个班级。班级表是主表,学生表是从表。
创建班级表(主表):
sql
CREATE TABLE myclass (
id INT PRIMARY KEY,
name VARCHAR(30) NOT NULL COMMENT '班级名'
);
创建学生表(从表,包含外键):
sql
CREATE TABLE stu (
id INT PRIMARY KEY,
name VARCHAR(30) NOT NULL COMMENT '学生名',
class_id INT,
FOREIGN KEY (class_id) REFERENCES myclass(id)
);
插入班级数据:
sql
INSERT INTO myclass VALUES (10, 'C++ 大牛班'), (20, 'Java 大神班');
插入学生数据(正常情况):
sql
-- 学生属于班级 10 和 20,都存在
INSERT INTO stu VALUES (100, '张三', 10);
INSERT INTO stu VALUES (101, '李四', 20);
成功。
尝试插入不存在的班级:
sql
-- 学生属于班级 30,但班级 30 不存在
INSERT INTO stu VALUES (102, '王五', 30);
输出:
bash
ERROR 1452 (23000): Cannot add or update a child row:
a foreign key constraint fails (`database`.`stu`, CONSTRAINT `stu_ibfk_1`
FOREIGN KEY (`class_id`) REFERENCES `myclass` (`id`))
外键约束阻止了这个违规操作。
插入 NULL 值:
sql
-- 学生暂时没有分配班级,class_id 为 NULL 是允许的
INSERT INTO stu VALUES (102, '王五', NULL);
成功。外键允许 NULL 值。
9.4 外键的作用
为什么需要外键:
假设没有外键约束,你可以随意在学生表中插入任意的班级 ID,即使那个班级并不存在。这会导致:
- 数据不一致:存在学生的班级 ID 指向不存在的班级。
- 数据混乱:无法知道哪些数据是真实有效的。
- 业务错误:报表统计会出错。
外键的好处:
- 提前告诉 MySQL 表之间的关系。
- 让 MySQL 自动验证数据的一致性。
- 防止无效数据的插入。
9.5 外键的级联操作
默认情况下,如果主表中的数据被删除,从表中有引用时会报错。可以设置级联操作来自动处理。
示例:
sql
CREATE TABLE stu (
id INT PRIMARY KEY,
name VARCHAR(30) NOT NULL,
class_id INT,
FOREIGN KEY (class_id) REFERENCES myclass(id)
ON DELETE CASCADE -- 删除班级时,对应的学生也删除
ON UPDATE CASCADE -- 更新班级 ID 时,学生记录也更新
);
级联操作选项:
| 选项 | 说明 |
|---|---|
RESTRICT |
默认。如果从表有引用,不允许删除主表数据 |
CASCADE |
主表数据被删除时,从表相关数据也删除 |
SET NULL |
主表数据被删除时,从表的外键值设为 NULL |
NO ACTION |
与 RESTRICT 类似 |
十、综合案例:电商数据库设计
10.1 需求分析
设计一个电商平台的数据库,包含三个核心表:
- 商品表 (goods):存储商品信息
- 客户表 (customer):存储客户信息
- 购买记录表 (purchase):记录客户的购买信息
10.2 表结构设计
sql
-- 创建数据库
CREATE DATABASE IF NOT EXISTS bit32mall
DEFAULT CHARACTER SET utf8mb4;
USE bit32mall;
-- 创建商品表(主表)
CREATE TABLE IF NOT EXISTS goods (
goods_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '商品编号',
goods_name VARCHAR(32) NOT NULL COMMENT '商品名称',
unitprice INT NOT NULL DEFAULT 0 COMMENT '单价,单位分',
category VARCHAR(12) COMMENT '商品分类',
provider VARCHAR(64) NOT NULL COMMENT '供应商名称'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 创建客户表(主表)
CREATE TABLE IF NOT EXISTS customer (
customer_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '客户编号',
name VARCHAR(32) NOT NULL COMMENT '客户姓名',
address VARCHAR(256) COMMENT '客户地址',
email VARCHAR(64) UNIQUE KEY COMMENT '电子邮箱',
sex ENUM('男', '女') NOT NULL COMMENT '性别',
card_id CHAR(18) UNIQUE KEY COMMENT '身份证号'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- 创建购买记录表(从表,包含两个外键)
CREATE TABLE IF NOT EXISTS purchase (
order_id INT PRIMARY KEY AUTO_INCREMENT COMMENT '订单号',
customer_id INT COMMENT '客户编号',
goods_id INT COMMENT '商品编号',
nums INT DEFAULT 0 COMMENT '购买数量',
FOREIGN KEY (customer_id) REFERENCES customer(customer_id),
FOREIGN KEY (goods_id) REFERENCES goods(goods_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
10.3 插入测试数据
插入商品数据:
sql
INSERT INTO goods (goods_name, unitprice, category, provider) VALUES
('iPhone 15', 599900, '手机', '苹果公司'),
('MacBook Pro', 1299900, '电脑', '苹果公司'),
('Android 手机', 199900, '手机', '小米公司'),
('无线耳机', 49900, '配件', '索尼公司');
插入客户数据:
sql
INSERT INTO customer (name, address, email, sex, card_id) VALUES
('张三', '北京市朝阳区', 'zhangsan@example.com', '男', '110101199001011234'),
('李四', '上海市浦东新区', 'lisi@example.com', '女', '310101199201021234'),
('王五', '深圳市南山区', 'wangwu@example.com', '男', '440301199401031234');
插入购买记录:
sql
-- 正常插入(客户和商品都存在)
INSERT INTO purchase (customer_id, goods_id, nums) VALUES
(1, 1, 1), -- 客户 1 购买商品 1(iPhone 15)1 部
(1, 4, 2), -- 客户 1 购买商品 4(无线耳机)2 个
(2, 2, 1), -- 客户 2 购买商品 2(MacBook Pro)1 部
(3, 3, 1); -- 客户 3 购买商品 3(Android 手机)1 部
SELECT * FROM purchase;
输出:
bash
+----------+-------------+----------+------+
| order_id | customer_id | goods_id | nums |
+----------+-------------+----------+------+
| 1 | 1 | 1 | 1 |
| 2 | 1 | 4 | 2 |
| 3 | 2 | 2 | 1 |
| 4 | 3 | 3 | 1 |
+----------+-------------+----------+------+
尝试外键约束验证:
sql
-- 尝试购买不存在的商品(商品 ID 99 不存在)
INSERT INTO purchase (customer_id, goods_id, nums) VALUES (1, 99, 1);
输出:
bash
ERROR 1452 (23000): Cannot add or update a child row:
a foreign key constraint fails
外键约束阻止了这个操作。
10.4 关联查询示例
查询客户张三的所有购买记录:
sql
SELECT
c.name AS '客户姓名',
g.goods_name AS '商品名称',
g.unitprice AS '单价',
p.nums AS '数量',
(g.unitprice * p.nums) AS '总价'
FROM purchase p
JOIN customer c ON p.customer_id = c.customer_id
JOIN goods g ON p.goods_id = g.goods_id
WHERE c.name = '张三';
输出:
bash
+-----------+----------+-------+------+-------+
| 客户姓名 | 商品名称 | 单价 | 数量 | 总价 |
+-----------+----------+-------+------+-------+
| 张三 | iPhone 15| 599900| 1 | 599900|
| 张三 | 无线耳机 | 49900 | 2 | 99800 |
+-----------+----------+-------+------+-------+
十一、约束的最佳实践
11.1 设计表时的约束建议
| 约束类型 | 使用建议 |
|---|---|
| NOT NULL | 除非有明确原因允许为空,否则都设置 NOT NULL |
| DEFAULT | 为常用值设置默认值,避免重复输入 |
| PRIMARY KEY | 每个表都需要一个主键,通常用自增整数 |
| AUTO_INCREMENT | 与主键搭配,自动生成唯一标识 |
| UNIQUE KEY | 为需要唯一的业务字段设置(如邮箱、用户名) |
| FOREIGN KEY | 建立表之间的关系,维护数据一致性 |
| COMMENT | 为每个字段添加说明,便于维护 |
11.2 常见设计错误
❌ 错误一:用业务字段作主键
sql
-- 不推荐
CREATE TABLE users (
email VARCHAR(100) PRIMARY KEY,
name VARCHAR(50)
);
问题:如果业务需求变化(如允许多个邮箱),主键改变会很麻烦。
✅ 正确做法:
sql
-- 推荐
CREATE TABLE users (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
email VARCHAR(100) UNIQUE,
name VARCHAR(50)
);
❌ 错误二:忽视外键约束
sql
-- 不推荐
CREATE TABLE orders (
order_id INT PRIMARY KEY,
customer_id INT, -- 没有外键约束
amount DECIMAL(10, 2)
);
问题:可能插入不存在的客户 ID,导致数据不一致。
✅ 正确做法:
sql
-- 推荐
CREATE TABLE orders (
order_id INT PRIMARY KEY AUTO_INCREMENT,
customer_id INT NOT NULL,
amount DECIMAL(10, 2),
FOREIGN KEY (customer_id) REFERENCES customer(customer_id)
);
❌ 错误三:允许太多 NULL 值
sql
-- 不推荐
CREATE TABLE products (
id INT PRIMARY KEY,
name VARCHAR(100), -- 可能为 NULL,但名称是必需的
price DECIMAL(10, 2), -- 可能为 NULL,但价格是必需的
description TEXT
);
✅ 正确做法:
sql
-- 推荐
CREATE TABLE products (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
price DECIMAL(10, 2) NOT NULL,
description TEXT
);
11.3 性能考虑
- 主键选择:使用小整数类型,避免大字符串。
- 外键数量:外键会增加插入删除的检查成本,合理使用。
- 唯一键数量:唯一键也会被创建索引,过多会影响插入性能。
- NULL 值:NULL 值在索引中占用空间,尽量避免。
11.4 修改约束的常用操作
添加 NOT NULL 约束:
sql
ALTER TABLE table_name MODIFY column_name datatype NOT NULL;
添加默认值:
sql
ALTER TABLE table_name MODIFY column_name datatype DEFAULT value;
添加唯一键:
sql
ALTER TABLE table_name ADD UNIQUE KEY (column_name);
删除唯一键:
sql
ALTER TABLE table_name DROP INDEX index_name;
添加外键:
sql
ALTER TABLE child_table ADD FOREIGN KEY (fk_column)
REFERENCES parent_table(pk_column);
删除外键:
sql
ALTER TABLE child_table DROP FOREIGN KEY constraint_name;
十二、约束与数据完整性
12.1 数据完整性的三个层面
| 层面 | 约束 | 说明 |
|---|---|---|
| 实体完整性 | PRIMARY KEY | 每行数据都有唯一的标识 |
| 域完整性 | NOT NULL、DEFAULT、CHECK | 字段中的数据满足定义的条件 |
| 参照完整性 | FOREIGN KEY | 表之间的数据保持一致 |
12.2 约束的执行顺序
在执行 INSERT 或 UPDATE 时,MySQL 按以下顺序检查约束:
- 数据类型检查:值是否符合列的数据类型。
- NOT NULL 检查:是否违反 NOT NULL 约束。
- 默认值:如果没有提供值且有默认值,使用默认值。
- 唯一性检查:是否违反 UNIQUE 或 PRIMARY KEY 约束。
- 外键检查:是否违反 FOREIGN KEY 约束。
- 触发器:执行可能存在的触发器。
任何一步失败都会中止操作,返回错误。
十三、总结与下一步
现在你已经掌握了:
✅ NULL/NOT NULL:控制字段是否允许为空
✅ DEFAULT:设置字段的默认值
✅ COMMENT:为字段添加注释说明
✅ ZEROFILL:整数字段的零填充
✅ PRIMARY KEY:唯一标识表中的行,每表一个
✅ AUTO_INCREMENT:自动递增,通常与主键搭配
✅ UNIQUE KEY:保证字段值唯一,可多个,允许 NULL
✅ FOREIGN KEY:建立表间关系,维护数据一致性
✅ 完整案例:电商数据库的三表设计与关联
✅ 最佳实践:约束的设计原则和常见错误
建议练习:
-
根据以下需求设计数据库表:
- 博客系统:用户表、文章表、评论表、标签表
- 图书管理系统:书籍表、用户表、借阅记录表、分类表
-
为每个表设计合适的约束(主键、唯一键、外键、NOT NULL 等)。
-
尝试插入一些违反约束的数据,理解约束是如何工作的。
下一篇,我们将学习基本查询。查询是数据库最常用的操作,会详细讲解 SELECT 语句的各种用法、WHERE 条件、ORDER BY 排序、LIMIT 限制等。