MySQL 深度实践:表的约束及其在数据完整性中的作用

MySQL 相关知识点 可以通过点击 以下链接进行学习 一起加油!

@toc

在掌握了MySQL的数据类型之后,下一个核心议题便是表的约束(Constraints)。数据类型本身虽然为字段提供了基础的类型和范围限制,但这种约束力相对单一。为了从更贴近业务逻辑的层面保证数据的合法性、准确性和一致性,MySQL提供了一套丰富的表约束机制。这些约束如同数据库的"法律法规",确保所有写入的数据都符合预设的规则,从而极大地提升了数据的质量和可靠性。

表的约束:数据完整性的守护者

表的约束是施加在表列上的规则,用于限制可以存入表中的数据。它们通过技术手段,从数据库层面引导开发者和用户插入正确的数据,防止因误操作或程序逻辑漏洞导致的数据污染。常见的表约束包括:

  • NOT NULL:非空约束
  • DEFAULT:默认值约束
  • COMMENT:列注释
  • ZEROFILL:零填充属性
  • PRIMARY KEY:主键约束
  • AUTO_INCREMENT:自增属性
  • UNIQUE KEY:唯一键约束
  • FOREIGN KEY:外键约束

1. 空属性 (NULL / NOT NULL)

在数据库中,一个字段的值可以被定义为允许NULL或不允许NULLNULL是一个特殊的值,表示"未知"或"不存在",它不等同于数字0,也不等同于空字符串''

NULL vs '' 的本质区别

  • NULL:表示该字段没有任何值。在概念上,就像一个人没有办理过银行卡。
  • ''(空字符串):表示该字段有一个值,但这个值是空的。概念上,就像一个人有银行卡,但卡里余额为零。

这个区别在数据处理中至关重要,因为NULL值不能参与大多数算术运算和比较运算。任何与NULL进行的运算,结果仍然是NULL

sql 复制代码
mysql> select 1 + null;
+----------+
| 1 + null |
+----------+
| NULL     |
+----------+

这个简单的SQL查询展示了NULL的传播性。1加上一个未知的值,结果依然是未知的。

在实际的业务开发中,应尽可能地为字段设置NOT NULL约束。因为NULL值会给应用程序的逻辑带来额外的复杂性,需要频繁地进行IS NULLIS NOT NULL的判断,稍有疏忽就可能引发空指针异常或逻辑错误。

NOT NULL 约束实践

设想一个场景:创建一个班级表,包含班级名和教室位置。从业务逻辑上讲,一个班级必须有名字,也必须有上课的教室。如果这两项信息缺失,数据就是无效的。

*这个操作序列演示了NOT NULL约束的效果:

  1. 建表t1表的class_nameclass_room列都被定义为NOT NULL
  2. 插入失败(隐式NULL) :尝试只插入class_name,不为class_room提供值。MySQL报错Field 'class_room' doesn't have a default value,因为它既不能为空,又没有默认值,所以插入被拒绝。
  3. 插入失败(显式NULL) :尝试将class_room的值显式设置为NULL。MySQL报错Column 'class_room' cannot be null,直接指出了违反了非空约束。
  4. 插入成功 :为所有NOT NULL的列都提供了有效值,插入操作成功。*

NOT NULL约束是保证数据完整性的第一道防线。

2. 默认值 (DEFAULT)

DEFAULT约束用于为列指定一个默认值。当插入一条新记录但没有为该列提供值时,MySQL会自动使用这个默认值。

t2表的agesex列分别设置了默认值18'男'。当插入记录时只提供了nameagesex自动填充为默认值。当显式提供了agesex时,则使用提供的值。

DEFAULTNOT NULL 的共存

一个常见的疑问是:DEFAULTNOT NULL能否同时作用于一个列?如果可以,是否有意义?

创建了t3表,name列同时拥有NOT NULLDEFAULT 'unnamed'两个约束。

*测试插入:

  • 不提供name值:插入成功,name被自动设为默认值'unnamed'
  • 显式提供name'张三':插入成功。
  • 显式提供nameNULL:插入失败,因为NOT NULL约束禁止显式插入NULL。*

