1. 约束(constraint)概述

1.1 为什么需要约束

数据完整性是指数据的精确性和可靠性。它是防止数据库中存在不符合语义规定的数据和因错误信息的输入输出造成无效操作或错误信息而提出的。

为了保证数据的完整性,SQL规范以约束的方式对表数据进行额外的条件限制。从以下四个方面进行考虑:

  • 实体完整性(Entity Integrity):例如,同一个表中,不能存在两条完全相同无法区分的记录
  • 域完整性(Domain Integrity):例如:年龄范围0-120,性别范围“男/女”
  • 引用完整性(Referential Integrity):例如:员工所在部门,在部门表中要能找到这个部门
  • 用户自定义完整性(User-defined Integrity):例如:用户名唯一、密码不能为空等,本部门经理的工资不得高于本部门职工的平均工资的5倍。

1.2 什么是约束

约束是对表中字段的一个强制限制。
可以在创建表时规定约束(通过 CREATE TABLE 语句),或者在表创建之后通过 ALTER TABLE 语句规定约束

1.3 约束的分类

  • 按照约束的字段的个数
    • 单列约束:每个约束只约束一列
    • 多列约束:每个约束可以约束多列
  • 按照约束的作用范围
    • 列级约束:只能作用在一列上,跟在列的定义后面
    • 表级约束:可以作用在多列上,不和列定义在一起,单独进行定义
  • 按照约束的作用(功能)分类
    • 非空约束NOT NULL:规定某个字段不可以为空
    • 唯一性约束UNIQUE:规定某个字段在整张表中是唯一存在的
    • 主键约束PRIMARY KEY:主键(非空且唯一)约束
    • 外键约束FOREIGN KEY
    • 检查约束CHECK
    • 默认值约束DEFAULT

      注意:MySQL不支持check约束,虽然可以使用,但是不会有任何的作用效果。

如何查看表中约束

  1. #information_schema数据库名(系统库)
  2. #table_constraints表名称(专门存储各个表的约束)
  3. SELECT * FROM information_schema.table_constraints
  4. WHERE table_name = '表名称';

image.png

2. 非空约束

2.1 作用

限制某个字段/列不允许为空

2.2 关键字

NOT NULL

2.3 特点

  • 默认,所有的类型的值都可以是NULL,包括INT、FLOAT等数据类型
  • 非空约束只能出现在表对象的列上,只能某个列单独限定非空,不能组合非空
  • 一个表可以有很多列都分别限定了非空
  • 空字符串’’不等于NULL,0也不等于NULL

2.4 添加非空约束

  • 在创建表CREATE TABLE的时候添加约束
    1. CREATE TABLE 表名称(
    2. 字段名 数据类型,
    3. 字段名 数据类型 NOT NULL,
    4. 字段名 数据类型 NOT NULL
    5. );
    举例:
    1. CREATE TABLE t_test1(
    2. id INT NOT NULL,
    3. last_name VARCHAR(25) NOT NULL,
    4. email VARCHAR(25),
    5. salary DECIMAL(10,2)
    6. );
    7. USE dbtest2;
    8. DESC t_test1;
    ```plsql SELECT * FROM t_test1; — 成功 INSERT INTO t_test1 VALUES(1,’Tom’,’Tom@126.com’,12000);

— 1048 - Column ‘id’ cannot be null INSERT INTO t_test1 VALUES(NULL,’Jerry’,’Jerry@126.com’,13000);

— 1048 - Column ‘last_name’ cannot be null INSERT INTO t_test1 VALUES(2,NULL,’test@126.com’,12450);

  1. - 建表之后添加约束
  2. `alter table 表名称 modify 字段名 数据类型 not null;`
  3. ```plsql
  4. -- OK
  5. ALTER TABLE t_test1
  6. MODIFY email varchar(25) NOT NULL;
  7. DESC t_test1;

image.png

2.5 删除非空约束

  1. alter table 表名称 modify 字段名 数据类型 NULL;
  2. alter table 表名称 modify 字段名 数据类型;

3. 唯一性约束

3.1 作用

用来限制某个字段/某列的值不可以重复。
image.png

3.2 关键字

UNIQUE

