【MySQL篇】表的约束:保证数据完整性

文章目录

    • 表的约束:保证数据完整性
    • 一、前言
    • [二、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 主键)
    • [七、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   | 男  |
+-------+-----+-----+

agesex 字段自动使用了默认值。

插入数据,显式给出值

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 显示为 000001000002 等。
  • 编码字段:确保编码位数一致。
  • 很少使用:现代应用通常在应用层处理格式化,不在数据库层。

六、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      |       |
+--------+---------------------+------+-----+---------+-------+

idcourse 两列的 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 按以下顺序检查约束:

  1. 数据类型检查:值是否符合列的数据类型。
  2. NOT NULL 检查:是否违反 NOT NULL 约束。
  3. 默认值:如果没有提供值且有默认值,使用默认值。
  4. 唯一性检查:是否违反 UNIQUE 或 PRIMARY KEY 约束。
  5. 外键检查:是否违反 FOREIGN KEY 约束。
  6. 触发器:执行可能存在的触发器。

任何一步失败都会中止操作,返回错误。


十三、总结与下一步

现在你已经掌握了:

NULL/NOT NULL:控制字段是否允许为空

DEFAULT:设置字段的默认值

COMMENT:为字段添加注释说明

ZEROFILL:整数字段的零填充

PRIMARY KEY:唯一标识表中的行,每表一个

AUTO_INCREMENT:自动递增,通常与主键搭配

UNIQUE KEY:保证字段值唯一,可多个,允许 NULL

FOREIGN KEY:建立表间关系,维护数据一致性

完整案例:电商数据库的三表设计与关联

最佳实践:约束的设计原则和常见错误

建议练习

  1. 根据以下需求设计数据库表:

    • 博客系统:用户表、文章表、评论表、标签表
    • 图书管理系统:书籍表、用户表、借阅记录表、分类表
  2. 为每个表设计合适的约束(主键、唯一键、外键、NOT NULL 等)。

  3. 尝试插入一些违反约束的数据,理解约束是如何工作的。


下一篇,我们将学习基本查询。查询是数据库最常用的操作,会详细讲解 SELECT 语句的各种用法、WHERE 条件、ORDER BY 排序、LIMIT 限制等。


相关推荐
rchmin2 小时前
阿里Tair分布式锁与Redis分布式锁的实现区别
数据库·redis·分布式
等....10 小时前
Minio使用
数据库
win x11 小时前
Redis 使用~如何在Java中连接使用redis
java·数据库·redis
迷枫71212 小时前
DM8 数据库安装实战:从零搭建达梦数据库环境(附全套工具链接)
数据库
XDHCOM13 小时前
PostgreSQL 25001: active_sql_transaction 报错原因分析,故障修复步骤详解,远程处理解决方案
数据库·sql·postgresql
卤炖阑尾炎13 小时前
PostgreSQL 日常运维全指南:从基础操作到备份恢复
运维·数据库·postgresql
daad77714 小时前
wifi_note
运维·服务器·数据库
计算机毕设vx_bysj686914 小时前
【免费领源码】77196基于java的手机银行app管理系统的设计与实现 计算机毕业设计项目推荐上万套实战教程JAVA,node.js,C++、python、大屏数据可视化
java·mysql·智能手机·课程设计
吴声子夜歌15 小时前
ES6——正则的扩展详解
前端·mysql·es6