结论NOT NULLDEFAULT在语法上可以共存。它们的组合效果是:不允许显式插入NULL,但在不提供值时会自动填充默认值 。这种组合在某些场景下是有用的,它确保了字段永远有值,同时为开发者提供了便利。但在通常的设计中,如果一个字段有默认值,它本质上就不会为NULL,所以NOT NULL约束有时会显得冗余。

这个例子作为对照,当一个列没有任何约束时,如果不提供值,它默认为NULL

3. 列描述 (COMMENT)

COMMENT约束并不对数据本身施加任何限制,它的作用是为列添加一段描述性文本。这个注释会存储在数据库的元数据中,是为数据库管理员(DBA)和开发者准备的"文档"。

创建t4表时为name列添加了注释。使用desc t4命令时,Comment列是空的,无法看到注释。要查看注释,必须使用show create table t4;show full columns from t4;。这说明COMMENT是元数据的一部分,而不是简单的表结构摘要。

4. 零填充 (ZEROFILL)

ZEROFILL是一个作用于数值类型的显示属性,而不是一个真正的约束。它通常与INT(M)中的M(显示宽度)配合使用。

对于INT(10),这里的10在没有ZEROFILL的情况下,并没有实际的约束作用,INT依然是4个字节,其取值范围也不会改变。

ZEROFILL被启用时,M的意义就显现出来了:它指定了数值显示的最小宽度。如果实际数值的位数小于M,MySQL会在左边用0来填充,直到达到M的宽度。

创建了t6表,num列是INT(5) ZEROFILL。插入1后,查询结果显示为00001。这正是ZEROFILL的效果。

重要特性

  • 仅影响显示ZEROFILL只改变数据的显示格式,数据库中实际存储的仍然是原始数值。
  • 自动UNSIGNED :当为一个列指定ZEROFILL时,MySQL会自动为该列添加UNSIGNED属性。

show create table t6;的结果清晰地显示,num列的类型被MySQL解释为int(5) unsigned zerofill。这印证了ZEROFILL会自动附加UNSIGNED

关于INT的默认显示宽度 默认情况下,一个有符号INT的显示宽度是11,而无符号INT是10。这是因为有符号INT的最大值是2147483647(10位),最小值是-2147483648(10位加一个负号,共11个字符位)。无符号INT最大值4294967295是10位。

5. 主键 (PRIMARY KEY)

主键是表中用于唯一标识每一行记录的一列或一组列。它是数据库中最重要的约束之一。

主键的特性

  • 唯一性(Uniqueness):主键列中的每个值都必须是唯一的。
  • 非空性(Not Null) :主键列不允许有NULL值。
  • 唯一索引:MySQL会自动在主键列上创建一个唯一的索引,以加速数据检索。
  • 单一性:每张表只能有一个主键。

主键的创建与作用

方法1:在列定义中指定 创建t12表时,在id列后直接跟上PRIMARY KEYdesc t12;的结果显示,id列的Null字段为NOKey字段为PRI(Primary)。这表明MySQL在设置主键时,自动强制了非空约束。

主键的唯一性约束效果 第一次插入id=1成功。第二次尝试插入id=1时,MySQL报错Duplicate entry '1' for key 'PRIMARY',明确指出了主键冲突,插入被拒绝。

方法2:在表定义末尾指定

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

方法3:为已存在的表添加主键

sql 复制代码
ALTER TABLE table_name ADD PRIMARY KEY(column_name);

删除主键

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

删除主键后,desc结果显示Key字段变为空,但Null字段依然是NO,说明删除主键不会移除原有的非空约束。

复合主键

一个主键可以由多个列组成,这称为复合主键。复合主键的唯一性体现在所有主键列的组合值 必须是唯一的。 创建tt14表,primary key(id, course)定义了idcourse为复合主键。desc结果显示这两列的Key都是PRI

  • insert into tt14 (id,course)values(1, '123'); 成功。
  • insert into tt14 (id,course)values(1, '456'); 成功,因为(1, '456')(1, '123')不同。
  • insert into tt14 (id,course)values(1, '123'); 失败,因为组合值(1, '123')重复。*

复合主键常用于关系表或需要通过多个维度来唯一确定一条记录的场景。

6. 自增 (AUTO_INCREMENT)