3.3 特点

  • 同一个表里面可以有多个唯一性约束
  • 唯一约束可以是某一个列的值唯一,也可以是多个列的组合的值唯一
  • 唯一性约束允许列值为空NULL
  • 在创建唯一性约束的时候,如果不显式的给约束命名,那么就默认和列名相同
  • MySQL会给唯一约束的列上默认创建一个唯一索引

    3.4 添加唯一约束

    3.4.1 创建表的时候添加约束

    1. create table 表名称(
    2. 字段名 数据类型,
    3. 字段名 数据类型 unique,
    4. 字段名 数据类型 unique key,
    5. 字段名 数据类型
    6. );
    7. create table 表名称(
    8. 字段名 数据类型,
    9. 字段名 数据类型,
    10. 字段名 数据类型,
    11. [constraint 约束名] unique key(字段名)
    12. );
    举例: ```plsql CREATE TABLE IF NOT EXISTS test2( id INT UNIQUE, # 列级约束 last_name VARCHAR(15), email VARCHAR(25), salary DECIMAL(10,2),

    表级约束

    CONSTRAINT uni_test2_email UNIQUE(email) );

DESC test2;

  1. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/21436600/1645875252376-8110950a-a87a-4ccf-affd-3c06e7d377f6.png#clientId=ue1b715fb-6ff9-4&from=paste&height=133&id=u0be300de&originHeight=190&originWidth=679&originalType=binary&ratio=1&rotation=0&showTitle=false&size=15081&status=done&style=none&taskId=u7eb3d632-ae75-4b13-a29f-77b6c3ae615&title=&width=476.5)
  2. ```plsql
  3. INSERT INTO test2 ( id, last_name, email, salary )
  4. VALUES
  5. ( 1, 'Tom', 'Tom@123.com', 5000 );
  6. SELECT * FROM test2;
  7. -- 编号重复
  8. INSERT INTO test2 VALUES (1, 'Tom2','Tom2@123.com',5000);
  9. -- 邮箱重复
  10. INSERT INTO test2 VALUES (2, 'Tom2','Tom@123.com',5000);
  11. -- 正常插入
  12. INSERT INTO test2 VALUES (2, 'Tom2','Tom2@123.com',5000);

3.4.2 创建表之后添加约束

  1. 方式1:

    1. #字段列表中如果是一个字段,表示该列的值唯一。如果是两个或更多个字段,那么复合唯一,即多个字段的组合是唯一的
    2. #方式1:
    3. alter table 表名称 add unique key(字段列表);
  2. 方式2:

    1. #方式2:
    2. alter table 表名称 modify 字段名 字段类型 unique;
    1. -- 创建表之后指定唯一约束
    2. -- 表示要求idemail的组合唯一
    3. ALTER TABLE test2 ADD UNIQUE KEY (id,email);

    image.png

    3.5 关于复合唯一约束

    复合唯一约束只能在表级上进行添加,表示的多个字段的组合值唯一

    1. create table 表名称(
    2. 字段名 数据类型,
    3. 字段名 数据类型,
    4. 字段名 数据类型,
    5. unique key(字段列表) #字段列表中写的是多个字段名,多个字段名用逗号分隔,表示那么是复合唯一,即多个字段的组合是唯一的
    6. );

    ```plsql

    学生表

    create table student( sid int, #学号 sname varchar(20), #姓名 tel char(11) unique key, #电话 cardid char(18) unique key #身份证号 );

课程表

create table course( cid int, #课程编号 cname varchar(20) #课程名称 );

选课表

create table student_course( id int, sid int, cid int, score int, unique key(sid,cid) #复合唯一 );

  1. ```plsql
  2. insert into student values(1,'张三','13710011002','101223199012015623');#成功
  3. insert into student values(2,'李四','13710011003','101223199012015624');#成功
  4. insert into course values(1001,'Java'),(1002,'MySQL');#成功
  1. mysql> select * from student;
  2. +-----+-------+-------------+--------------------+
  3. | sid | sname | tel | cardid |
  4. +-----+-------+-------------+--------------------+
  5. | 1 | 张三 | 13710011002 | 101223199012015623 |
  6. | 2 | 李四 | 13710011003 | 101223199012015624 |
  7. +-----+-------+-------------+--------------------+
  8. 2 rows in set (0.00 sec)
  9. mysql> select * from course;
  10. +------+-------+
  11. | cid | cname |
  12. +------+-------+
  13. | 1001 | Java |
  14. | 1002 | MySQL |
  15. +------+-------+
  16. 2 rows in set (0.00 sec)

3.6 删除唯一约束

  • 添加唯一性约束的列上也会自动创建唯一索引
  • 删除唯一约束的方式只能通过删除唯一索引的方式进行删除
  • 删除的时候需要指定唯一索引名,唯一索引名和唯一约束名的地位一样
  • 如果创建唯一约束时未指定名称,如果是单列,就默认和列名相同;如果是组合列,那么默认和()中排在第一个的列名相同。也可以自定义唯一性约束名。
    1. SELECT * FROM information_schema.table_constraints WHERE table_name = '表名';
    2. #查看都有哪些约束
    1. ALTER TABLE '表名'
    2. DROP INDEX uk_name_pwd;
    image.png

4. 主键约束

4.1 作用

用来唯一的标识表中的一行记录。

4.2 关键字

PRIMARY KEY

4.3 特点

  • 主键约束相当于唯一约束+非空约束的组合,主键约束列不允许重复,也不允许出现空值

