【MySQL】表的约束

文章目录

  • 前言
  • [1. 默认值约束](#1. 默认值约束)
    • [1.1 default null 约束](#1.1 default null 约束)
    • [1.2 default 约束](#1.2 default 约束)
  • [2. 空属性约束](#2. 空属性约束)
    • [2.1 not null 约束](#2.1 not null 约束)
    • [2.2 如何理解NULL](#2.2 如何理解NULL)
    • [补充知识1:not null 约束和 default 约束](#补充知识1:not null 约束和 default 约束)
  • [3. 列描述属性](#3. 列描述属性)
  • [4. 零填充属性](#4. 零填充属性)
    • [4.1 zerofill](#4.1 zerofill)
  • [5. 主键约束](#5. 主键约束)
    • [5.1 primary key 约束](#5.1 primary key 约束)
    • [5.2 添加主键的方式](#5.2 添加主键的方式)
    • [5.3 复合主键](#5.3 复合主键)
  • [6. 自增长属性](#6. 自增长属性)
  • 写到这里,未完待续。
  • [7. 唯一键约束](#7. 唯一键约束)

前言

前面我们学习了列名,类型的相关知识,那查询表结构时,后面的这些是什么呢?

答:表的约束。

什么是表的约束?

在讲数据类型时,我们也强调过,表的数据类型其实是一种约束,但是这种约束比较单一,所以我们还需要一些额外的约束来保证数据的合法性。从业务逻辑角度保证数据的正确性。


表中一定要有各种类型的约束,通过约束,让我们未来插入数据库中的数据是符合预期的。
约束的本质是通过技术手段,倒逼程序员插入正确的数据。

反之,站在mysql 的视角,凡是插入进数据库的数据,都是符合数据约束的。

约束的最终目标:保证数据的完整性和可预期性。

为什么数据库要搞得这么严格呢,因为数据库是维护用户数据的最后一道防线。


mysql 就相当于在你进行数据插入时,出现问题,它要帮你把问题暴露出来。

类似的场景还有写代码时,编译器会对你输入的代码进行约束,一旦发现语法错误,编译器会进行报错。倒逼程序员把代码写对,其实这也是一种约束。尽管约束不能避免所有的问题,但是能保证在语法层面一定没有问题。

1. 默认值约束

1.1 default null 约束

在 MySQL 中,列的默认行为是允许为 NULL(即默认没有NOT NULL约束),如果创建表时没有明确指定NOT NULL,且没有设置其他默认值(DEFAULT),那么插入记录时若不填写该列的值,MySQL 会自动为其填充NULL。
在建表时,如果一个列没有default约束,mysql会对语句进行优化,默认带上DEFAULT NULL约束。

可以发现,创建表时,并没有加default null约束,查看表配置时被自动添加了default null约束

1.2 default 约束

default 约束,指的是:

  1. 用户插入数据时,忽略被default约束的列,则默认插入default设置的数据。
    如果某一列。
  2. 用户插入数据时,忽略被not null约束的列,该列不允许为空,并且没有设置默认值,则default约束不允许用户插入该行数据。
    (因为该列不允许为空,并且没设置default约束,而被default约束禁止插入数据)

2. 空属性约束

2.1 not null 约束

not null 约束,指的是:

  1. 用户插入数据时,对被not null约束的列 插入null ,则约束用户不允许插入该行数据。
  2. 用户插入数据时,忽略被not null约束的列,该列不允许为空,并且没有设置默认值,则default约束不允许用户插入该行数据。
    (因为该列不允许为空,并且没设置default约束,而被default约束禁止插入数据
    这里很容易误以为这种情况属于not null约束,但实际上这种情况属于default约束,不属于not null约束。

在实际应用中not null的应用场景:

例如,某网页的注册页中的用户名字段要求不能为空,需要用户手动输入用户名,如果没输入,特定字段的方框会变红。

2.2 如何理解NULL

MySQL 官方对 NULL 的定义是:"表示缺失的未知值"(a missing unknown value)

NULL 是一种特殊的数据状态,属于 "数据" 的范畴,但它不是一个具体的 "值"。

NULL 不参与运算

在mysql 中,一行数据里的每一个列都是一定有内容的:

区分:NULL,没有数据,空字符串

  1. NULL 是指有数据,只不过数据是NULL;
  2. 没有数据 是指什么都没有,连NULL值都没有。
  3. '' 是指有数据,数据是长度为 0 的空字符串;

假设有一张student表,结构为:id(学号)、name(姓名)、age(年龄)。
举例:NULL

对于age列(存在的属性):如果某条记录的age值是NULL,这就是 "空属性"(该属性存在,但值为空)。

举例:没有数据

对于height列"(不存在的属性):因为表中根本没有height列,所以 "height列的数据" 就是 "没有数据"。

对于age列(存在的属性):如果某条记录的age值是什么都没有,连null都没有,就是 "没有数据"。但是事实上数据库中不会存在一条某列"没有数据"的记录。

因为插入数据时:
(1)如果某列没有约束,MySQL会自动加上NULL约束。
(2)如果某列有not null约束 或 default约束,那该列一定有数据。

举例:空字符串

对于name列(存在的属性):如果某条记录的name值是'',虽然查询表中数据时,看上去什么都没有,但是实际上有内容,内容为空字符串。

维度 ​ 空值(NULL) ​ 没有数据 ​ 空字符串('')
数据载体 (列 / 记录)​存在(列已定义,记录存在)​ 不存在(列未定义或记录不存在)​ 存在(列已定义,记录存在)​
值的状态​ 未知 / 缺失(无明确值)​ 无值(连值的概念都没有)​ 明确的空内容(有具体值)​
MySQL 判断方式​ col IS NULL​ 无法判断(载体不存在)​ col = ''​
典型场景​ 未填写邮箱(没提供)​ 表中没有 "手机号" 列​ 明确填了空邮箱(主动留空)​

三者的核心区分逻辑可简化为两步:

先看 "有没有数据载体":有载体(列 / 记录存在)→ 排除 "没有数据";无载体→ 就是 "没有数据"。

有载体时,再看 "值是否明确":值未知 / 缺失→ 空值(NULL);值是明确的空字符串→ 空字符串('')。

补充知识1:not null 约束和 default 约束

有时候,某一列可能会同时设置 not nulldefault

有人可能会想,如果某列已经设置了 default ,那不就说明这列如果被用户忽略了,那就插入默认值,也就不会导致该列为空,那设置 not null 有什么意义呢?

答:有可能有些用户会主动对某一列插入 null

default 和 not null并不冲突,而是互相补充的

下面我来对这两个约束进行简单的讲解:

对某一列设置了 not null 就是当用户想插入数据,不能插入null

对某一列设置了 default 就是当用户不想插入数据,插入默认值

对某一列设置了 not null 和 default 就是用户,既可以选择不插入数据,插入默认值,也可以选择插入数据,但是插入数据的时候,不能插入null

这里还有一点需要注意:(设置了 not null 后就必须对该列进行有值插入,不能忽略)

如果我们没有明确指定一列要插入,用的是default,如果建表中,对应列 默认没有设置default值,无法直接插入。

虽然语法上支持not null 和 default同时设置,但是not null 和 default 一般不需要同时出现,因为default 本身有默认值,不会为空。

如果一个列既设置了default 又设置了not null。

not null 约束的是用户主动插入数据的情况,主动插入数据无非就是两种情况:

  1. 插入null,报错
  2. 插入合法数据,可以插入

default 约束的是用户忽略这一列的情况,

  1. 已经设置了默认值,自动插入默认值
  2. 没有设置默认值,报错

3. 列描述属性

列描述:comment,没有实际含义,专门用来描述字段,会根据表创建语句保存,用来给程序员或DBA(Database Administrator 数据库管理人员)来进行了解。

其实就相当于代码中的注释。

那这种描述字段也算约束吗?约束谁了?

其实这种描述字段算是一种软性的约束,描述字段可以被程序员或数据库管理人员看到,然后软性的告诉程序员这个字段是什么用途,从而使程序员开发特定的功能,达到约束的目的。而不是一种强制的硬性约束。

通过desc查不到描述信息,需要通过show create table 表名;查询约束信息。

4. 零填充属性

4.1 zerofill

作用:显示宽度小于设定的宽度,自动填充0,将位数补至设定的宽度。

刚开始学习数据库时,很多人对整数类型后面括号里的内容很迷茫。不过前面我们已经讲过了。

在 MySQL 中,tinyint、int 等整数类型后面括号里的数字(如 tinyint(3)、int(10))主要表示 "显示宽度",不使用 ZEROFILL 时,显示宽度几乎无意义。

创建一张包含int 和 无符号int类型的表。

可以发现,创建时并没有带括号,查询表结构时,mysql 自动加上了括号

插入一条数据,可以发现只显示了3位,因为插入的数字只需要3位,并且没有使用zerofill。验证了:不使用 ZEROFILL 时,显示宽度几乎无意义。

如果字段没有设置 ZEROFILL,括号里的数字(显示宽度)不会对查询结果的显示产生任何影响,MySQL 会直接按数值的实际位数显示。

sql 复制代码
#我们尝试给列加上zerofill属性。
alter table testzerofill change a a int zerofill;

可以发现,当给int加上zerofill属性后,自动变成了无符号类型。

在 MySQL 中,当给整型字段(如int、tinyint、bigint等)添加ZEROFILL(零填充)属性时,该字段会被自动隐式转换为无符号类型(UNSIGNED),这是 MySQL 的固定行为。

再次查询数据,我们发现,a列的数据被0填充补满了10位。

这里需要注意的是,这只是显示后的结果,mysql中存储的数据仍然是100,我们可以验证。

还可以用hex函数(按16进制显示)验证一下

6 × 161 + 4 × 160 = 6×16 + 4×1 = 96 + 4 = 100

HEX(a) 显示为 64 时,a 的实际数值(十进制)是 100。

下面我们修改一下a列的显示宽度。

插入两条数据,可以发现,

当a列数据宽度 < 4时,用0填充位数至4,

当a列数据宽度 > 4时,就不管宽度,超过的也正常显示。
添加zerofill属性后,只是把不足位数的数据用0填充。
超过位数后依然能插入,该怎样就怎样。

也就是说给int(4)类型的列添加上zerofill属性后,是一种至少的行为,确保该列至少宽度为4,超出范围的不做限制。

5. 主键约束

5.1 primary key 约束

主键:primary key用来唯一的约束该字段里面的数据,不能重复,不能为空,一张表中最多只能有一个主键;设置主键后会自动加上not null约束。

主键所在的列通常是整数类型

在数据库中,为了方便增删改查,一般在建表时,都要设置一个主键。

创建一个class表,给id列设置主键约束。

还可以发现,设置主键约束的列,被自动加上了非空约束。

查询一下表的创建信息,也可以发现上述发生的事情。

插入数据试试

由于id被设置了主键约束,所以学号相同时,不允许插入数据(主键列不允许有相同的数据)。

站在mysql角度思考,被主键约束的列,凡是被插入的数据,一定都是不同的。

这有什么意义呢?将来我查询数据时,查询id为2的数据,只可能查出一条唯一的数据。

也可以对唯一的数据进行修改,例如:
update class set name ='l3bo' where id=2;

5.2 添加主键的方式

(1)创建表时添加主键

在 MySQL 中,创建表时为列添加主键(列级主键约束)时,PRIMARY KEY 关键字必须放在 "数据类型" 之后,顺序是固定的,不能颠倒。

sql 复制代码
#法一
create table class(
id int unsigned primary key,
name varchar(20) not null
);
#法二
create table class(
id int unsigned primary key,
name varchar(20) not null,
primary key(id) -- 也可以把主键约束加在最后面
);

(2)创建完表后添加主键
alter table 表名 add primary key(列名);

可以发现成功添加主键约束。

这里需要注意,如果创建表后已经使用了一段时间。就有添加失败的风险。

把刚刚的newclass表删除重新创建,这次我们插入一些数据以后再添加主键。

插入一些数据

可以发现这里被拒绝添加主键约束,原因是id列中的数据有冲突。看报错信息是id=2的数据。

所以要设置主键,尽量在表刚刚创建好时,正式使用之前,就把主键设置好。

否则等表用了一段时间后,可能会存在冲突的情况,需要修改或删除冲突数据才能添加主键,这样对用户是不友好的。删除谁或者修改谁都会不开心,所以尽量在一开始就避免这种情况的发生。

现在我们想添加主键就只好将l3boid修改成3,就可以添加主键了。
update newclass set id = 3 where name = 'l3bo';

然后再添加主键就可以成功了。

删除主键约束:alter table newclass drop primary key;

这时没有主键约束了

5.3 复合主键

前面我们说一个表最多只能有一个主键,但并不意味着主键只能添加给一列。

一个主键可以添加到一列或者多列。

给多列添加主键就叫做复合主键。

例如:学生选课的场景

sql 复制代码
#创建一张pick_course表
create table pick_course(
id int unsigned comment'学生学号',
course_id int unsigned comment'课程编号',
score tinyint unsigned comment'课程得分',
primary key(id, course_id)
);

可以看到这里我们成功将id 和 course_id 都设置成了主键

刚刚不是还说一张表只能有一个主键嘛,怎么现在有"两个主键"了?

这里的"两个主键"并不是真的两个主键,而是id列course_id列共同构成一个主键。

实际这里只有一个主键。

下面我们插入一些数据,可以看出,只有在id列course_id这个组合完全相同时,mysql才会报错,提示有数据重复。

总结,主键可以确定该列数据的唯一性,通过设置主键也可以很容易的对数据做出增删改查的操作。

6. 自增长属性

auto_increment:当对应的字段,不给值,会自动的被系统触发,系统会从当前字段中已经有的最大值+1操作,得到一个新的不同的值。通常和主键搭配使用,作为逻辑主键。

自增长的特点:

  • 自增长字段必须是整数
  • 一张表最多只能有一个自增长
  • 任何一个字段要做自增长,前提是本身是一个索引(key一栏有值)

创建一张表,设置自增长属性。

自增长属性(AUTO_INCREMENT)不一定要搭配 INT UNSIGNED(无符号整数),但强烈推荐使用无符号类型,核心原因是 "充分利用取值范围"。

自增长的核心是生成 "唯一、非负、递增" 的数值(默认从 1 开始,且不会自动生成负数),而无符号类型(UNSIGNED)恰好能完美匹配这个需求

sql 复制代码
create table student(
id int unsigned primary key auto_increment,
name varchar(20) not null
);

插入一些数据,由于id列我们已经设置了auto_increment属性,所以我们可以让他自动生成。

sql 复制代码
insert into student (name) values ('l1bo');
insert into student (name) values ('l2bo');

默认从1开始自增。

插入数据时,手动给auto_increment列输入数据,下次插入从现有的最大值开始继续增长。

sql 复制代码
insert into student (id, name) values (5, 'l5bo');

我们也可以主动给一个小于目前最大的id值 的id值,只要给的id值不是重复的id值,都可以插入成功。

mysql凭什么知道auto_increment应该从哪里开始插入呢?

答:mysql在表的配置信息中存储了auto_increment下次的起始位置。

在创建表时,就给出auto_increment值,然后再插入数据

可以发现第一条数据的id自动就从100开始了。

尝试给没有主键约束的列添加auto_increment属性

sql 复制代码
create table test_auto_(
id int unsigned auto_increment,
name varchar(20) not null
);

可以发现是不可行的,要想给一列添加auto_increment属性,要先确保目标列的唯一性。

在表中没有数据时,我想尝试插入一条id为0的数据,看会发生什么?

  1. 默认情况(未开启 NO_AUTO_VALUE_ON_ZERO 模式):插入 0 会触发自增长,生成下一个自增值
    MySQL 会将 0 视为 "未指定值",自动忽略手动插入的 0,转而使用 AUTO_INCREMENT 机制生成下一个递增的数值(和 "不指定 id 字段" 的效果一致)。
  2. 若开启 NO_AUTO_VALUE_ON_ZERO 模式:插入 0 会实际存储 0
    NO_AUTO_VALUE_ON_ZERO 是 MySQL 的一个 SQL 模式,开启后会改变 AUTO_INCREMENT 对 0 的处理逻辑:
    此时插入 0 会被视为 "有效数值",直接存储 0(不再触发自增长);
    但需确保 0 未被使用过(否则会因主键唯一性约束报错)

总结

默认情况:插入 0 会触发自增长,实际存储下一个自增值(0 被忽略);

开启 NO_AUTO_VALUE_ON_ZERO:插入 0 会实际存储 0(需保证 0 未被使用)。

日常开发中几乎不会开启 NO_AUTO_VALUE_ON_ZERO,因此插入 0 通常等价于 "让 MySQL 自动生成 ID"。

获取现有数据的最后一个auto_increment值

sql 复制代码
#用于查询当前会话(当前数据库连接)中,最近一次成功执行的 INSERT 语句所生成的 AUTO_INCREMENT 字段值。
select last_insert_id();

我最新插入的数据的表是newstu,所以这里用这个函数查询到的值就是newstu表中的。

不信的话我再给student表重新插入一次,然后再查一次。[旺柴],可以看到确实是9。

简单了解下索引:

在关系数据库中,索引是一种单独的、物理的对数据库表中一列或多列的值进行排序的一种存储结构,它是某个表中一列或若干列值的集合和相应的指向表中物理标识这些值的数据页的逻辑指针清单。

索引的作用相当于图书的目录,可以根据目录中的页码快速找到所需的内容。

索引提供指向存储在表的指定列中的数据值的指针,然后根据您指定的排序顺序对这些指针排序。

数据库使用索引以找到特定值,然后顺指针找到包含该值的行。这样可以使对应于表的SQL语句执行得更快,可快速访问数据库表中的特定信息。

索引就好比一本书的目录。假设一本书有 1000 页,你想找到第 6 章的内容:如果没有目录,你只能逐页线性遍历,从第 1 页翻到第 1000 页直到找到目标章节,这个过程会耗费大量时间,查找效率极低;但如果有目录(比如占 20 页),你只需先翻到目录页,快速定位到 "第 6 章 → 对应第 666 页" 的映射关系,再直接翻到第 666 页,瞬间就能找到目标内容 ------ 这就是目录的价值:用 "快速定位" 替代 "逐页查找",大幅提升效率。

当然,目录也有代价:它多占用了 20 页纸来存储 "章节名 - 页码" 的映射关系,而这 20 页本可以用来承载更多正文内容。

这个逻辑在数据库中完全适用:索引就是数据库的 "目录"。它会单独占用一部分存储空间(对应目录的 20 页纸),但能帮数据库跳过 "逐行扫描全表" 的低效操作,直接通过 "索引值 - 数据位置" 的映射找到目标数据,本质都是 "用空间换时间",在存储成本和查询效率之间做平衡。

写到这里,未完待续。

7. 唯一键约束

5.1主键与唯一键

主键与唯一键并不冲突,而是互相补充的

主键保证的是记录的唯一性(指的是数据库中的某条记录在数据库中的唯一性),可以更简单的理解为,主键使得每一条记录都是独一无二的记录,只要主键不相同,那么就是一条独立的记录,一旦主键相同,那么就不允许插入此条记录

唯一键保证的是某一列中的数据不出现重复,比如某人的身份证号,手机号,我们期望插入数据库中的这两列数据不出现重复

这两种约束的行为类似,但是目的不同。

唯一键可以有不止一个,主键只能有一个

相关推荐
胖胖的战士4 小时前
Mysql 数据库迁移
数据库·mysql
星光一影4 小时前
大型酒店管理系统源码(多酒店版)
mysql·php
czhc11400756634 小时前
LINUX1012 mysql GLIBC安装
数据库·mysql
MC皮蛋侠客4 小时前
Ubuntu禁用系统手势,阻止应用程序异常最小化
linux·运维·qt·ubuntu
小草儿7994 小时前
gbase8s之.net8连接8s之mysql模式(windows)demo
windows·mysql·.net
颇有几分姿色4 小时前
Ubuntu 系统安装教程(二):系统安装
linux·运维·ubuntu
DemonAvenger4 小时前
深入 Redis Hash:从原理到实战,10 年经验的后端工程师带你玩转哈希结构
数据库·redis·性能优化
sakoba5 小时前
MySQL的json处理相关方法
android·学习·mysql·json
序属秋秋秋5 小时前
《Linux系统编程之入门基础》【Linux基础 理论+命令】(下)
linux·运维·服务器·学习·ubuntu·xshell·命令