AUTO_INCREMENT属性通常与主键配合使用,创建一个"代理主键"或"逻辑主键"。当插入新记录时不为该列提供值,MySQL会自动为其生成一个唯一的、递增的整数。

自增的特点

  1. 基于索引:一个列要成为自增列,它必须是一个索引(通常是主键或唯一键)。
  2. 整数类型:自增列必须是整数类型。
  3. 表内唯一:一张表最多只能有一个自增列。

*t15表的idPRIMARY KEY AUTO_INCREMENT

  • 第一次插入不提供idid自动变为1
  • 第二次插入,id自动变为2
  • 第三次显式插入id=5,插入成功。
  • 第四次插入不提供id,MySQL会找到当前id的最大值5,然后加1,所以新id6。*

控制AUTO_INCREMENT的起始值

可以在创建表时或之后,通过AUTO_INCREMENT = value来设置下一个自增值的起点。

创建t16表时指定AUTO_INCREMENT=100。第一次插入后,id的值就是100

获取最后插入的自增ID

在程序中,插入一条记录后,往往需要立即获取其生成的自增ID,以便进行后续操作。这可以通过SELECT 0;函数实现。

插入一条记录到t15后,立即执行SELECT 0;,返回了刚才生成的id7。这个函数返回的是当前连接上一次INSERT操作生成的第一个AUTO_INCREMENT值。

7. 唯一键 (UNIQUE KEY)

当一张表中,除了主键之外,还有其他列也需要保证其值的唯一性时,就需要使用唯一键。

唯一键与主键的异同

  • 相同点:都强制唯一性,都会自动创建索引。
  • 不同点
    1. 数量:一张表只能有一个主键,但可以有多个唯一键。
    2. NULL值 :主键不允许NULL。唯一键允许NULL,并且可以有多个NULL值(因为NULL不等于任何值,包括另一个NULL)。

*t17表的id是主键,phone_num是唯一键。

  • 插入phone_num110的记录成功。
  • 再次插入110失败,违反唯一键约束。
  • 插入phone_numNULL的记录成功。
  • 再次插入phone_numNULL的记录依然成功。这证明了唯一键对NULL值的特殊处理。*

主键与唯一键的选择哲学

  • 主键 :其核心目的是标识行 。它应该是稳定、不变、且与具体业务逻辑无关的。因此,使用AUTO_INCREMENT的整数作为主键是最佳实践。
  • 唯一键 :其核心目的是保证业务数据的唯一性。例如,用户表中的用户名、邮箱、手机号等,都应该是唯一键,因为它们在业务上是不允许重复的。

8. 外键 (FOREIGN KEY)

外键用于在两个表之间建立和强制实施一种关联关系,以保证引用完整性(Referential Integrity)。

  • 主表(父表):被引用的表(如班级表)。
  • 从表(子表):引用主表的表(如学生表)。
  • 外键约束 :定义在从表上,要求外键列的值必须存在于主表的主键或唯一键列中,或者为NULL

创建了classes(主表)和students(从表)。students表的class_id列通过FOREIGN KEY (class_id) REFERENCES classes(id)classes表的id主键建立了外键关系。desc students;的结果中,class_idKey字段为MUL,表示它是一个非唯一的索引,并且是外键的一部分。

外键约束的效果

*先向classes表插入id=1id=2的班级。然后向students表插入学生:

  • 插入class_id=1的学生成功,因为classes表中存在id=1的班级。
  • 插入class_id=3的学生失败,并报错a foreign key constraint fails,因为classes表中不存在id=3的班级。*

这里演示了外键对主表删除操作的约束。尝试删除classes表中id=1的班级失败了,因为students表中还有学生属于这个班级。这有效地防止了"孤儿数据"的产生。

外键是关系型数据库保证数据一致性的强大工具,但它也会带来一定的性能开销(每次对从表的写操作都需要检查主表)。在某些追求极致性能的互联网应用中,有时会选择在应用层逻辑中来保证引用完整性,而不在数据库层面设置物理外键。

综合案例:商城数据库设计

通过一个综合案例,可以将前面学到的所有约束知识融会贯通。