image.png

  • 一个表中最多只能有一个主键约束
    • 主键约束可以在列的级别进行创建
    • 也可以在表的级别进行创建
  • 主键约束对应表中的一列或者多列(可以是复合主键)
  • 如果是多列组合的复合主键约束,那么这些列都不允许为空值,并且组合的值不允许重复。
  • 在MySQL中,主键的名字始终为PRIMARY,就算自己自定义了约束的名字也没有作用
  • 当创建主键约束时,系统默认会在所在的列或列组合上建立对应的主键索引(能够根据主键查询的,就根据主键查询,效率更高)。如果删除主键约束了,主键约束对应的索引就自动删除了。
  • 需要注意的一点是,不要修改主键字段的值。因为主键是数据记录的唯一标识,如果修改了主键的值,就有可能会破坏数据的完整性。

4.4 添加主键约束

  1. 建表的时候指定主键约束
    1. create table 表名称(
    2. 字段名 数据类型 primary key, #列级模式
    3. 字段名 数据类型,
    4. 字段名 数据类型
    5. );
    6. create table 表名称(
    7. 字段名 数据类型,
    8. 字段名 数据类型,
    9. 字段名 数据类型,
    10. [constraint 约束名] primary key(字段名) #表级模式
    11. );
    ```plsql create table if not exists test2( id int PRIMARY KEY, last_name varchar(30), age int, salary DECIMAL(10,2) );

DESC test2;

  1. ![image.png](https://cdn.nlark.com/yuque/0/2022/png/21436600/1645877079969-4627c380-77e7-40c5-b65a-2cbd29392397.png#clientId=ue1b715fb-6ff9-4&from=paste&height=321&id=u5775015c&originHeight=462&originWidth=758&originalType=binary&ratio=1&rotation=0&showTitle=false&size=38293&status=done&style=none&taskId=ud58bfc84-84cf-41d6-9315-7c7b0d3c453&title=&width=526)<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/21436600/1645877105797-1f545463-b4b0-4b2a-80c6-763d5b882786.png#clientId=ue1b715fb-6ff9-4&from=paste&height=169&id=u94d960ba&originHeight=337&originWidth=1167&originalType=binary&ratio=1&rotation=0&showTitle=false&size=29936&status=done&style=none&taskId=uf0e8a2a6-17fa-406d-a00c-42f409416fe&title=&width=583.5)
  2. ```plsql
  3. -- OK
  4. INSERT INTO test2 VALUES (1,'Tom',18,5400);
  5. -- ERROR: Duplicate entry '1' for key 'PRIMARY'
  6. INSERT INTO test2 VALUES (1,'Tom2',19,5500);
  7. -- ERROR: Column 'id' cannot be null
  8. INSERT INTO test2 VALUES (NULL,'Tom2',19,5500);

4.4.1 列级约束

  1. CREATE TABLE emp4(
  2. id INT PRIMARY KEY AUTO_INCREMENT ,
  3. NAME VARCHAR(20)
  4. );

4.4.2 表级约束

  1. CREATE TABLE emp5(
  2. id INT NOT NULL AUTO_INCREMENT,
  3. NAME VARCHAR(20),
  4. pwd VARCHAR(15),
  5. -- 自定义的约束名称不会生效
  6. CONSTRAINT emp5_id_pk PRIMARY KEY(id)
  7. );
  1. 创建表之后指定主键约束
    1. #字段列表可以是一个字段,也可以是多个字段,如果是多个字段的话,是复合主键
    2. ALTER TABLE 表名称 ADD PRIMARY KEY(字段列表);
    1. -- 建表之后添加主键
    2. create table if not exists test3(
    3. id int,
    4. last_name varchar(30),
    5. age int,
    6. salary DECIMAL(10,2)
    7. );
    8. desc test3;
    9. ALTER table test3 add primary key(id);

    4.5 关于复合主键

    复合主键表示多个字段(两个和两个以上)的组合是非空且唯一的
    1. create table 表名称(
    2. 字段名 数据类型,
    3. 字段名 数据类型,
    4. 字段名 数据类型,
    5. primary key(字段名1,字段名2) #表示字段1和字段2的组合是唯一的,也可以有更多个字段
    6. );

    4.6 删除主键约束

    实际开发的过程中,不会去删除主键约束 原因:删除主键约束之后如果再重新添加上的话,就需要在底层中重新创建一个B+树,费时费力。

  1. alter table '表名' drop primary key;

举例:

  1. ALTER TABLE student DROP PRIMARY KEY;

删除主键约束的时候不需要指定主键的名称,因为每一张表都只有一个主键。 删除主键约束之后,非空约束仍然存在

5. 自增列:AUTO_INCREMENT

5.1 作用

使某一个字段的值自增

5.2 关键字

AUTO_INCREMENT

5.3 特点和要求

  1. 一个表中最多只能有一个自增长列
  2. 当需要产生唯一标识符(比如主键)或顺序值的时候,可以设置成自增长列
  3. 自增长列约束的列必须是键列(主键列,唯一键列)
  4. 自增约束的列的数据类型必须是整型
  5. 如果自增列指定了 0 和 null,会在当前最大值的基础上自增;如果自增列手动指定了具体值,直接赋值为具体值。

