MySQL 从零入门(三):表的约束详解

MySQL 从零入门(三):表的约束详解(NOT NULL、DEFAULT、PRIMARY KEY、AUTO_INCREMENT、UNIQUE、FOREIGN KEY,附 SQL 实战)


🔥 星恒随风: 个人主页 ❄️ 个人专栏: 《Linux网络编程从0到1》 | 《C语言基础》 | 《数据结构和算法》 | 《MySQL入门心法》 | 《前端基础》 | 《python基础》 | 《C++从入门到入土》 | 《Linux系统编程》 ✨ 数据即知识,压缩即智能


文章目录

  • [MySQL 从零入门(三):表的约束详解(NOT NULL、DEFAULT、PRIMARY KEY、AUTO_INCREMENT、UNIQUE、FOREIGN KEY,附 SQL 实战)](#MySQL 从零入门(三):表的约束详解(NOT NULL、DEFAULT、PRIMARY KEY、AUTO_INCREMENT、UNIQUE、FOREIGN KEY,附 SQL 实战))
  • [一、MySQL 约束的基本概念](#一、MySQL 约束的基本概念)
    • [1.1 什么是约束?](#1.1 什么是约束?)
    • [1.2 常见约束及相关属性](#1.2 常见约束及相关属性)
    • [1.3 准备实验环境](#1.3 准备实验环境)
  • [二、NULL 与 NOT NULL:空值约束](#二、NULL 与 NOT NULL:空值约束)
    • [2.1 什么是 NULL?](#2.1 什么是 NULL?)
    • [2.2 NULL 参与运算](#2.2 NULL 参与运算)
    • [2.3 NOT NULL:不允许为空](#2.3 NOT NULL:不允许为空)
    • [2.4 验证 NOT NULL 约束](#2.4 验证 NOT NULL 约束)
    • [2.5 NOT NULL 能否禁止空字符串?](#2.5 NOT NULL 能否禁止空字符串?)
  • 三、DEFAULT:默认值约束
    • [3.1 为什么需要默认值?](#3.1 为什么需要默认值?)
    • [3.2 DEFAULT 基本语法](#3.2 DEFAULT 基本语法)
    • [3.3 创建带有默认值的表](#3.3 创建带有默认值的表)
    • [3.4 默认值什么时候生效?](#3.4 默认值什么时候生效?)
    • [3.5 DEFAULT 和 NULL 的区别](#3.5 DEFAULT 和 NULL 的区别)
    • [3.6 未设置 DEFAULT 的字段一定不能省略吗?](#3.6 未设置 DEFAULT 的字段一定不能省略吗?)
  • 四、COMMENT:字段注释
    • [4.1 什么是 COMMENT?](#4.1 什么是 COMMENT?)
    • [4.2 创建带注释的表](#4.2 创建带注释的表)
    • [4.3 如何查看字段注释?](#4.3 如何查看字段注释?)
    • [4.4 COMMENT 有什么作用?](#4.4 COMMENT 有什么作用?)
  • 五、ZEROFILL:零填充属性
    • [5.1 INT(11) 中的 11 是什么意思?](#5.1 INT(11) 中的 11 是什么意思?)
    • [5.2 ZEROFILL 的作用](#5.2 ZEROFILL 的作用)
    • [5.3 数据库中真的存储了 00001 吗?](#5.3 数据库中真的存储了 00001 吗?)
    • [5.4 ZEROFILL 与 UNSIGNED](#5.4 ZEROFILL 与 UNSIGNED)
  • [六、PRIMARY KEY:主键约束](#六、PRIMARY KEY:主键约束)
    • [6.1 什么是主键?](#6.1 什么是主键?)
    • [6.2 创建主键](#6.2 创建主键)
    • [6.3 验证主键不能重复](#6.3 验证主键不能重复)
    • [6.4 主键不能保存 NULL](#6.4 主键不能保存 NULL)
    • [6.5 创建表后添加主键](#6.5 创建表后添加主键)
    • [6.6 删除主键](#6.6 删除主键)
  • 七、复合主键
    • [7.1 什么是复合主键?](#7.1 什么是复合主键?)
    • [7.2 创建复合主键](#7.2 创建复合主键)
    • [7.3 复合主键的唯一性](#7.3 复合主键的唯一性)
  • 八、AUTO_INCREMENT:自增长属性
    • [8.1 为什么需要自增长?](#8.1 为什么需要自增长?)
    • [8.2 创建自增长主键](#8.2 创建自增长主键)
    • [8.3 插入数据](#8.3 插入数据)
    • [8.4 AUTO_INCREMENT 的使用条件](#8.4 AUTO_INCREMENT 的使用条件)
    • [8.5 自增长是否一定连续?](#8.5 自增长是否一定连续?)
    • [8.6 LAST_INSERT_ID()](#8.6 LAST_INSERT_ID())
  • [九、UNIQUE KEY:唯一约束](#九、UNIQUE KEY:唯一约束)
    • [9.1 为什么需要 UNIQUE?](#9.1 为什么需要 UNIQUE?)
    • [9.2 创建 UNIQUE 字段](#9.2 创建 UNIQUE 字段)
    • [9.3 验证 UNIQUE](#9.3 验证 UNIQUE)
    • [9.4 UNIQUE 可以保存 NULL 吗?](#9.4 UNIQUE 可以保存 NULL 吗?)
    • [9.5 PRIMARY KEY 与 UNIQUE 的区别](#9.5 PRIMARY KEY 与 UNIQUE 的区别)
    • [9.6 为什么推荐使用业务无关的主键?](#9.6 为什么推荐使用业务无关的主键?)
  • [十、FOREIGN KEY:外键约束](#十、FOREIGN KEY:外键约束)
    • [10.1 为什么需要外键?](#10.1 为什么需要外键?)
    • [10.2 什么是外键?](#10.2 什么是外键?)
    • [10.3 外键基本语法](#10.3 外键基本语法)
    • [10.4 创建父表](#10.4 创建父表)
    • [10.5 创建子表](#10.5 创建子表)
    • [10.6 插入正常数据](#10.6 插入正常数据)
    • [10.7 插入不存在的班级编号](#10.7 插入不存在的班级编号)
    • [10.8 外键可以为 NULL 吗?](#10.8 外键可以为 NULL 吗?)
    • [10.9 外键对删除父表记录有什么影响?](#10.9 外键对删除父表记录有什么影响?)
    • [10.10 外键使用注意事项](#10.10 外键使用注意事项)
  • 十一、综合实战:设计一个简单的商城数据库
    • [11.1 创建数据库](#11.1 创建数据库)
    • [11.2 创建商品表 goods](#11.2 创建商品表 goods)
    • [11.3 创建客户表 customer](#11.3 创建客户表 customer)
    • [11.4 创建购买记录表 purchase](#11.4 创建购买记录表 purchase)
    • [11.5 插入测试数据](#11.5 插入测试数据)
    • [11.6 验证外键约束](#11.6 验证外键约束)
    • [11.7 这个商城数据库还存在哪些问题?](#11.7 这个商城数据库还存在哪些问题?)
  • 十二、常见问题总结
    • [12.1 为什么 NOT NULL 仍然可以插入空字符串?](#12.1 为什么 NOT NULL 仍然可以插入空字符串?)
    • [12.2 为什么设置 DEFAULT 后,插入 NULL 没有使用默认值?](#12.2 为什么设置 DEFAULT 后,插入 NULL 没有使用默认值?)
    • [12.3 PRIMARY KEY 与 UNIQUE 有什么区别?](#12.3 PRIMARY KEY 与 UNIQUE 有什么区别?)
    • [12.4 AUTO_INCREMENT 能保证编号连续吗?](#12.4 AUTO_INCREMENT 能保证编号连续吗?)
    • [12.5 复合主键是否要求每个字段都不重复?](#12.5 复合主键是否要求每个字段都不重复?)
    • [12.6 为什么插入外键值时报错?](#12.6 为什么插入外键值时报错?)
    • [12.7 为什么删除父表记录时会报错?](#12.7 为什么删除父表记录时会报错?)
    • [12.8 PRIMARY KEY 和索引有什么关系?](#12.8 PRIMARY KEY 和索引有什么关系?)
  • [十三、常用 SQL 约束速查表](#十三、常用 SQL 约束速查表)
  • 十四、总结

前言

在上一篇《MySQL 从零入门(二):表的操作与数据类型详解》中,我们学习了如何创建和修改数据库表,以及 MySQL 中常见的数据类型。

例如,可以通过下面的 SQL 创建一张学生表:

sql 复制代码
CREATE TABLE student (
    id INT,
    name VARCHAR(20),
    age TINYINT
);

但是,这张表存在一些问题。

假设插入以下数据:

sql 复制代码
INSERT INTO student VALUES (1, '张三', 18);
INSERT INTO student VALUES (1, '李四', 20);
INSERT INTO student VALUES (2, NULL, 19);
INSERT INTO student VALUES (3, '王五', NULL);

这些数据可能全部插入成功。

但是从业务角度考虑:

  • 学号 id 不应该重复。
  • 学生姓名 name 不应该为空。
  • 学生年龄 age 可能需要默认值。
  • 学生所属班级必须真实存在。
  • 一个用户的邮箱不应该与其他用户重复。

仅依靠 INT、VARCHAR 等数据类型,并不能保证所有业务规则都得到满足。

因此,我们需要使用 MySQL 约束(Constraint)。

约束的作用,就是在数据库层面对数据施加规则,防止不符合要求的数据进入数据库。

本文将系统介绍 MySQL 中常见的约束,并结合实际 SQL 操作说明它们的作用和区别。


一、MySQL 约束的基本概念

1.1 什么是约束?

约束(Constraint)是定义在数据库表或字段上的规则,用于限制数据的取值、唯一性以及表之间的关系。

例如:

sql 复制代码
CREATE TABLE student (
    id INT PRIMARY KEY,
    name VARCHAR(20) NOT NULL,
    age TINYINT UNSIGNED DEFAULT 18
);

这里出现了三个约束:

sql 复制代码
PRIMARY KEY

表示主键,要求 id 唯一且不能为空。

sql 复制代码
NOT NULL

表示 name 字段不能保存 SQL 的 NULL。

sql 复制代码
DEFAULT 18

表示在没有显式提供 age 值时,可以采用默认值 18。

因此,数据库不仅负责存储数据,还可以在数据写入时检查是否满足指定规则。

1.2 常见约束及相关属性

约束或属性 作用
NULL / NOT NULL 控制字段能否保存 NULL
DEFAULT 设置字段默认值
COMMENT 添加字段说明
ZEROFILL 控制整数的零填充显示格式
PRIMARY KEY 主键,保证记录唯一标识
AUTO_INCREMENT 自动生成递增编号
UNIQUE KEY 保证字段值或字段组合唯一
FOREIGN KEY 建立表之间的引用约束

需要说明:

COMMENT 和 ZEROFILL 严格来说并不是与主键、外键同类的完整性约束,而是字段定义中的属性。

但是它们都属于建表时经常使用的 SQL 语法,因此放在一起学习。

1.3 准备实验环境

首先在 Ubuntu 终端连接 MySQL:

bash 复制代码
mycli -h 127.0.0.1 -P 3306 -u root

创建专用实验数据库:

sql 复制代码
CREATE DATABASE IF NOT EXISTS lab05
CHARACTER SET utf8
COLLATE utf8_general_ci;

切换到该数据库:

sql 复制代码
USE lab05;

查看当前数据库:

sql 复制代码
SELECT DATABASE();

预期结果:

text 复制代码
+------------+
| DATABASE() |
+------------+
| lab05      |
+------------+

接下来的实验都在 lab05 数据库中完成。


二、NULL 与 NOT NULL:空值约束

2.1 什么是 NULL?

在 MySQL 中,NULL 表示一个值不存在、未知或者尚未确定。

例如,一名学生还没有填写电话号码:

text 复制代码
+----+--------+-------+
| id | name   | phone |
+----+--------+-------+
|  1 | 张三   | NULL  |
+----+--------+-------+

这里的 NULL 并不是数字 0,也不是空字符串。

需要区分:

值 含义
NULL 未知或缺失的值
0 数字零
'' 空字符串
'NULL' 内容为 NULL 的普通字符串

例如:

sql 复制代码
SELECT NULL;

返回:

text 复制代码
NULL

而:

sql 复制代码
SELECT '';

返回的是空字符串。

2.2 NULL 参与运算

执行:

sql 复制代码
SELECT 1 + NULL;

结果:

text 复制代码
+----------+
| 1 + NULL |
+----------+
| NULL     |
+----------+

为什么不是 1?

因为 NULL 表示未知值。

如果一个运算需要依赖未知值,那么计算结果通常也是未知的。

例如:

sql 复制代码
SELECT 100 + NULL;
SELECT 100 * NULL;

结果均为 NULL。

需要特别注意:SQL 使用三值逻辑,除了 TRUE 和 FALSE,还存在 UNKNOWN。

因此,判断 NULL 时不能使用普通的等号。

错误示例:

sql 复制代码
SELECT * FROM student WHERE age = NULL;

正确写法:

sql 复制代码
SELECT * FROM student WHERE age IS NULL;

查询非 NULL:

sql 复制代码
SELECT * FROM student WHERE age IS NOT NULL;

这里:

  • IS NULL:判断字段是否为 NULL。
  • IS NOT NULL:判断字段是否不是 NULL。

2.3 NOT NULL:不允许为空

创建一张班级表:

sql 复制代码
CREATE TABLE class_required (
    class_name VARCHAR(20) NOT NULL,
    class_room VARCHAR(10) NOT NULL
);

逐个解释:

部分 含义
CREATE TABLE 创建表
class_required 表名
class_name 班级名称
VARCHAR(20) 最多保存 20 个字符
NOT NULL 不允许保存 NULL
class_room 教室名称

从业务角度考虑:

一间教室必须有名称,一个班级也应该有对应的班级名称。

因此,这两个字段都设置成 NOT NULL。

查看表结构:

sql 复制代码
DESC class_required;

预期结果:

text 复制代码
+------------+-------------+------+-----+---------+-------+
| Field      | Type        | Null | Key | Default | Extra |
+------------+-------------+------+-----+---------+-------+
| class_name | varchar(20) | NO   |     | NULL    |       |
| class_room | varchar(10) | NO   |     | NULL    |       |
+------------+-------------+------+-----+---------+-------+

其中:

text 复制代码
Null = NO

说明字段不允许保存 NULL。

2.4 验证 NOT NULL 约束

正常插入:

sql 复制代码
INSERT INTO class_required
VALUES ('C++班', 'A101');

插入成功。

接下来只指定班级名:

sql 复制代码
INSERT INTO class_required(class_name)
VALUES ('Java班');

在 MySQL 5.7 的严格 SQL 模式下,会报错:

text 复制代码
ERROR 1364 (HY000):
Field 'class_room' doesn't have a default value

原因是:

class_room 被设置为 NOT NULL,而且没有设置默认值。

因此插入记录时,不能直接省略这个必填字段。

如果显式插入 NULL:

sql 复制代码
INSERT INTO class_required
VALUES ('Python班', NULL);

同样会违反 NOT NULL 约束。

2.5 NOT NULL 能否禁止空字符串?

需要注意:

sql 复制代码
NOT NULL

只能禁止 SQL 的 NULL,并不能禁止普通空字符串。

例如:

sql 复制代码
INSERT INTO class_required
VALUES ('', '');

在当前字段定义下,这条 SQL 可以成功执行。

因为:

sql 复制代码
''

并不是 NULL。

因此,NOT NULL 不等于"字符串内容必须非空"。

真实项目如果要求用户名不能为空字符串,还需要在应用程序中校验,或者通过数据库支持的其他机制保证。


三、DEFAULT:默认值约束

3.1 为什么需要默认值?

假设我们创建一张用户表:

text 复制代码
name    age    sex

对于某些业务字段,如果用户没有提供值,可以采用事先指定的默认值。

例如:

  • 年龄默认 0;
  • 性别使用业务定义的默认选项;
  • 用户状态默认未激活。

MySQL 可以通过 DEFAULT 实现。

3.2 DEFAULT 基本语法

sql 复制代码
字段名 数据类型 DEFAULT 默认值

例如:

sql 复制代码
age TINYINT UNSIGNED DEFAULT 0

其中:

  • age:字段名。
  • TINYINT:小范围整数类型。
  • UNSIGNED:不允许保存负数。
  • DEFAULT 0:默认值为 0。

3.3 创建带有默认值的表

sql 复制代码
CREATE TABLE default_demo (
    name VARCHAR(20) NOT NULL,
    age TINYINT UNSIGNED DEFAULT 0,
    sex CHAR(2) DEFAULT '男'
);

查看结构:

sql 复制代码
DESC default_demo;

预期结果:

text 复制代码
+-------+---------------------+------+-----+---------+-------+
| Field | Type                | Null | Key | Default | Extra |
+-------+---------------------+------+-----+---------+-------+
| name  | varchar(20)         | NO   |     | NULL    |       |
| age   | tinyint(3) unsigned | YES  |     | 0       |       |
| sex   | char(2)             | YES  |     | 男      |       |
+-------+---------------------+------+-----+---------+-------+

3.4 默认值什么时候生效?

执行:

sql 复制代码
INSERT INTO default_demo(name)
VALUES ('zhangsan');

注意,这里只提供了 name 字段。

由于 age 和 sex 都存在默认值,因此 MySQL 可以自动填充。

查询:

sql 复制代码
SELECT * FROM default_demo;

结果:

text 复制代码
+----------+------+------+
| name     | age  | sex  |
+----------+------+------+
| zhangsan |    0 | 男   |
+----------+------+------+

说明 DEFAULT 已经生效。

3.5 DEFAULT 和 NULL 的区别

一个容易混淆的地方是:

设置 DEFAULT,并不意味着禁止字段保存 NULL。

例如:

sql 复制代码
age TINYINT UNSIGNED DEFAULT 0

并没有声明 NOT NULL。

因此:

sql 复制代码
INSERT INTO default_demo(name, age)
VALUES ('lisi', NULL);

可以保存:

text 复制代码
name = lisi
age = NULL

而不是自动变成 0。

因为显式插入 NULL 与省略字段不同。

如果希望字段既不能为空,又有默认值,可以同时使用:

sql 复制代码
age TINYINT UNSIGNED NOT NULL DEFAULT 0

这两种约束并不冲突。

3.6 未设置 DEFAULT 的字段一定不能省略吗?

不一定。

例如:

sql 复制代码
CREATE TABLE default_test (
    id INT,
    name VARCHAR(20)
);

两个字段都允许 NULL。

因此:

sql 复制代码
INSERT INTO default_test(id)
VALUES (1);

通常可以正常执行,省略的 name 使用隐含的 NULL 默认值。

但如果字段声明了 NOT NULL,没有默认值,且不是自增长等特殊情况,在严格模式下省略字段通常会报错。

所以应该区分:

显式 DEFAULT、隐含 NULL 默认值,以及 NOT NULL 必填字段。


四、COMMENT:字段注释

4.1 什么是 COMMENT?

在真实项目中,一张表可能有几十个字段。

例如:

text 复制代码
id
name
age
sex
status
created_at

如果没有说明信息,其他开发人员可能不知道某个字段具体代表什么。

因此,可以使用 COMMENT 给字段添加描述。

基本语法:

sql 复制代码
字段名 数据类型 COMMENT '说明文字'

4.2 创建带注释的表

sql 复制代码
CREATE TABLE comment_demo (
    name VARCHAR(20) NOT NULL COMMENT '姓名',
    age TINYINT UNSIGNED DEFAULT 0 COMMENT '年龄',
    sex CHAR(2) DEFAULT '男' COMMENT '性别'
);

其中:

sql 复制代码
COMMENT '姓名'

只是添加字段说明,并不会自动限制字段内容。

4.3 如何查看字段注释?

执行:

sql 复制代码
DESC comment_demo;

可以查看字段名称、类型、默认值等信息,但普通 DESC 输出不显示 COMMENT 内容。

可以执行:

sql 复制代码
SHOW CREATE TABLE comment_demo;

或者:

sql 复制代码
SHOW CREATE TABLE comment_demo\G

其中:

text 复制代码
\G

是 MySQL 客户端支持的纵向结果显示结束符。

在原生 MySQL 客户端中,它可以让结果按照纵向形式展示。

使用 mycli 时,也可以直接使用:

sql 复制代码
SHOW FULL COLUMNS FROM comment_demo;

其中 Comment 列可以显示字段注释。

4.4 COMMENT 有什么作用?

COMMENT 的主要作用包括:

  • 说明字段的业务含义;
  • 帮助开发人员理解数据库结构;
  • 提高数据库维护效率;
  • 为数据库文档提供描述信息。

例如:

sql 复制代码
status TINYINT COMMENT '0-禁用,1-启用'

比单纯写:

sql 复制代码
status TINYINT

更容易理解。


五、ZEROFILL:零填充属性

5.1 INT(11) 中的 11 是什么意思?

刚开始学习 MySQL 时,经常会看到:

text 复制代码
int(11)

但是我们知道:

sql 复制代码
INT

通常占用 4 字节。

那么,括号里的 11 是什么意思?

在 MySQL 5.7 中,整数类型括号中的数字主要表示显示宽度,而不是实际占用的字节数。

例如:

sql 复制代码
INT(5)

并不表示只能保存五位整数。

实际上,INT 的正常取值范围仍然由数据类型本身决定。

5.2 ZEROFILL 的作用

ZEROFILL 可以让数字在显示时,按照指定宽度在左侧补充 0。

例如:

sql 复制代码
CREATE TABLE zerofill_demo (
    a INT(5) UNSIGNED ZEROFILL,
    b INT
);

插入:

sql 复制代码
INSERT INTO zerofill_demo
VALUES (1, 2);

查询:

sql 复制代码
SELECT * FROM zerofill_demo;

在 MySQL 5.7 中,结果类似:

text 复制代码
+-------+------+
| a     | b    |
+-------+------+
| 00001 |    2 |
+-------+------+

可以看到:

  • a 显示为 00001。
  • b 仍然显示为 2。

为什么?

因为 a 设置了:

sql 复制代码
INT(5) UNSIGNED ZEROFILL

其中 5 表示显示宽度。

数字 1 只有一位,所以左侧补充四个 0。

5.3 数据库中真的存储了 00001 吗?

没有。

ZEROFILL 只影响显示格式,不会将整数转换为字符串。

可以通过:

sql 复制代码
SELECT a, HEX(a)
FROM zerofill_demo;

验证。

结果类似:

text 复制代码
+-------+--------+
| a     | HEX(a) |
+-------+--------+
| 00001 | 1      |
+-------+--------+

HEX() 用于将数值转换为十六进制形式。

数字 1 的十六进制仍然是 1。

因此:

ZEROFILL 不改变整数的实际数值,只影响整数显示时的补零效果。

5.4 ZEROFILL 与 UNSIGNED

在 MySQL 5.7 中,为整数列指定 ZEROFILL 会使其具有 UNSIGNED 属性。

例如:

sql 复制代码
INT(5) ZEROFILL

也会隐含无符号属性。

需要注意,ZEROFILL 属于旧版 MySQL 的显示功能。

实际开发中如果需要格式化编号,例如:

text 复制代码
000001
000002
000003

通常更推荐由应用程序或查询表达式负责格式化,而不是依赖数据库字段的显示宽度。


六、PRIMARY KEY:主键约束

6.1 什么是主键?

主键(Primary Key)用于唯一标识表中的一条记录。

例如:

text 复制代码
+----+--------+
| id | name   |
+----+--------+
|  1 | 张三   |
|  2 | 李四   |
|  3 | 王五   |
+----+--------+

我们希望通过 id 唯一找到某个学生。

因此:

sql 复制代码
id INT PRIMARY KEY

就可以把 id 设置成主键。

主键具有两个核心特点:

  1. 主键值不能重复。
  2. 主键列不能为 NULL。

一张表最多只能有一个主键,但这个主键可以由多个字段组合而成。

6.2 创建主键

sql 复制代码
CREATE TABLE primary_demo (
    id INT UNSIGNED PRIMARY KEY COMMENT '学号',
    name VARCHAR(20) NOT NULL
);

其中:

sql 复制代码
PRIMARY KEY

表示将 id 设置为主键。

查看表结构:

sql 复制代码
DESC primary_demo;

预期结果:

text 复制代码
+-------+------------------+------+-----+---------+-------+
| Field | Type             | Null | Key | Default | Extra |
+-------+------------------+------+-----+---------+-------+
| id    | int(10) unsigned | NO   | PRI | NULL    |       |
| name  | varchar(20)      | NO   |     | NULL    |       |
+-------+------------------+------+-----+---------+-------+

其中:

text 复制代码
Key = PRI

代表该字段属于主键。

6.3 验证主键不能重复

插入:

sql 复制代码
INSERT INTO primary_demo
VALUES (1, '张三');

正常执行。

再次插入:

sql 复制代码
INSERT INTO primary_demo
VALUES (1, '李四');

将出现:

text 复制代码
ERROR 1062 (23000):
Duplicate entry '1' for key 'PRIMARY'

原因是已经存在主键值为 1 的记录。

因此,MySQL 不允许再插入另一个 id = 1 的学生。

6.4 主键不能保存 NULL

执行:

sql 复制代码
INSERT INTO primary_demo
VALUES (NULL, '王五');

会违反主键非空要求。

这是因为:

主键必须能够唯一、明确地标识一条记录。

如果允许 NULL,就无法满足主键的基本语义。

6.5 创建表后添加主键

如果创建表时没有指定主键,可以通过 ALTER TABLE 添加。

例如:

sql 复制代码
CREATE TABLE primary_add_demo (
    id INT NOT NULL,
    name VARCHAR(20)
);

添加主键:

sql 复制代码
ALTER TABLE primary_add_demo
ADD PRIMARY KEY(id);

这里:

  • ALTER TABLE:修改表结构。
  • ADD:添加。
  • PRIMARY KEY(id):将 id 设置成主键。

如果表中已经存在重复 ID,添加主键就会失败。

因此,添加主键之前必须保证已有数据满足唯一性和非空要求。

6.6 删除主键

基本语法:

sql 复制代码
ALTER TABLE 表名
DROP PRIMARY KEY;

例如:

sql 复制代码
ALTER TABLE primary_add_demo
DROP PRIMARY KEY;

这会移除主键约束。

需要注意:

如果主键字段同时具有 AUTO_INCREMENT 属性,通常不能直接移除其唯一索引,而需要先处理自增长属性及索引依赖。


七、复合主键

7.1 什么是复合主键?

通常一个主键可以只包含一个字段。

例如:

sql 复制代码
PRIMARY KEY(id)

但是某些业务场景中,单独一个字段无法唯一确定记录。

例如学生成绩表:

student_id course score
1 C++ 90
1 MySQL 95
2 C++ 88

这里:

text 复制代码
student_id = 1

出现了两次。

但这并不代表数据有问题,因为同一名学生可以学习不同课程。

真正需要保证唯一的是:

text 复制代码
学生编号 + 课程编号

的组合。

这时可以使用复合主键。

7.2 创建复合主键

sql 复制代码
CREATE TABLE student_course (
    student_id INT UNSIGNED,
    course CHAR(10) COMMENT '课程代码',
    score TINYINT UNSIGNED DEFAULT 60 COMMENT '成绩',
    PRIMARY KEY(student_id, course)
);

关键部分:

sql 复制代码
PRIMARY KEY(student_id, course)

表示将 student_id 和 course 组合为一个主键。

注意,不是创建两个独立主键,而是创建一个包含两个字段的主键。

7.3 复合主键的唯一性

插入:

sql 复制代码
INSERT INTO student_course
VALUES (1, 'C++', 90);

再次插入:

sql 复制代码
INSERT INTO student_course
VALUES (1, 'MySQL', 95);

可以成功。

因为两个记录的组合键不同。

但是:

sql 复制代码
INSERT INTO student_course
VALUES (1, 'C++', 88);

就会违反复合主键的唯一性约束。

因为:

text 复制代码
(1, C++)

这个组合已经存在。

因此:

复合主键要求所有参与主键的字段组合唯一,而不是要求每个字段单独唯一。


八、AUTO_INCREMENT:自增长属性

8.1 为什么需要自增长?

假设用户注册时,需要为每个用户分配一个唯一编号。

如果每次手动输入:

sql 复制代码
INSERT INTO users VALUES (1, '张三');
INSERT INTO users VALUES (2, '李四');
INSERT INTO users VALUES (3, '王五');

会带来一些问题:

  • 程序需要自己管理编号。
  • 多个客户端同时插入时可能发生编号冲突。
  • 人工编号容易出错。

MySQL 提供了:

sql 复制代码
AUTO_INCREMENT

用于自动生成新的整数编号。

8.2 创建自增长主键

sql 复制代码
CREATE TABLE auto_demo (
    id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(20) NOT NULL
);

其中:

sql 复制代码
PRIMARY KEY AUTO_INCREMENT

表示:

  • id 是主键。
  • id 可以自动生成递增编号。

8.3 插入数据

执行:

sql 复制代码
INSERT INTO auto_demo(name)
VALUES ('张三');

这里没有指定 id。

然后:

sql 复制代码
INSERT INTO auto_demo(name)
VALUES ('李四');

查询:

sql 复制代码
SELECT * FROM auto_demo;

在新建空表且未调整自增长起点的情况下:

text 复制代码
+----+--------+
| id | name   |
+----+--------+
|  1 | 张三   |
|  2 | 李四   |
+----+--------+

MySQL 自动分配了主键编号。

8.4 AUTO_INCREMENT 的使用条件

在 MySQL 5.7 中,自增长具有几个特点:

  • 只能用于整数类型字段。
  • 一张表最多有一个 AUTO_INCREMENT 字段。
  • 该字段必须建立在索引上。
  • 通常与 PRIMARY KEY 配合使用。

一个常见写法是:

sql 复制代码
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT

适用于需要大量唯一编号的业务场景。

8.5 自增长是否一定连续?

不一定。

例如:

text 复制代码
1
2
3
5
6

也可能是合法的自增长编号序列。

原因包括:

  • 插入失败;
  • 事务回滚;
  • 删除记录;
  • 并发插入;
  • 自增长计数器的分配机制。

因此,AUTO_INCREMENT 的核心目的不是保证编号连续,而是帮助生成新的编号。

不要把自增长主键当作严格连续的业务流水号。

8.6 LAST_INSERT_ID()

MySQL 可以通过:

sql 复制代码
SELECT LAST_INSERT_ID();

获取当前连接最近一次生成的 AUTO_INCREMENT 值。

例如:

sql 复制代码
INSERT INTO auto_demo(name)
VALUES ('王五');

SELECT LAST_INSERT_ID();

如果自动生成的编号为 3,则返回:

text 复制代码
3

需要注意:

LAST_INSERT_ID() 与当前数据库连接有关。

如果批量插入多条记录,通常返回这次批量插入生成的第一个自增长编号。

对于 C/C++ 后端开发,这个功能很实用。

例如:

用户注册成功后,需要获得新用户的数据库 ID,再将该 ID 用于其他关联操作。


九、UNIQUE KEY:唯一约束

9.1 为什么需要 UNIQUE?

假设我们开发一个用户系统,用户表包含:

text 复制代码
id
username
email
phone

其中:

  • id 应该唯一。
  • email 不能重复。
  • phone 可能也需要唯一。

如果一个表只能有一个主键,应该怎样限制其他字段?

答案就是:

sql 复制代码
UNIQUE

也叫唯一键约束。

9.2 创建 UNIQUE 字段

sql 复制代码
CREATE TABLE unique_demo (
    id INT PRIMARY KEY AUTO_INCREMENT,
    email VARCHAR(64) UNIQUE,
    name VARCHAR(20)
);

其中:

sql 复制代码
email VARCHAR(64) UNIQUE

表示 email 字段不能出现重复的非 NULL 值。

9.3 验证 UNIQUE

插入:

sql 复制代码
INSERT INTO unique_demo(email, name)
VALUES ('zhangsan@example.com', '张三');

再次插入相同邮箱:

sql 复制代码
INSERT INTO unique_demo(email, name)
VALUES ('zhangsan@example.com', '李四');

会发生唯一键冲突。

错误信息通常类似:

text 复制代码
ERROR 1062 (23000):
Duplicate entry ... for key ...

说明该邮箱已经存在。

9.4 UNIQUE 可以保存 NULL 吗?

这是 UNIQUE 与 PRIMARY KEY 的重要区别之一。

在 MySQL 5.7 中,如果唯一字段允许 NULL,那么可以存在多个 NULL。

例如:

sql 复制代码
INSERT INTO unique_demo(email, name)
VALUES (NULL, '王五');

INSERT INTO unique_demo(email, name)
VALUES (NULL, '赵六');

都可以成功。

原因是:

SQL 的 NULL 表示未知值。

MySQL 的 UNIQUE 约束允许多个 NULL 值存在。

但是如果业务要求邮箱不能为空,就应该使用:

sql 复制代码
email VARCHAR(64) NOT NULL UNIQUE

这样既保证非空,又保证唯一。

9.5 PRIMARY KEY 与 UNIQUE 的区别

特点 PRIMARY KEY UNIQUE
是否要求唯一 是 是
是否允许 NULL 不允许 可允许多个 NULL
一张表的数量 最多一个 可以有多个
是否可以包含多个字段 可以 可以
典型用途 唯一标识记录 限制业务字段重复

例如:

sql 复制代码
CREATE TABLE account (
    id INT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(32) NOT NULL UNIQUE,
    email VARCHAR(64) NOT NULL UNIQUE
);

其中:

  • id:作为主键。
  • username:不能重复。
  • email:不能重复。

9.6 为什么推荐使用业务无关的主键?

例如,用户可以修改自己的邮箱。

如果将邮箱直接设计为主键:

sql 复制代码
email VARCHAR(64) PRIMARY KEY

将来修改邮箱时,可能影响其他引用这个主键的表。

因此,实际项目通常会使用一个稳定的编号:

sql 复制代码
id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT

再将邮箱声明为:

sql 复制代码
email VARCHAR(64) UNIQUE

这样,即使业务信息发生变化,主键也可以保持稳定。


十、FOREIGN KEY:外键约束

10.1 为什么需要外键?

假设我们有两张表:

班级表 myclass

id name
10 C++班
20 Java班

学生表 stu

id name class_id
100 张三 10
101 李四 20

其中:

text 复制代码
class_id

表示学生所属的班级编号。

问题来了:

如果插入:

text 复制代码
102 王五 30

但是班级表中根本不存在编号为 30 的班级,该怎么办?

从普通字段类型来看:

sql 复制代码
class_id INT

完全允许保存 30。

但是从业务角度来看,这是一条不合法的数据。

因此,需要使用外键来保证两张表之间的引用关系。

10.2 什么是外键?

外键(Foreign Key)用于约束一张表中的字段值,使其引用另一张表中符合要求的字段值。

通常:

  • 被引用的表称为父表或主表。
  • 包含外键字段的表称为子表或从表。

例如:

text 复制代码
myclass(父表)
    │
    │ id
    ▼
stu(子表)
    │
    └── class_id

这里:

text 复制代码
stu.class_id

引用:

text 复制代码
myclass.id

10.3 外键基本语法

sql 复制代码
FOREIGN KEY (子表字段)
REFERENCES 父表(被引用字段)

其中:

  • FOREIGN KEY:声明外键。
  • REFERENCES:指定引用关系。
  • 父表(字段):被引用的目标字段。

10.4 创建父表

sql 复制代码
CREATE TABLE myclass (
    id INT PRIMARY KEY,
    name VARCHAR(30) NOT NULL COMMENT '班级名'
) ENGINE=InnoDB;

这里 id 是班级主键。

使用 InnoDB,因为本实验需要真正执行外键约束检查。

10.5 创建子表

sql 复制代码
CREATE TABLE stu (
    id INT PRIMARY KEY,
    name VARCHAR(30) NOT NULL COMMENT '学生姓名',
    class_id INT,
    FOREIGN KEY (class_id)
        REFERENCES myclass(id)
) ENGINE=InnoDB;

关键部分:

sql 复制代码
FOREIGN KEY (class_id)
REFERENCES myclass(id)

表示:

学生表中的 class_id 必须引用 myclass 表中存在的 id,或者在允许 NULL 的情况下保存 NULL。

10.6 插入正常数据

先插入班级:

sql 复制代码
INSERT INTO myclass VALUES
(10, 'C++班'),
(20, 'Java班');

再插入学生:

sql 复制代码
INSERT INTO stu VALUES
(100, '张三', 10),
(101, '李四', 20);

查询:

sql 复制代码
SELECT * FROM stu;

结果:

text 复制代码
+-----+--------+----------+
| id  | name   | class_id |
+-----+--------+----------+
| 100 | 张三   |       10 |
| 101 | 李四   |       20 |
+-----+--------+----------+

10.7 插入不存在的班级编号

执行:

sql 复制代码
INSERT INTO stu VALUES
(102, '王五', 30);

由于 myclass 中没有编号为 30 的班级,MySQL 将拒绝该操作。

通常会看到:

text 复制代码
ERROR 1452 (23000):
Cannot add or update a child row:
a foreign key constraint fails

这说明外键约束生效了。

10.8 外键可以为 NULL 吗?

可以,前提是该外键字段允许 NULL。

例如:

sql 复制代码
INSERT INTO stu VALUES
(102, '王五', NULL);

可以表示:

学生王五目前还没有分配班级。

因为我们没有将:

sql 复制代码
class_id INT

声明为 NOT NULL。

如果业务要求每名学生必须属于一个班级,则应该同时声明:

sql 复制代码
class_id INT NOT NULL

10.9 外键对删除父表记录有什么影响?

假设班级 10 已经有学生:

text 复制代码
张三 → class_id = 10

如果执行:

sql 复制代码
DELETE FROM myclass
WHERE id = 10;

在当前未指定级联动作的 InnoDB 外键定义下,MySQL 通常会拒绝删除被子表引用的父表记录。

原因是:

如果班级被删除,张三的 class_id = 10 就会失去对应的班级记录。

这会破坏引用完整性。

MySQL 还支持 ON DELETE CASCADE、ON DELETE SET NULL 等外键动作,但是否使用需要根据业务需求决定。

尤其是 CASCADE 可能导致关联记录被自动删除,不能随意配置。

10.10 外键使用注意事项

在 MySQL 5.7 中使用 InnoDB 外键时,需要注意:

  1. 父表和子表应使用支持外键的存储引擎,例如 InnoDB。
  2. 外键字段与被引用字段的类型必须兼容。
  3. 整数字段的类型大小与有符号、无符号属性需要匹配。
  4. 外键相关字段需要满足索引要求。
  5. 被引用字段通常应设计为主键或唯一键。
  6. 修改或删除相关表结构时,需要考虑外键依赖。

例如:

sql 复制代码
parent_id INT UNSIGNED

和:

sql 复制代码
child_parent_id INT

由于符号属性不同,在 MySQL 5.7 中可能无法建立相应外键。

因此,创建外键前需要检查双方字段定义。


十一、综合实战:设计一个简单的商城数据库

下面综合使用前面学习的约束,设计一个简单的商城系统。

系统包含三张表:

text 复制代码
goods       商品表
customer    客户表
purchase    购买记录表

它们之间的关系如下:

text 复制代码
customer(客户)
    │
    │ customer_id
    ▼
purchase(购买记录)
    ▲
    │ goods_id
    │
goods(商品)

每条购买记录关联一个客户和一个商品。

11.1 创建数据库

sql 复制代码
CREATE DATABASE IF NOT EXISTS mall_demo
DEFAULT CHARACTER SET utf8;

选择数据库:

sql 复制代码
USE mall_demo;

11.2 创建商品表 goods

sql 复制代码
CREATE TABLE 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;

这里使用了:

  • PRIMARY KEY:商品主键。
  • AUTO_INCREMENT:自动生成商品编号。
  • NOT NULL:必填字段。
  • DEFAULT:默认值。
  • COMMENT:字段说明。

特别注意:

sql 复制代码
unitprice INT NOT NULL DEFAULT 0

这里选择整数,并将价格单位设为分。

例如:

text 复制代码
1999

表示:

text 复制代码
19.99 元

这种设计可以避免使用 FLOAT 保存金额时可能出现的精度误差。

另一种常见方案是使用 DECIMAL 保存十进制金额。

11.3 创建客户表 customer

sql 复制代码
CREATE TABLE 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;

这里涉及两个 UNIQUE 字段:

sql 复制代码
email VARCHAR(64) UNIQUE KEY

和:

sql 复制代码
card_id CHAR(18) UNIQUE KEY

可以保证对应字段中的非 NULL 值不重复。

这里的性别字段仅用于演示 ENUM 数据类型及其约束行为,真实系统应根据业务需求设计更合适的数据模型。

另外,身份证号属于敏感个人信息,真实项目不能仅依赖数据库 UNIQUE 约束,还需要考虑合法性校验、访问控制、加密及数据最小化。

11.4 创建购买记录表 purchase

sql 复制代码
CREATE TABLE 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;

这里有两个外键:

sql 复制代码
FOREIGN KEY (customer_id)
REFERENCES customer(customer_id)

表示购买记录中的客户必须引用客户表中的有效客户编号。

sql 复制代码
FOREIGN KEY (goods_id)
REFERENCES goods(goods_id)

表示购买记录中的商品必须引用商品表中的有效商品编号。

11.5 插入测试数据

先插入商品:

sql 复制代码
INSERT INTO goods
(goods_name, unitprice, category, provider)
VALUES
('机械键盘', 19900, '电脑外设', '供应商A'),
('无线鼠标', 5900, '电脑外设', '供应商B');

再插入客户:

sql 复制代码
INSERT INTO customer
(name, email, sex)
VALUES
('张三', 'zhangsan@example.com', '男'),
('李四', 'lisi@example.com', '女');

查询分配的编号:

sql 复制代码
SELECT * FROM goods;

SELECT * FROM customer;

如果实验数据库是新建的,且未指定或修改自增长编号,那么一般会从 1 开始。

假设查询确认:

text 复制代码
商品:
1 机械键盘
2 无线鼠标

客户:
1 张三
2 李四

再插入购买记录:

sql 复制代码
INSERT INTO purchase
(customer_id, goods_id, nums)
VALUES
(1, 1, 2),
(2, 2, 1);

查询:

sql 复制代码
SELECT * FROM purchase;

预期类似:

text 复制代码
+----------+-------------+----------+------+
| order_id | customer_id | goods_id | nums |
+----------+-------------+----------+------+
|        1 |           1 |        1 |    2 |
|        2 |           2 |        2 |    1 |
+----------+-------------+----------+------+

11.6 验证外键约束

尝试插入:

sql 复制代码
INSERT INTO purchase
(customer_id, goods_id, nums)
VALUES
(999, 1, 1);

这里客户编号为 999。

由于客户表中不存在对应客户,所以 MySQL 会因为外键约束而拒绝插入。

这说明外键可以在数据库层面阻止部分不合法的关联数据。

11.7 这个商城数据库还存在哪些问题?

虽然现在已经用到了很多约束,但从实际业务角度来看,还不够完善。

例如:

sql 复制代码
nums INT DEFAULT 0

目前仍然允许插入负数。

同样:

sql 复制代码
unitprice INT NOT NULL DEFAULT 0

也不能仅凭这些声明保证价格一定非负。

另外,purchase.customer_id 和 purchase.goods_id 也没有设置 NOT NULL。

这意味着可以出现没有明确客户或商品的购买记录。

如果是真正的商城业务,通常还需要考虑:

  • 商品价格不能为负数。
  • 购买数量必须大于 0。
  • 订单必须关联有效客户和商品。
  • 客户和商品编号必须唯一。
  • 邮箱格式需要校验。
  • 订单需要记录创建时间和业务状态。

在 MySQL 5.7 中,不能依靠声明 CHECK 来有效强制执行这些规则,因为该版本不真正执行 CHECK 约束。

因此,需要结合字段类型、NOT NULL、UNSIGNED、唯一键、外键以及应用程序业务校验等方式设计完整的数据约束。

例如,购买数量可以先声明为:

sql 复制代码
nums INT UNSIGNED NOT NULL

这可以阻止保存负数和 NULL,但仍不能阻止保存 0。

真正的业务规则需要进一步设计。


十二、常见问题总结

12.1 为什么 NOT NULL 仍然可以插入空字符串?

因为:

sql 复制代码
NULL

和:

sql 复制代码
''

是不同的值。

NOT NULL 禁止的是 SQL 的 NULL,而不是空字符串。

12.2 为什么设置 DEFAULT 后,插入 NULL 没有使用默认值?

因为默认值通常在字段被省略或显式使用 DEFAULT 关键字时生效。

例如:

sql 复制代码
INSERT INTO default_demo(name)
VALUES ('张三');

会使用字段默认值。

但是:

sql 复制代码
INSERT INTO default_demo(name, age)
VALUES ('张三', NULL);

如果 age 允许 NULL,就会存储 NULL。

12.3 PRIMARY KEY 与 UNIQUE 有什么区别?

PRIMARY KEY 同时要求唯一和非空。

UNIQUE 要求唯一,但在 MySQL 5.7 中,如果字段允许 NULL,可以包含多个 NULL。

另外,一张表最多只有一个主键,但可以有多个唯一约束。

12.4 AUTO_INCREMENT 能保证编号连续吗?

不能。

它负责生成自增长编号,但由于事务回滚、删除、并发操作等原因,编号中可能出现空缺。

不要将其作为必须连续的业务流水号。

12.5 复合主键是否要求每个字段都不重复?

不是。

例如:

sql 复制代码
PRIMARY KEY(student_id, course)

要求的是两个字段组成的组合不重复。

不同记录可以具有相同的 student_id,只要 course 不同即可。

12.6 为什么插入外键值时报错?

如果出现:

text 复制代码
ERROR 1452 (23000)

通常说明子表正在插入或更新一个无法在父表中找到匹配记录的外键值。

应该先确认父表中对应的记录确实存在。

12.7 为什么删除父表记录时会报错?

如果某条父表记录正在被子表引用,InnoDB 外键约束可能阻止删除。

常见错误是:

text 复制代码
ERROR 1451 (23000)

表示无法删除或修改正在被引用的父表记录。

12.8 PRIMARY KEY 和索引有什么关系?

在 MySQL 中,定义主键时会创建相应的主键索引。

对于 InnoDB,主键通常还对应聚簇索引的组织方式。

因此,主键不仅用于保证唯一性,也与数据存储和查询性能有关。

这一部分将在后续索引章节详细学习。


十三、常用 SQL 约束速查表

SQL 语法 作用
NOT NULL 禁止字段存储 NULL
NULL 允许字段存储 NULL
DEFAULT value 设置字段默认值
COMMENT '说明' 添加字段注释
ZEROFILL 设置整数零填充显示
PRIMARY KEY 创建主键
PRIMARY KEY(a,b) 创建复合主键
AUTO_INCREMENT 自动生成递增编号
UNIQUE KEY 创建唯一约束
FOREIGN KEY 创建外键
REFERENCES 指定外键引用目标
ALTER TABLE ... ADD PRIMARY KEY 添加主键
ALTER TABLE ... DROP PRIMARY KEY 删除主键
LAST_INSERT_ID() 获取当前连接最近生成的自增长编号

十四、总结

本篇主要围绕 MySQL 的数据完整性约束展开。

通过这些约束,我们可以从数据库层面保证部分业务规则得到执行。

第一,空值与默认值。

  • NOT NULL 用于禁止字段保存 NULL。
  • DEFAULT 用于在适当情况下自动填充默认值。
  • COMMENT 用于提高字段定义的可读性。

第二,主键与自增长。

  • PRIMARY KEY 用于唯一标识记录。
  • 复合主键通过多个字段的组合实现唯一性。
  • AUTO_INCREMENT 用于自动生成编号。

第三,唯一键。

  • UNIQUE 用于防止业务字段重复。
  • PRIMARY KEY 与 UNIQUE 都能够限制重复值,但它们的非空要求和使用场景不同。

第四,外键。

  • FOREIGN KEY 用于建立表之间的引用关系。
  • 外键可以防止部分无效关联数据进入数据库。
  • 父表与子表之间的删除、更新行为也可能受到外键约束影响。

从后端开发的角度看,数据库约束的意义并不只是让 SQL 报错,而是帮助系统维护数据正确性。

例如:

sql 复制代码
CREATE TABLE user_account (
    id BIGINT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(32) NOT NULL UNIQUE,
    email VARCHAR(64) UNIQUE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

这张表已经具备一些基础的数据约束:

  • id 自动生成,并且唯一。
  • username 必须提供,且不能重复。
  • email 可以为空,但非 NULL 的邮箱不能重复。
  • created_at 默认记录创建时间。
相关推荐
Alluxio1 小时前
Alluxio + OCI:为 AI 工作负载实现亚毫秒访问延迟与可扩展吞吐
运维·数据库·人工智能·分布式·缓存·ai·语言模型
StarsGreen2 小时前
libva在intel平台上的前端流程简介
linux
蒸蒸yyyyzwd5 小时前
从后端面经学到的 3 个知识
笔记·学习
17岁的勇气5 小时前
Unity Shader unity文档学习笔记(二十二):shader变体
笔记·学习·unity
数据库急诊室_阿哲5 小时前
一次加索引建议的复核记录:从报告到签字
数据库·ai数据库
坤坤子吖5 小时前
Linux网络编程:自定义应用层协议与 JSON 序列化
linux·服务器·网络·c++·笔记·json
青瓦梦滋5 小时前
Redis主从复制
服务器·数据库·redis·缓存·bootstrap
慧都小妮子6 小时前
Wyn商业智能国产数据库适配:数据接入、分析缓存与审计日志验证清单
数据库·数据分析·数据可视化·审计日志·商业智能
沫璃染墨6 小时前
从零入门计算机网络系列(十八):TCP连接管理详解——从三次握手与四次挥手到TIME_WAIT机制
运维·服务器·网络·c++·网络协议·tcp/ip·tcp