业务需求

  • 商品表(goods):商品编号、名称、单价、类别、供应商。
  • 客户表(customer):客户号、姓名、地址、邮箱、性别、身份证号。
  • 购买表(purchase):订单号、客户号、商品号、数量。

约束要求

  1. 确定各表的主键和外键。
  2. 客户姓名不能为空。
  3. 邮箱和身份证号不能重复。
  4. 性别只能是"男"或"女"。

建表SQL脚本

sql 复制代码
-- 创建数据库
CREATE DATABASE IF NOT EXISTS mall DEFAULT CHARACTER SET utf8;
USE mall;

-- 商品表
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 '供应商名称'
);

-- 客户表
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 '身份证'
);

-- 购买表 (关系表)
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)
);

设计分析

  • goodscustomer表都使用了INT PRIMARY KEY AUTO_INCREMENT作为无业务含义的代理主键。
  • goods_nameunitpriceprovidercustomer.name都使用了NOT NULL约束。
  • customer.emailcustomer.card_id使用了UNIQUE KEY来保证业务唯一性。
  • customer.sex使用了ENUM类型来限制取值。
  • purchase表是连接客户和商品的关系表,其customer_idgoods_id列分别作为外键,引用了customer表和goods表的主键,从而建立了数据间的关联,保证了引用完整性。

通过这个案例,可以看到表的约束是如何将抽象的业务规则转化为具体的数据库结构,为构建一个健壮、可靠的数据系统奠定了坚实的基础。

MySQL 相关知识点 可以通过点击 以下链接进行学习 一起加油!

@toc

在掌握了MySQL的数据类型之后,下一个核心议题便是表的约束(Constraints)。数据类型本身虽然为字段提供了基础的类型和范围限制,但这种约束力相对单一。为了从更贴近业务逻辑的层面保证数据的合法性、准确性和一致性,MySQL提供了一套丰富的表约束机制。这些约束如同数据库的"法律法规",确保所有写入的数据都符合预设的规则,从而极大地提升了数据的质量和可靠性。

表的约束:数据完整性的守护者

表的约束是施加在表列上的规则,用于限制可以存入表中的数据。它们通过技术手段,从数据库层面引导开发者和用户插入正确的数据,防止因误操作或程序逻辑漏洞导致的数据污染。常见的表约束包括:

  • NOT NULL:非空约束
  • DEFAULT:默认值约束
  • COMMENT:列注释
  • ZEROFILL:零填充属性
  • PRIMARY KEY:主键约束
  • AUTO_INCREMENT:自增属性
  • UNIQUE KEY:唯一键约束
  • FOREIGN KEY:外键约束

1. 空属性 (NULL / NOT NULL)

在数据库中,一个字段的值可以被定义为允许NULL或不允许NULLNULL是一个特殊的值,表示"未知"或"不存在",它不等同于数字0,也不等同于空字符串''

NULL vs '' 的本质区别

  • NULL:表示该字段没有任何值。在概念上,就像一个人没有办理过银行卡。
  • ''(空字符串):表示该字段有一个值,但这个值是空的。概念上,就像一个人有银行卡,但卡里余额为零。

这个区别在数据处理中至关重要,因为NULL值不能参与大多数算术运算和比较运算。任何与NULL进行的运算,结果仍然是NULL

sql 复制代码
mysql> select 1 + null;
+----------+
| 1 + null |
+----------+
| NULL     |
+----------+

这个简单的SQL查询展示了NULL的传播性。1加上一个未知的值,结果依然是未知的。

在实际的业务开发中,应尽可能地为字段设置NOT NULL约束。因为NULL值会给应用程序的逻辑带来额外的复杂性,需要频繁地进行IS NULLIS NOT NULL的判断,稍有疏忽就可能引发空指针异常或逻辑错误。

NOT NULL 约束实践

设想一个场景:创建一个班级表,包含班级名和教室位置。从业务逻辑上讲,一个班级必须有名字,也必须有上课的教室。如果这两项信息缺失,数据就是无效的。