错误演示:

  1. create table employee(
  2. eid int auto_increment,
  3. ename varchar(20)
  4. );
  5. # ERROR 1075 (42000): Incorrect table definition; there can be only one auto column and it must be defined as a key
  1. create table employee(
  2. eid int primary key,
  3. ename varchar(20) unique key auto_increment
  4. );
  5. # ERROR 1063 (42000): Incorrect column specifier for column 'ename' 因为ename不是整数类型

5.4 如何指定自增约束

  1. 建表的时候

    1. create table 表名称(
    2. 字段名 数据类型 primary key auto_increment,
    3. 字段名 数据类型 unique key not null,
    4. 字段名 数据类型 unique key,
    5. 字段名 数据类型 not null default 默认值,
    6. );
    7. create table 表名称(
    8. 字段名 数据类型 default 默认值 ,
    9. 字段名 数据类型 unique key auto_increment,
    10. 字段名 数据类型 not null default 默认值,,
    11. primary key(字段名)
    12. );
    1. create table employee(
    2. eid int primary key auto_increment,
    3. ename varchar(20)
    4. );
    1. mysql> desc employee;
    2. +-------+-------------+------+-----+---------+----------------+
    3. | Field | Type | Null | Key | Default | Extra |
    4. +-------+-------------+------+-----+---------+----------------+
    5. | eid | int(11) | NO | PRI | NULL | auto_increment |
    6. | ename | varchar(20) | YES | | NULL | |
    7. +-------+-------------+------+-----+---------+----------------+
    8. 2 rows in set (0.00 sec)
  2. 建表之后

    1. alter table 表名称 modify 字段名 数据类型 auto_increment;

    比如:

    1. create table employee(
    2. eid int primary key ,
    3. ename varchar(20)
    4. );
    1. alter table employee modify eid int auto_increment;
    1. mysql> desc employee;
    2. +-------+-------------+------+-----+---------+----------------+
    3. | Field | Type | Null | Key | Default | Extra |
    4. +-------+-------------+------+-----+---------+----------------+
    5. | eid | int(11) | NO | PRI | NULL | auto_increment |
    6. | ename | varchar(20) | YES | | NULL | |
    7. +-------+-------------+------+-----+---------+----------------+
    8. 2 rows in set (0.00 sec)

    5.5 如何删除自增约束

    ```plsql

    alter table 表名称 modify 字段名 数据类型 auto_increment;#给这个字段增加自增约束

alter table 表名称 modify 字段名 数据类型; #去掉auto_increment相当于删除

  1. ```plsql
  2. alter table employee modify eid int;
  1. mysql> desc employee;
  2. +-------+-------------+------+-----+---------+-------+
  3. | Field | Type | Null | Key | Default | Extra |
  4. +-------+-------------+------+-----+---------+-------+
  5. | eid | int(11) | NO | PRI | NULL | |
  6. | ename | varchar(20) | YES | | NULL | |
  7. +-------+-------------+------+-----+---------+-------+
  8. 2 rows in set (0.00 sec)

5.6 MySQL8.0新特性—自增变量的持久化

在MySQL 8.0之前,自增主键AUTO_INCREMENT的值如果大于max(primary key)+1,在MySQL重启后,会重置AUTO_INCREMENT=max(primary key)+1,这种现象在某些情况下会导致业务主键冲突或者其他难以发现的问题。下面通过案例来对比不同的版本中自增变量是否持久化。在MySQL 5.7版本中,测试步骤如下:创建的数据表中包含自增主键的id字段,语句如下:

  1. CREATE TABLE test1(
  2. id INT PRIMARY KEY AUTO_INCREMENT
  3. );

插入4个空值,执行:

  1. INSERT INTO test1
  2. VALUES(0),(0),(0),(0);

查询数据表test1中的数据,结果如下:

  1. mysql> SELECT * FROM test1;
  2. +----+
  3. | id |
  4. +----+
  5. | 1 |
  6. | 2 |
  7. | 3 |
  8. | 4 |
  9. +----+
  10. 4 rows in set (0.00 sec)

删除id为4的记录,语句如下:

  1. DELETE FROM test1 WHERE id = 4;

再次插入一个空值,语句如下:

  1. INSERT INTO test1 VALUES(0);

查询此时数据表test1中的数据,结果如下:

  1. mysql> SELECT * FROM test1;
  2. +----+
  3. | id |
  4. +----+
  5. | 1 |
  6. | 2 |
  7. | 3 |
  8. | 5 |
  9. +----+
  10. 4 rows in set (0.00 sec)

从结果可以看出,虽然删除了id为4的记录,但是再次插入空值时,并没有重用被删除的4,而是分配了5。
删除id为5的记录,结果如下:

  1. DELETE FROM test1 where id=5;

重启数据库服务,再次插入一个空值

  1. INSERT INTO test1 values(0);

再次查询数据表中的数据,结果如下:

  1. mysql> SELECT * FROM test1;
  2. +----+
  3. | id |
  4. +----+
  5. | 1 |
  6. | 2 |
  7. | 3 |
  8. | 4 |
  9. +----+
  10. 4 rows in set (0.00 sec)