*这个操作序列演示了NOT NULL约束的效果:

  1. 建表t1表的class_nameclass_room列都被定义为NOT NULL
  2. 插入失败(隐式NULL) :尝试只插入class_name,不为class_room提供值。MySQL报错Field 'class_room' doesn't have a default value,因为它既不能为空,又没有默认值,所以插入被拒绝。
  3. 插入失败(显式NULL) :尝试将class_room的值显式设置为NULL。MySQL报错Column 'class_room' cannot be null,直接指出了违反了非空约束。
  4. 插入成功 :为所有NOT NULL的列都提供了有效值,插入操作成功。*

NOT NULL约束是保证数据完整性的第一道防线。

2. 默认值 (DEFAULT)

DEFAULT约束用于为列指定一个默认值。当插入一条新记录但没有为该列提供值时,MySQL会自动使用这个默认值。

t2表的agesex列分别设置了默认值18'男'。当插入记录时只提供了nameagesex自动填充为默认值。当显式提供了agesex时,则使用提供的值。

DEFAULTNOT NULL 的共存

一个常见的疑问是:DEFAULTNOT NULL能否同时作用于一个列?如果可以,是否有意义?

创建了t3表,name列同时拥有NOT NULLDEFAULT 'unnamed'两个约束。

*测试插入:

  • 不提供name值:插入成功,name被自动设为默认值'unnamed'
  • 显式提供name'张三':插入成功。
  • 显式提供nameNULL:插入失败,因为NOT NULL约束禁止显式插入NULL。*

结论NOT NULLDEFAULT在语法上可以共存。它们的组合效果是:不允许显式插入NULL,但在不提供值时会自动填充默认值 。这种组合在某些场景下是有用的,它确保了字段永远有值,同时为开发者提供了便利。但在通常的设计中,如果一个字段有默认值,它本质上就不会为NULL,所以NOT NULL约束有时会显得冗余。

这个例子作为对照,当一个列没有任何约束时,如果不提供值,它默认为NULL

3. 列描述 (COMMENT)

COMMENT约束并不对数据本身施加任何限制,它的作用是为列添加一段描述性文本。这个注释会存储在数据库的元数据中,是为数据库管理员(DBA)和开发者准备的"文档"。

创建t4表时为name列添加了注释。使用desc t4命令时,Comment列是空的,无法看到注释。要查看注释,必须使用show create table t4;show full columns from t4;。这说明COMMENT是元数据的一部分,而不是简单的表结构摘要。

4. 零填充 (ZEROFILL)

ZEROFILL是一个作用于数值类型的显示属性,而不是一个真正的约束。它通常与INT(M)中的M(显示宽度)配合使用。

对于INT(10),这里的10在没有ZEROFILL的情况下,并没有实际的约束作用,INT依然是4个字节,其取值范围也不会改变。

ZEROFILL被启用时,M的意义就显现出来了:它指定了数值显示的最小宽度。如果实际数值的位数小于M,MySQL会在左边用0来填充,直到达到M的宽度。

创建了t6表,num列是INT(5) ZEROFILL。插入1后,查询结果显示为00001。这正是ZEROFILL的效果。

重要特性

  • 仅影响显示ZEROFILL只改变数据的显示格式,数据库中实际存储的仍然是原始数值。
  • 自动UNSIGNED :当为一个列指定ZEROFILL时,MySQL会自动为该列添加UNSIGNED属性。

show create table t6;的结果清晰地显示,num列的类型被MySQL解释为int(5) unsigned zerofill。这印证了ZEROFILL会自动附加UNSIGNED

关于INT的默认显示宽度 默认情况下,一个有符号INT的显示宽度是11,而无符号INT是10。这是因为有符号INT的最大值是2147483647(10位),最小值是-2147483648(10位加一个负号,共11个字符位)。无符号INT最大值4294967295是10位。

5. 主键 (PRIMARY KEY)

主键是表中用于唯一标识每一行记录的一列或一组列。它是数据库中最重要的约束之一。

主键的特性

  • 唯一性(Uniqueness):主键列中的每个值都必须是唯一的。
  • 非空性(Not Null) :主键列不允许有NULL值。
  • 唯一索引:MySQL会自动在主键列上创建一个唯一的索引,以加速数据检索。
  • 单一性:每张表只能有一个主键。

主键的创建与作用