从结果可以看出,新插入的0值分配的是4,按照重启前的操作逻辑,此处应该分配6。出现上述结果的主要原因是自增主键没有持久化。在MySQL 5.7系统中,对于自增主键的分配规则,是由InnoDB数据字典内部一个计数器来决定的,而该计数器只在内存中维护,并不会持久化到磁盘中。当数据库重启时,该计数器会被初始化。
MySQL 8.0版本中,上述测试步骤最后一步的结果如下:

  1. mysql> SELECT * FROM test1;
  2. +----+
  3. | id |
  4. +----+
  5. | 1 |
  6. | 2 |
  7. | 3 |
  8. | 6 |
  9. +----+
  10. 4 rows in set (0.00 sec)

从结果中可以发现,自增变量实际上已经被持久化了,而不是写入到内存中
MySQL 8.0将自增主键的计数器持久化到**重做日志**。每次计数器发生改变,都会将其写入重做日志中。如果数据库重启,InnoDB会根据重做日志中的信息来初始化计数器的内存值。

6. FOREIGN KEY约束

6.1 作用

外键约束。
限定某个表的某个字段的引用完整性。
image.png

6.2 关键字

FOREIGN KEY

6.3 主表和从表

主表:被引用的表,被参考的表
从表:引用别人的表,参考别人的表

比如,员工表中员工所在部门的这个字段的值要去参考部门表,因此员工表是从表,部门表是主表 一张子表可以有多个从表

6.4 特点

  1. 从表的外键列,必须引用的是主表中的主键或者唯一约束的列

因为被引用的值必须是唯一的

  1. 在创建外键约束的时候,如果不给外键约束命名,那么默认情况下不是列名,而是会自动产生一个外键名,也可以自己指定外键约束名
  2. 在创建表的时候就指定外键约束的话,先创建主表,然后再创建从表
  3. 删除表的时候,先删除从表,然后再删除主表
  4. 当主表的记录被从表所参照的时候,主表的记录将不允许被删除,如果要删除数据,则需要先删除从表中依赖该记录的数据,然后才可以删除主表中的数据
  5. 在“从表”中指定外键约束,并且一个表可以建立多个外键约束
  6. 从表的外键列与主表中被参照列的名字可以不相同,但是数据类型必须是一样的,逻辑意义也是一致的。
  7. 创建外键约束的时候,系统默认在所在的列上建立对应的普通索引。但是索引名是外键的约束名(根据外键的查询效率很高)
  8. 删除外键的时候,需要手动的删除对应的索引

    6.5 添加外键约束

    6.5.1 创建表的时候添加

    ```plsql create table 主表名称( 字段1 数据类型 primary key, 字段2 数据类型 );

create table 从表名称( 字段1 数据类型 primary key, 字段2 数据类型, [CONSTRAINT <外键约束名称>] FOREIGN KEY(从表的某个字段) references 主表名(被参考字段) );

— FOREIGN KEY: 在表级指定子表中的列 — REFERENCES: 标示在父表中的列

  1. ```plsql
  2. --说明:
  3. --(1)主表dept必须先创建成功,然后才能创建emp表,指定外键成功。
  4. --(2)删除表时,先删除从表emp,再删除主表dept
  5. CREATE TABLE t_dept(
  6. dept_id INT PRIMARY KEY,
  7. dept_name VARCHAR(25)
  8. ) ENGINE = InnoDB;
  9. CREATE TABLE t_emp(
  10. emp_id INT PRIMARY KEY AUTO_INCREMENT,
  11. emp_name VARCHAR(25),
  12. emp_mail VARCHAR(50),
  13. department_id INT,
  14. FOREIGN KEY (department_id) REFERENCES t_dept(dept_id)
  15. ) ENGINE = InnoDB;
  16. DESC t_emp;
  17. SELECT * FROM information_schema.TABLE_CONSTRAINTS WHERE table_name = 't_emp';

image.png

6.5.2 创建表之后添加

一般情况下,表与表的关联都是提前设计好了的,因此,会在创建表的时候就把外键约束定义好。不过,如果需要修改表的设计(比如添加新的字段,增加新的关联关系),但没有预先定义外键约束,那么,就要用修改表的方式来补充定义。
格式:

  1. ALTER TABLE 从表名 ADD [CONSTRAINT 约束名] FOREIGN KEY (从表的字段)
  2. REFERENCES 主表名(被引用字段) [on update xx][on delete xx];

举例:

  1. ALTER TABLE emp1
  2. ADD [CONSTRAINT emp_dept_id_fk] FOREIGN KEY(dept_id) REFERENCES dept(dept_id);