方法1:在列定义中指定 创建t12表时,在id列后直接跟上PRIMARY KEYdesc t12;的结果显示,id列的Null字段为NOKey字段为PRI(Primary)。这表明MySQL在设置主键时,自动强制了非空约束。

主键的唯一性约束效果 第一次插入id=1成功。第二次尝试插入id=1时,MySQL报错Duplicate entry '1' for key 'PRIMARY',明确指出了主键冲突,插入被拒绝。

方法2:在表定义末尾指定

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

方法3:为已存在的表添加主键

sql 复制代码
ALTER TABLE table_name ADD PRIMARY KEY(column_name);

删除主键

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

删除主键后,desc结果显示Key字段变为空,但Null字段依然是NO,说明删除主键不会移除原有的非空约束。

复合主键

一个主键可以由多个列组成,这称为复合主键。复合主键的唯一性体现在所有主键列的组合值 必须是唯一的。 创建tt14表,primary key(id, course)定义了idcourse为复合主键。desc结果显示这两列的Key都是PRI

  • insert into tt14 (id,course)values(1, '123'); 成功。
  • insert into tt14 (id,course)values(1, '456'); 成功,因为(1, '456')(1, '123')不同。
  • insert into tt14 (id,course)values(1, '123'); 失败,因为组合值(1, '123')重复。*

复合主键常用于关系表或需要通过多个维度来唯一确定一条记录的场景。

6. 自增 (AUTO_INCREMENT)

AUTO_INCREMENT属性通常与主键配合使用,创建一个"代理主键"或"逻辑主键"。当插入新记录时不为该列提供值,MySQL会自动为其生成一个唯一的、递增的整数。

自增的特点

  1. 基于索引:一个列要成为自增列,它必须是一个索引(通常是主键或唯一键)。
  2. 整数类型:自增列必须是整数类型。
  3. 表内唯一:一张表最多只能有一个自增列。

*t15表的idPRIMARY KEY AUTO_INCREMENT

  • 第一次插入不提供idid自动变为1
  • 第二次插入,id自动变为2
  • 第三次显式插入id=5,插入成功。
  • 第四次插入不提供id,MySQL会找到当前id的最大值5,然后加1,所以新id6。*

控制AUTO_INCREMENT的起始值

可以在创建表时或之后,通过AUTO_INCREMENT = value来设置下一个自增值的起点。

创建t16表时指定AUTO_INCREMENT=100。第一次插入后,id的值就是100

获取最后插入的自增ID

在程序中,插入一条记录后,往往需要立即获取其生成的自增ID,以便进行后续操作。这可以通过SELECT 0;函数实现。

插入一条记录到t15后,立即执行SELECT 0;,返回了刚才生成的id7。这个函数返回的是当前连接上一次INSERT操作生成的第一个AUTO_INCREMENT值。

7. 唯一键 (UNIQUE KEY)

当一张表中,除了主键之外,还有其他列也需要保证其值的唯一性时,就需要使用唯一键。

唯一键与主键的异同

  • 相同点:都强制唯一性,都会自动创建索引。
  • 不同点
    1. 数量:一张表只能有一个主键,但可以有多个唯一键。
    2. NULL值 :主键不允许NULL。唯一键允许NULL,并且可以有多个NULL值(因为NULL不等于任何值,包括另一个NULL)。

*t17表的id是主键,phone_num是唯一键。

  • 插入phone_num110的记录成功。
  • 再次插入110失败,违反唯一键约束。
  • 插入phone_numNULL的记录成功。
  • 再次插入phone_numNULL的记录依然成功。这证明了唯一键对NULL值的特殊处理。*

主键与唯一键的选择哲学

  • 主键 :其核心目的是标识行 。它应该是稳定、不变、且与具体业务逻辑无关的。因此,使用AUTO_INCREMENT的整数作为主键是最佳实践。
  • 唯一键 :其核心目的是保证业务数据的唯一性。例如,用户表中的用户名、邮箱、手机号等,都应该是唯一键,因为它们在业务上是不允许重复的。

8. 外键 (FOREIGN KEY)

外键用于在两个表之间建立和强制实施一种关联关系,以保证引用完整性(Referential Integrity)。

  • 主表(父表):被引用的表(如班级表)。
  • 从表(子表):引用主表的表(如学生表)。
  • 外键约束 :定义在从表上,要求外键列的值必须存在于主表的主键或唯一键列中,或者为NULL