举例:

  1. create table dept(
  2. did int primary key, #部门编号
  3. dname varchar(50) #部门名称
  4. );
  5. create table emp(
  6. eid int primary key, #员工编号
  7. ename varchar(5), #员工姓名
  8. deptid int #员工所在的部门
  9. );
  10. #这两个表创建时,没有指定外键的话,那么创建顺序是随意
  1. alter table emp add foreign key (deptid) references dept(did);
  1. ALTER TABLE t_emp2 ADD CONSTRAINT fk_emp2_dept_id FOREIGN KEY ( department_id ) REFERENCES t_dept ( dept_id );
  2. SELECT
  3. *
  4. FROM
  5. information_schema.TABLE_CONSTRAINTS
  6. WHERE
  7. table_name = 't_emp2';

6.5.3 创建外键的坑

  1. 在MySQL中创建外键的时候,两个字段的类型必须是严格的相同
  2. 主表中被引用的字段没有建立起索引,或者说不是一个PRIMARY KEY
  3. 两张表必须都是InnoDB存储引擎的
    1. 实际上,如果两个表都是MyISAM引擎的,创建外键的时候不会报错,但是不会产生外键,只会建立起索引
    2. 如果创建外键约束的时候,发现创建失败,那么需要检查一下两张表的存储引擎

      6.5.4 总结

      外键约束关系是针对双方的
  • 添加了外键约束后,主表的修改和删除数据受到约束
  • 添加了外键约束后,从表的修改和删除数据受到约束
  • 在从表上建立外键,要求主表必须存在
  • 删除主表的时候,要求先将从表先删除,或将从表中外键引用该主表的关系先删除

    6.6 约束等级

  • CASCADE:在父表上update/delete记录时,同步update/delete掉子表的匹配记录

  • SET NULL:在父表上update/delete记录时,将子表上匹配记录的列设为null,但是要注意子表的外键列不能为not null
  • No ACTION:如果子表中有匹配的记录,则不允许对父表对应候选键进行update/delete操作
  • Restrict:同no action, 都是立即检查外键约束

对于外键约束,最好是采用: ON UPDATE CASCADE ON DELETE RESTRICT 的方式。

(1)演示1:on update cascade on delete set null

  1. create table dept(
  2. did int primary key, #部门编号
  3. dname varchar(50) #部门名称
  4. );
  5. create table emp(
  6. eid int primary key, #员工编号
  7. ename varchar(5), #员工姓名
  8. deptid int, #员工所在的部门
  9. foreign key (deptid) references dept(did) on update cascade on delete set null
  10. #把修改操作设置为级联修改等级,把删除操作设置为set null等级
  11. );
  1. insert into dept values(1001,'教学部');
  2. insert into dept values(1002, '财务部');
  3. insert into dept values(1003, '咨询部');
  4. insert into emp values(1,'张三',1001); #在添加这条记录时,要求部门表有1001部门
  5. insert into emp values(2,'李四',1001);
  6. insert into emp values(3,'王五',1002);
  1. mysql> select * from dept;
  2. mysql> select * from emp;
  1. #修改主表成功,从表也跟着修改,修改了主表被引用的字段1002为1004,从表的引用字段就跟着修改为1004了
  2. mysql> update dept set did = 1004 where did = 1002;
  3. Query OK, 1 row affected (0.00 sec)
  4. Rows matched: 1 Changed: 1 Warnings: 0
  5. mysql> select * from dept;
  6. +------+--------+
  7. | did | dname |
  8. +------+--------+
  9. | 1001 | 教学部 |
  10. | 1003 | 咨询部 |
  11. | 1004 | 财务部 | #原来是1002,修改为1004
  12. +------+--------+
  13. 3 rows in set (0.00 sec)
  14. mysql> select * from emp;
  15. +-----+-------+--------+
  16. | eid | ename | deptid |
  17. +-----+-------+--------+
  18. | 1 | 张三 | 1001 |
  19. | 2 | 李四 | 1001 |
  20. | 3 | 王五 | 1004 | #原来是1002,跟着修改为1004
  21. +-----+-------+--------+
  22. 3 rows in set (0.00 sec)
  1. #删除主表的记录成功,从表对应的字段的值被修改为null
  2. mysql> delete from dept where did = 1001;
  3. Query OK, 1 row affected (0.01 sec)
  4. mysql> select * from dept;
  5. +------+--------+
  6. | did | dname | #记录1001部门被删除了
  7. +------+--------+
  8. | 1003 | 咨询部 |
  9. | 1004 | 财务部 |
  10. +------+--------+
  11. 2 rows in set (0.00 sec)
  12. mysql> select * from emp;
  13. +-----+-------+--------+
  14. | eid | ename | deptid |
  15. +-----+-------+--------+
  16. | 1 | 张三 | NULL | #原来引用1001部门的员工,deptid字段变为null
  17. | 2 | 李四 | NULL |
  18. | 3 | 王五 | 1004 |
  19. +-----+-------+--------+
  20. 3 rows in set (0.00 sec)

6.7 删除外键约束

  1. (1)第一步先查看约束名和删除外键约束
  2. #查看某个表的约束名
  3. SELECT * FROM information_schema.table_constraints WHERE table_name = '表名称';
  4. ALTER TABLE 从表名 DROP FOREIGN KEY 外键约束名;
  5. 2)第二步查看索引名和删除索引。(注意,必须自己手动删除)
  6. #查看某个表的索引名
  7. SHOW INDEX FROM 表名称;
  8. ALTER TABLE 从表名 DROP INDEX 索引名;

举例:

  1. -- 删除外键约束
  2. SELECT * FROM information_schema.TABLE_CONSTRAINTS WHERE TABLE_NAME = 't_emp2';
  3. ALTER TABLE t_emp2 DROP FOREIGN KEY fk_emp2_dept_id;
  4. SHOW INDEX FROM t_emp2;
  5. ALTER TABLE t_emp2 DROP INDEX fk_emp2_dept_id;

6.8 开发场景

问题1:如果两个表之间有关系(一对一、一对多),比如:员工表和部门表(一对多),它们之间是否一定要建外键约束?

答:不是的

问题2:建和不建外键约束有什么区别?

答:建外键约束,你的操作(创建表、删除表、添加、修改、删除)会受到限制,从语法层面受到限制。例如:在员工表中不可能添加一个员工信息,它的部门的值在部门表中找不到。

不建外键约束,你的操作(创建表、删除表、添加、修改、删除)不受限制,要保证数据的引用完整性,只能依靠程序员的自觉,或者是在Java程序中进行限定。例如:在员工表中,可以添加一个员工的信息,它的部门指定为一个完全不存在的部门。

问题3:那么建和不建外键约束和查询有没有关系?

答:没有

在 MySQL 里,外键约束是有成本的,需要消耗系统资源。对于大并发的 SQL 操作,有可能会不适合。比如大型网站的中央数据库,可能会因为外键约束的系统开销而变得非常慢。所以, MySQL 允许你不使用系统自带的外键约束,在应用层面完成检查数据一致性的逻辑。也就是说,即使你不用外键约束,也要想办法通过应用层面的附加逻辑,来实现外键约束的功能,确保数据的一致性。

6.9 阿里开发规范

强制】不得使用外键与级联,一切外键概念必须在应用层解决。
说明:(概念解释)学生表中的 student_id 是主键,那么成绩表中的 student_id 则为外键。如果更新学生表中的 student_id,同时触发成绩表中的 student_id 更新,即为级联更新。外键与级联更新适用于单机低并发,不适合分布式高并发集群;级联更新是强阻塞,存在数据库更新风暴的风险;外键影响数据库的插入速度

7. CHECK约束

7.1 作用

检查某个字段的值是否符合XX要求,一般指的是值的范围

7.2 关键字

CHECK

7.3 说明:MySQL5.7不支持CHECK约束

MySQL5.7 可以使用check约束,但check约束对数据验证没有任何作用。添加数据时,没有任何错误或警告
但是MySQL 8.0中可以使用check约束了

  1. CREATE TABLE test1(
  2. e_id INT PRIMARY KEY,
  3. e_name VARCHAR(25),
  4. e_gender CHAR CHECK('男' OR '女')
  5. );
  6. INSERT INTO test1 VALUES(1,'Tom','妖');
  7. SELECT * FROM test1;

image.png

8. DEFAULT约束

8.1 作用

给某一个字段/列指定默认值,一旦设置了默认值后,在插入数据的时候,如果此字段没有显式赋值,那么就赋值为默认值。

8.2 关键字

DEFAULT

8.3 如何给字段加默认值

(1)建表的时候添加
说明:默认值约束一般不在唯一键和主键列上加

  1. create table 表名称(
  2. 字段名 数据类型 primary key,
  3. 字段名 数据类型 unique key not null,
  4. 字段名 数据类型 unique key,
  5. 字段名 数据类型 not null default 默认值,
  6. );
  7. create table 表名称(
  8. 字段名 数据类型 default 默认值 ,
  9. 字段名 数据类型 not null default 默认值,
  10. 字段名 数据类型 not null default 默认值,
  11. primary key(字段名),
  12. unique key(字段名)
  13. );
  1. create table employee(
  2. eid int primary key,
  3. ename varchar(20) not null,
  4. gender char default '男',
  5. tel char(11) not null default '' #默认是空字符串
  6. );
  1. mysql> desc employee;
  2. +--------+-------------+------+-----+---------+-------+
  3. | Field | Type | Null | Key | Default | Extra |
  4. +--------+-------------+------+-----+---------+-------+
  5. | eid | int(11) | NO | PRI | NULL | |
  6. | ename | varchar(20) | NO | | NULL | |
  7. | gender | char(1) | YES | | | |
  8. | tel | char(11) | NO | | | |
  9. +--------+-------------+------+-----+---------+-------+
  10. 4 rows in set (0.00 sec)
  1. insert into employee values(1,'汪飞','男','13700102535'); #成功
  1. mysql> select * from employee;
  2. +-----+-------+--------+-------------+
  3. | eid | ename | gender | tel |
  4. +-----+-------+--------+-------------+
  5. | 1 | 汪飞 | | 13700102535 |
  6. +-----+-------+--------+-------------+
  7. 1 row in set (0.00 sec)
  1. insert into employee(eid,ename) values(2,'天琪'); #成功
  1. mysql> select * from employee;
  2. +-----+-------+--------+-------------+
  3. | eid | ename | gender | tel |
  4. +-----+-------+--------+-------------+
  5. | 1 | 汪飞 | | 13700102535 |
  6. | 2 | 天琪 | | |
  7. +-----+-------+--------+-------------+
  8. 2 rows in set (0.00 sec)
  1. insert into employee(eid,ename) values(3,'二虎');
  2. #ERROR 1062 (23000): Duplicate entry '' for key 'tel'
  3. #如果tel有唯一性约束的话会报错,如果tel没有唯一性约束,可以添加成功