创建了classes(主表)和students(从表)。students表的class_id列通过FOREIGN KEY (class_id) REFERENCES classes(id)classes表的id主键建立了外键关系。desc students;的结果中,class_idKey字段为MUL,表示它是一个非唯一的索引,并且是外键的一部分。

外键约束的效果

*先向classes表插入id=1id=2的班级。然后向students表插入学生:

  • 插入class_id=1的学生成功,因为classes表中存在id=1的班级。
  • 插入class_id=3的学生失败,并报错a foreign key constraint fails,因为classes表中不存在id=3的班级。*

这里演示了外键对主表删除操作的约束。尝试删除classes表中id=1的班级失败了,因为students表中还有学生属于这个班级。这有效地防止了"孤儿数据"的产生。

外键是关系型数据库保证数据一致性的强大工具,但它也会带来一定的性能开销(每次对从表的写操作都需要检查主表)。在某些追求极致性能的互联网应用中,有时会选择在应用层逻辑中来保证引用完整性,而不在数据库层面设置物理外键。

综合案例:商城数据库设计

通过一个综合案例,可以将前面学到的所有约束知识融会贯通。

业务需求

  • 商品表(goods):商品编号、名称、单价、类别、供应商。
  • 客户表(customer):客户号、姓名、地址、邮箱、性别、身份证号。
  • 购买表(purchase):订单号、客户号、商品号、数量。

约束要求

  1. 确定各表的主键和外键。
  2. 客户姓名不能为空。
  3. 邮箱和身份证号不能重复。
  4. 性别只能是"男"或"女"。

建表SQL脚本

sql 复制代码
-- 创建数据库
CREATE DATABASE IF NOT EXISTS mall DEFAULT CHARACTER SET utf8;
USE mall;

-- 商品表
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 '供应商名称'
);

-- 客户表
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 '身份证'
);

-- 购买表 (关系表)
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)
);

设计分析

  • goodscustomer表都使用了INT PRIMARY KEY AUTO_INCREMENT作为无业务含义的代理主键。
  • goods_nameunitpriceprovidercustomer.name都使用了NOT NULL约束。
  • customer.emailcustomer.card_id使用了UNIQUE KEY来保证业务唯一性。
  • customer.sex使用了ENUM类型来限制取值。
  • purchase表是连接客户和商品的关系表,其customer_idgoods_id列分别作为外键,引用了customer表和goods表的主键,从而建立了数据间的关联,保证了引用完整性。

通过这个案例,可以看到表的约束是如何将抽象的业务规则转化为具体的数据库结构,为构建一个健壮、可靠的数据系统奠定了坚实的基础。

相关推荐
红尘散仙7 小时前
我把终端小说阅读器接上了 AI Agent:TRNovel 现在能用 skill 生成书源了
人工智能·后端·rust
卷毛的技术笔记8 小时前
告别硬编码!Spring AI Alibaba 实现 AI Agent 智能工具调用(Tool Calling)
java·人工智能·后端·python·spring·ai编程
会编程的土豆8 小时前
Go 语言反射(Reflection)详解
开发语言·后端·golang
喵个咪9 小时前
GoWind Toolkit Go后端代码生成 完整全流程实战
后端·go·orm
basketball6169 小时前
Go 语言从入门到进阶:4. 数组和MAP使用方法总结
开发语言·后端·golang
qq_2518364579 小时前
SpringBoot+Vue 共享电池柜管理系统 完整实现 前后端分离项目实战 完整代码
vue.js·spring boot·后端
zhangxingchao9 小时前
AI 大模型核心六:量化、Workflow 与 Agent、多轮 RAG
前端·人工智能·后端
IT_陈寒11 小时前
Vite打包时遇到的坑,原来问题出在这里
前端·人工智能·后端
ayqy贾杰12 小时前
基层管理的三板斧,在AI时代行不通了
前端·后端·团队管理
Apifox12 小时前
Apifox 5 月更新|Postman 导入优化、Runner 支持非 root 运行、请求代码自动带鉴权
前端·后端·安全