(2)建表之后添加

  1. alter table 表名称 modify 字段名 数据类型 default 默认值;
  2. #如果这个字段原来有非空约束,你还保留非空约束,那么在加默认值约束时,还得保留非空约束,否则非空约束就被删除了
  3. #同理,在给某个字段加非空约束也一样,如果这个字段原来有默认值约束,你想保留,也要在modify语句中保留默认值约束,否则就删除了
  4. alter table 表名称 modify 字段名 数据类型 default 默认值 not null;
  1. create table employee(
  2. eid int primary key,
  3. ename varchar(20),
  4. gender char,
  5. tel char(11) not null
  6. );
  1. mysql> desc employee;
  2. +--------+-------------+------+-----+---------+-------+
  3. | Field | Type | Null | Key | Default | Extra |
  4. +--------+-------------+------+-----+---------+-------+
  5. | eid | int(11) | NO | PRI | NULL | |
  6. | ename | varchar(20) | YES | | NULL | |
  7. | gender | char(1) | YES | | NULL | |
  8. | tel | char(11) | NO | | NULL | |
  9. +--------+-------------+------+-----+---------+-------+
  10. 4 rows in set (0.00 sec)
  1. alter table employee modify gender char default '男'; #给gender字段增加默认值约束
  2. alter table employee modify tel char(11) default ''; #给tel字段增加默认值约束
  1. mysql> desc employee;
  2. +--------+-------------+------+-----+---------+-------+
  3. | Field | Type | Null | Key | Default | Extra |
  4. +--------+-------------+------+-----+---------+-------+
  5. | eid | int(11) | NO | PRI | NULL | |
  6. | ename | varchar(20) | YES | | NULL | |
  7. | gender | char(1) | YES | | | |
  8. | tel | char(11) | YES | | | |
  9. +--------+-------------+------+-----+---------+-------+
  10. 4 rows in set (0.00 sec)
  1. alter table employee modify tel char(11) default '' not null;#给tel字段增加默认值约束,并保留非空约束
  1. mysql> desc employee;
  2. +--------+-------------+------+-----+---------+-------+
  3. | Field | Type | Null | Key | Default | Extra |
  4. +--------+-------------+------+-----+---------+-------+
  5. | eid | int(11) | NO | PRI | NULL | |
  6. | ename | varchar(20) | YES | | NULL | |
  7. | gender | char(1) | YES | | | |
  8. | tel | char(11) | NO | | | |
  9. +--------+-------------+------+-----+---------+-------+
  10. 4 rows in set (0.00 sec)

8.4 删除默认值约束

  1. alter table 表名称 modify 字段名 数据类型 ;#删除默认值约束,也不保留非空约束
  2. alter table 表名称 modify 字段名 数据类型 not null; #删除默认值约束,保留非空约束

9. 面试题

面试1、为什么建表时,加 not null default ‘’ 或 default 0
答:不想让表中出现null值。

面试2、为什么不想要 null 的值
答:(1)不好比较。null是一种特殊值,比较时只能用专门的is null 和 is not null来比较。碰到运算符,通常返回null。
(2)效率不高。影响提高索引效果。因此,我们往往在建表时 not null default ‘’ 或 default 0

面试3、带AUTO_INCREMENT约束的字段值是从1开始的吗?
在MySQL中,默认AUTO_INCREMENT的初始值是1,每新增一条记录,字段值自动加1。设置自增属性(AUTO_INCREMENT)的时候,还可以指定第一条插入记录的自增字段的值,这样新插入的记录的自增字段值从初始值开始递增,如在表中插入第一条记录,同时指定id值为5,则以后插入的记录的id值就会从6开始往上增加。添加主键约束时,往往需要设置字段自动增加属性。

面试4、并不是每个表都可以任意选择存储引擎?
外键约束(FOREIGN KEY)不能跨引擎使用。
MySQL支持多种存储引擎,每一个表都可以指定一个不同的存储引擎,需要注意的是:外键约束是用来保证数据的参照完整性的,如果表之间需要关联外键,却指定了不同的存储引擎,那么这些表之间是不能创建外键约束的。所以说,存储引擎的选择也不完全是随意的。