MySQL基础-约束

MySQL基础-约束

一、约束概述

1.约束的由来

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

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

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

2.约束的定义

约束是表级的强制规定,可以在创建表时规定约束(通过 CREATE TABLE 语句),或者在表创建之后通过 ALTER TABLE 语句规定约束。

3.约束的分类

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

查看某个表已有的约束:

1
2
SELECT * FROM information_schema.table_constraints
WHERE table_name = '表名称';

二、非空约束

1.非空约束的特点

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

2.非空约束的添加

  • 创建表时添加非空约束
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
CREATE TABLE 表名称(
字段名 数据类型,
字段名 数据类型 NOT NULL,
字段名 数据类型 NOT NULL
);

-- 举例
mysql> CREATE TABLE test1(
-> id INT,
-> name VARCHAR(15) NOT NULL,
-> email VARCHAR(25) NOT NULL
-> );
Query OK, 0 rows affected (0.01 sec)

mysql> SHOW CREATE TABLE test1;
+-------+-------------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table |
+-------+-------------------------------------------------------------------------------------------------------------------------------------------------------+
| test1 | CREATE TABLE `test1` (
`id` int DEFAULT NULL,
`name` varchar(15) NOT NULL,
`email` varchar(25) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 |
+-------+-------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

mysql> DESC test1;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | int | YES | | NULL | |
| name | varchar(15) | NO | | NULL | |
| email | varchar(25) | NO | | NULL | |
+-------+-------------+------+-----+---------+-------+
3 rows in set (0.00 sec)

mysql> INSERT INTO test1 VALUES(1,'Tom','tom@163.com');
Query OK, 1 row affected (0.01 sec)

mysql> INSERT INTO test1 VALUES(1,NULL,'tom@163.com');
ERROR 1048 (23000): Column 'name' cannot be null
mysql> INSERT INTO test1 VALUES(1,'Tom',NULL);
ERROR 1048 (23000): Column 'email' cannot be null
mysql> INSERT INTO test1 VALUES(2,'Jerry','jerry@163.com');
Query OK, 1 row affected (0.01 sec)

mysql> SELECT * FROM test1;
+------+-------+---------------+
| id | name | email |
+------+-------+---------------+
| 1 | Tom | tom@163.com |
| 2 | Jerry | jerry@163.com |
+------+-------+---------------+
2 rows in set (0.00 sec)
  • 创建表以后添加非空约束
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
ALTER TABLE 表名称 MODIFY 字段名 数据类型 NOT NULL;

-- 举例
mysql> CREATE TABLE test1(
-> id INT,
-> name VARCHAR(15),
-> email VARCHAR(25)
-> );
Query OK, 0 rows affected (0.01 sec)

mysql> DESC test1;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | int | YES | | NULL | |
| name | varchar(15) | YES | | NULL | |
| email | varchar(25) | YES | | NULL | |
+-------+-------------+------+-----+---------+-------+
3 rows in set (0.00 sec)

mysql> ALTER TABLE test1 MODIFY name VARCHAR(15) NOT NULL;
Query OK, 0 rows affected (0.02 sec)
Records: 0 Duplicates: 0 Warnings: 0

mysql> DESC test1;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | int | YES | | NULL | |
| name | varchar(15) | NO | | NULL | |
| email | varchar(25) | YES | | NULL | |
+-------+-------------+------+-----+---------+-------+
3 rows in set (0.00 sec)

3.非空约束的删除

1
ALTER TABLE 表名称 MODIFY 字段名 数据类型;

三、唯一约束

1.唯一约束的特点

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

2.唯一约束的添加

  • 创建表时添加唯一约束
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
CREATE TABLE 表名称(
字段名 数据类型,
字段名 数据类型 UNIQUE,
字段名 数据类型 UNIQUE KEY,
字段名 数据类型
);

CREATE TABLE 表名称(
字段名 数据类型,
字段名 数据类型,
字段名 数据类型,
字段名 数据类型
[CONSTRAINT 约束名] UNIQUE KEY(字段名)
);

-- 举例
mysql> CREATE TABLE test1(
-> id INT UNIQUE KEY,
-> name VARCHAR(10),
-> password VARCHAR(20)
-> );
Query OK, 0 rows affected (0.01 sec)

mysql> SELECT * FROM information_schema.TABLE_CONSTRAINTS WHERE TABLE_NAME = 'test1';
+--------------------+-------------------+-----------------+--------------+------------+-----------------+----------+
| CONSTRAINT_CATALOG | CONSTRAINT_SCHEMA | CONSTRAINT_NAME | TABLE_SCHEMA | TABLE_NAME | CONSTRAINT_TYPE | ENFORCED |
+--------------------+-------------------+-----------------+--------------+------------+-----------------+----------+
| def | atguigudb | id | atguigudb | test1 | UNIQUE | YES |
+--------------------+-------------------+-----------------+--------------+------------+-----------------+----------+
1 row in set (0.00 sec)

mysql> SHOW INDEX FROM test1;
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| test1 | 0 | id | 1 | id | A | 0 | NULL | NULL | YES | BTREE | | | YES | NULL |
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
1 row in set (0.00 sec)

mysql> DESC test1;
+----------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+-------+
| id | int | YES | UNI | NULL | |
| name | varchar(10) | YES | | NULL | |
| password | varchar(20) | YES | | NULL | |
+----------+-------------+------+-----+---------+-------+
3 rows in set (0.00 sec)

mysql> INSERT INTO test1 VALUES(1,'root','root');
Query OK, 1 row affected (0.01 sec)

mysql> INSERT INTO test1 VALUES(1,'guest','guest');
ERROR 1062 (23000): Duplicate entry '1' for key 'test1.id'
  • 创建表以后添加唯一约束
1
2
ALTER TABLE 表名称 ADD UNIQUE KEY(字段列表);
ALTER TABLE 表名称 MODIFY 字段名 字段类型 UNIQUE KEY;

3.唯一约束的删除

  • 添加唯一性约束的列上也会自动创建唯一索引
  • 删除唯一约束只能通过删除唯一索引的方式删除
  • 删除时需要指定唯一索引名,唯一索引名就和唯一约束名一样
  • 如果创建唯一约束时未指定名称,如果是单列,就默认和列名相同
  • 如果是组合列即复合唯一索引,那么默认和()中排在第一个的列名相同,也可以自定义唯一约束名
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
ALTER TABLE USER DROP INDEX 唯一索引名;

-- 举例
mysql> CREATE TABLE test1(
-> id INT,
-> name VARCHAR(10),
-> password VARCHAR(20),
-> CONSTRAINT uk_test1_id UNIQUE KEY(id)
-> );
Query OK, 0 rows affected (0.01 sec)

mysql> SELECT * FROM information_schema.TABLE_CONSTRAINTS WHERE TABLE_NAME = 'test1';
+--------------------+-------------------+-----------------+--------------+------------+-----------------+----------+
| CONSTRAINT_CATALOG | CONSTRAINT_SCHEMA | CONSTRAINT_NAME | TABLE_SCHEMA | TABLE_NAME | CONSTRAINT_TYPE | ENFORCED |
+--------------------+-------------------+-----------------+--------------+------------+-----------------+----------+
| def | atguigudb | uk_test1_id | atguigudb | test1 | UNIQUE | YES |
+--------------------+-------------------+-----------------+--------------+------------+-----------------+----------+
1 row in set (0.00 sec)

mysql> SHOW INDEX FROM test1;
+-------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+-------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| test1 | 0 | uk_test1_id | 1 | id | A | 0 | NULL | NULL | YES | BTREE | | | YES | NULL |
+-------+------------+-------------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
1 row in set (0.00 sec)

mysql> DESC test1;
+----------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+-------+
| id | int | YES | UNI | NULL | |
| name | varchar(10) | YES | | NULL | |
| password | varchar(20) | YES | | NULL | |
+----------+-------------+------+-----+---------+-------+
3 rows in set (0.00 sec)

mysql> ALTER TABLE test1 DROP INDEX uk_test1_id;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0

mysql> DESC test1;
+----------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+----------+-------------+------+-----+---------+-------+
| id | int | YES | | NULL | |
| name | varchar(10) | YES | | NULL | |
| password | varchar(20) | YES | | NULL | |
+----------+-------------+------+-----+---------+-------+
3 rows in set (0.00 sec)

4.关于复合唯一约束

1
2
3
4
5
6
CREATE TABLE 表名称(
字段名 数据类型,
字段名 数据类型,
字段名 数据类型,
CONSTRAINT 唯一索引名 UNIQUE KEY(字段列表)
);

复合唯一索引使用的场景相对没有单一列的唯一索引那么广泛,举个实际的场景就是两个多对多表如students、courses的关联表中的student_id、course_id联合起来构建复合唯一索引(其实更好的做法是复合主键)。

四、主键约束

1.主键约束的特点

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

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

2.主键约束的添加

  • 创建表时添加主键约束
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
CREATE TABLE 表名称(
字段名 数据类型 PRIMARY KEY,
字段名 数据类型,
字段名 数据类型
);

CREATE TABLE 表名称(
字段名 数据类型,
字段名 数据类型,
字段名 数据类型,
[CONSTRAINT 约束名] PRIMARY KEY(字段名)
);

-- 举例
mysql> CREATE TABLE test1(
-> id INT PRIMARY KEY,
-> name VARCHAR(20)
-> );
Query OK, 0 rows affected (0.02 sec)

mysql> DESC test1;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | int | NO | PRI | NULL | |
| name | varchar(20) | YES | | NULL | |
+-------+-------------+------+-----+---------+-------+
2 rows in set (0.00 sec)

mysql> SHOW INDEX FROM test1;
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment | Visible | Expression |
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
| test1 | 0 | PRIMARY | 1 | id | A | 0 | NULL | NULL | | BTREE | | | YES | NULL |
+-------+------------+----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+---------------+---------+------------+
1 row in set (0.00 sec)

mysql> INSERT INTO test1 VALUES(1,'Tom');
Query OK, 1 row affected (0.01 sec)

mysql> INSERT INTO test1 VALUES(1,'Jerry');
ERROR 1062 (23000): Duplicate entry '1' for key 'test1.PRIMARY'
mysql> INSERT INTO test1 VALUES(2,'Jerry');
Query OK, 1 row affected (0.01 sec)

mysql> INSERT INTO test1 VALUES(NULL,'Bob');
ERROR 1048 (23000): Column 'id' cannot be null
mysql> SELECT * FROM test1;
+----+-------+
| id | name |
+----+-------+
| 1 | Tom |
| 2 | Jerry |
+----+-------+
2 rows in set (0.00 sec)
  • 创建表以后添加主键约束
1
ALTER TABLE 表名称 ADD PRIMARY KEY(字段列表);

3.主键约束的删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
ALTER TABLE 表名称 DROP PRIMARY KEY;

-- 举例
mysql> CREATE TABLE test1(
-> id INT PRIMARY KEY,
-> name VARCHAR(20)
-> );
Query OK, 0 rows affected (0.01 sec)

mysql> DESC test1;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | int | NO | PRI | NULL | |
| name | varchar(20) | YES | | NULL | |
+-------+-------------+------+-----+---------+-------+
2 rows in set (0.00 sec)

mysql> ALTER TABLE test1 DROP PRIMARY KEY;
Query OK, 0 rows affected (0.02 sec)
Records: 0 Duplicates: 0 Warnings: 0

mysql> DESC test1;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | int | NO | | NULL | |
| name | varchar(20) | YES | | NULL | |
+-------+-------------+------+-----+---------+-------+
2 rows in set (0.00 sec)

说明:删除主键约束,不需要指定主键名,因为一个表只有一个主键且名称固定为PRIMARY,删除主键约束后,字段的非空约束还存在!

4.关于复合主键约束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
mysql> #学生表
mysql> CREATE TABLE student (
-> sid INT PRIMARY KEY, #学号
-> sname VARCHAR (20) #学生姓名
-> );#课程表
Query OK, 0 rows affected (0.01 sec)

mysql> CREATE TABLE course (
-> cid INT PRIMARY KEY, #课程编号
-> cname VARCHAR (20) #课程名称
-> );
Query OK, 0 rows affected (0.01 sec)

mysql> #选课表
mysql> CREATE TABLE student_course (
-> sid INT,
-> cid INT,
-> score INT,
-> PRIMARY KEY (sid, cid) #复合主键
-> );
Query OK, 0 rows affected (0.02 sec)

mysql> DESC student_course;
+-------+------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+------+------+-----+---------+-------+
| sid | int | NO | PRI | NULL | |
| cid | int | NO | PRI | NULL | |
| score | int | YES | | NULL | |
+-------+------+------+-----+---------+-------+
3 rows in set (0.00 sec)

mysql> INSERT INTO student VALUES (1,'张三'),(2,'李四');
Query OK, 2 rows affected (0.01 sec)
Records: 2 Duplicates: 0 Warnings: 0

mysql> INSERT INTO course VALUES (1001,'Java'),(1002,'MySQL');
Query OK, 2 rows affected (0.00 sec)
Records: 2 Duplicates: 0 Warnings: 0

mysql> INSERT INTO student_course VALUES (1, 1001, 89),(1,1002,90),(2,1001,88),(2,1002,56);
Query OK, 4 rows affected (0.00 sec)
Records: 4 Duplicates: 0 Warnings: 0

mysql> SELECT * FROM student_course;
+-----+------+-------+
| sid | cid | score |
+-----+------+-------+
| 1 | 1001 | 89 |
| 1 | 1002 | 90 |
| 2 | 1001 | 88 |
| 2 | 1002 | 56 |
+-----+------+-------+
4 rows in set (0.00 sec)

mysql> INSERT INTO student_course VALUES(1,1001,100);
ERROR 1062 (23000): Duplicate entry '1-1001' for key 'student_course.PRIMARY'

五、自增约束

1.自增约束的特点

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

2.自增约束的添加

  • 创建表时添加自增约束
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
CREATE TABLE 表名称 ( 
字段名 数据类型 PRIMARY KEY AUTO_INCREMENT,
字段名 数据类型 UNIQUE KEY NOT NULL,
字段名 数据类型 UNIQUE KEY,
字段名 数据类型 NOT NULL DEFAULT 默认值
);
CREATE TABLE 表名称 (
字段名 数据类型 ,
字段名 数据类型 UNIQUE KEY AUTO_INCREMENT,
字段名 数据类型 NOT NULL DEFAULT 默认值,
PRIMARY KEY (字段名)
);

-- 举例
mysql> CREATE TABLE test1(
-> id INT PRIMARY KEY AUTO_INCREMENT,
-> name varchar(10)
-> );
Query OK, 0 rows affected (0.01 sec)

mysql> DESC test1;
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| id | int | NO | PRI | NULL | auto_increment |
| name | varchar(10) | YES | | NULL | |
+-------+-------------+------+-----+---------+----------------+
2 rows in set (0.00 sec)

mysql> INSERT INTO test1(name) VALUES('Tom'),('Jerry');
Query OK, 2 rows affected (0.01 sec)
Records: 2 Duplicates: 0 Warnings: 0

mysql> SELECT * FROM test1;
+----+-------+
| id | name |
+----+-------+
| 1 | Tom |
| 2 | Jerry |
+----+-------+
2 rows in set (0.00 sec)

mysql> INSERT INTO test1(id,name) VALUES(0,'Jack'),(NULL,'Bob');
Query OK, 2 rows affected (0.01 sec)
Records: 2 Duplicates: 0 Warnings: 0

mysql> SELECT * FROM test1;
+----+-------+
| id | name |
+----+-------+
| 1 | Tom |
| 2 | Jerry |
| 3 | Jack |
| 4 | Bob |
+----+-------+
4 rows in set (0.00 sec)

mysql> INSERT INTO test1(id,name) VALUES(10,'Juli');
Query OK, 1 row affected (0.01 sec)

mysql> INSERT INTO test1(name) VALUES('Lisa');
Query OK, 1 row affected (0.01 sec)

mysql> SELECT * FROM test1;
+----+-------+
| id | name |
+----+-------+
| 1 | Tom |
| 2 | Jerry |
| 3 | Jack |
| 4 | Bob |
| 10 | Juli |
| 11 | Lisa |
+----+-------+
6 rows in set (0.00 sec)
  • 创建表以后添加自增约束
1
ALTER TABLE 表名称 MODIFY 字段名 数据类型 AUTO_INCREMENT;

3.自增约束的删除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ALTER TABLE 表名称 MODIFY 字段名 数据类型 AUTO_INCREMENT;

-- 举例
mysql> ALTER TABLE test1 MODIFY id INT;
Query OK, 6 rows affected (0.03 sec)
Records: 6 Duplicates: 0 Warnings: 0

mysql> DESC test1;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | int | NO | PRI | NULL | |
| name | varchar(10) | YES | | NULL | |
+-------+-------------+------+-----+---------+-------+
2 rows in set (0.00 sec)

4.自增变量的持久化

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
CREATE TABLE test1(
id INT PRIMARY KEY AUTO_INCREMENT
);

INSERT INTO test1 VALUES(0),(0),(0),(0);

mysql> SELECT * FROM test1;
+----+
| id |
+----+
| 1 |
| 2 |
| 3 |
| 4 |
+----+
4 rows in set (0.00 sec)

DELETE FROM test1 WHERE id = 4;

INSERT INTO test1 VALUES(0);

mysql> SELECT * FROM test1;
+----+
| id |
+----+
| 1 |
| 2 |
| 3 |
| 5 |
+----+
4 rows in set (0.00 sec)

DELETE FROM test1 where id=5;

-- 重启数据库,重新插入一个空值
INSERT INTO test1 values(0);
mysql> SELECT * FROM test1;
+----+
| id |
+----+
| 1 |
| 2 |
| 3 |
| 4 |
+----+
4 rows in set (0.00 sec)

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

在MySQL 8.0版本中,上述测试步骤最后一步的结果如下:

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

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

六、外键约束

1.外键约束的特点

  • 从表的外键列,必须引用/参考主表的主键或唯一约束的列为什么?因为被依赖/被参考的值必须唯一
  • 在创建外键约束时,如果不给外键约束命名,默认名不是列名,而是自动产生一个外键名例如 student_ibfk_1,也可以手动指定外键约束名
  • 创建表时就指定外键约束的话,先创建主表,再创建从表
  • 删除表时,先删除从表(或先删除外键约束),再删除主表
  • 当主表的记录被从表参照时,主表的记录将不允许删除,如果要删除数据,需要先删除从表中依赖该记录的数据,然后才可以删除主表的数据
  • 在从表中指定外键约束,并且一个表可以建立多个外键约束
  • 从表的外键列与主表被参照的列名字可以不相同,但是数据类型必须一样,逻辑意义一致
  • 当创建外键约束时,系统默认会在所在的列上建立对应的普通索引,但是索引名是外键的约束名(根据外键查询效率很高)
  • 删除外键约束后,必须手动删除对应的索引

2.外键约束的添加

  • 创建表时添加外键约束
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
CREATE TABLE 主表名称(
字段1 数据类型 PRIMARY KEY,
字段2 数据类型
);

CREATE TABLE 从表名称(
字段1 数据类型 PRIMARY KEY,
字段2 数据类型,
[CONSTRAINT <外键约束名称>] FOREIGN KEY(从表的某个字段) REFERENCES 主表名(被参考字段)
);

-- 举例
mysql> CREATE TABLE dept (
-> did INT PRIMARY KEY, #部门编号
-> dname VARCHAR ( 50 ) #部门名称
-> );
Query OK, 0 rows affected (0.01 sec)

mysql> CREATE TABLE emp (
-> eid INT PRIMARY KEY, #员工编号
-> ename VARCHAR ( 5 ), #员工姓名
-> deptid INT, #员工所在的部门
-> FOREIGN KEY ( deptid ) REFERENCES dept ( did )
-> );
Query OK, 0 rows affected (0.02 sec)

mysql> DESC emp;
+--------+------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+------------+------+-----+---------+-------+
| eid | int | NO | PRI | NULL | |
| ename | varchar(5) | YES | | NULL | |
| deptid | int | YES | MUL | NULL | |
+--------+------------+------+-----+---------+-------+
3 rows in set (0.00 sec)
  • 创建表以后添加外键约束
1
2
ALTER TABLE 从表名 ADD [CONSTRAINT 约束名] FOREIGN KEY (从表的字段) REFERENCES 主表名(被引用
字段) [on update xx][on delete xx];

3.外键约束的演示

总结:约束关系是针对双方的!

  • 添加了外键约束后,主表的修改和删除数据受约束
  • 添加了外键约束后,从表的添加和修改数据受约束
  • 在从表上建立外键,要求主表必须存在
  • 删除主表时,要求从表先删除,或将从表中外键引用该主表的关系先删除
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
mysql> CREATE TABLE dept (
-> did INT PRIMARY KEY, #部门编号
-> dname VARCHAR ( 50 ) #部门名称
-> );
Query OK, 0 rows affected (0.01 sec)

mysql> CREATE TABLE emp (
-> eid INT PRIMARY KEY, #员工编号
-> ename VARCHAR ( 5 ), #员工姓名
-> deptid INT, #员工所在的部门
-> FOREIGN KEY ( deptid ) REFERENCES dept ( did )
-> );
Query OK, 0 rows affected (0.02 sec)

mysql> DESC emp;
+--------+------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+------------+------+-----+---------+-------+
| eid | int | NO | PRI | NULL | |
| ename | varchar(5) | YES | | NULL | |
| deptid | int | YES | MUL | NULL | |
+--------+------------+------+-----+---------+-------+
3 rows in set (0.00 sec)

mysql> INSERT INTO dept VALUES(1001,'教学部'),(1002,'财务部'),(1003,'招生办');
Query OK, 3 rows affected (0.01 sec)
Records: 3 Duplicates: 0 Warnings: 0

mysql> INSERT INTO emp VALUES(1,'Tom',1001),(2,'Jerry',1002);
Query OK, 2 rows affected (0.01 sec)
Records: 2 Duplicates: 0 Warnings: 0

mysql> SELECT * FROM dept;
+------+--------+
| did | dname |
+------+--------+
| 1001 | 教学部 |
| 1002 | 财务部 |
| 1003 | 招生办 |
+------+--------+
3 rows in set (0.00 sec)

mysql> SELECT * FROM emp;
+-----+-------+--------+
| eid | ename | deptid |
+-----+-------+--------+
| 1 | Tom | 1001 |
| 2 | Jerry | 1002 |
+-----+-------+--------+
2 rows in set (0.00 sec)

mysql> INSERT INTO emp VALUES(3,'Jack',1000);
ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`atguigudb`.`emp`, CONSTRAINT `emp_ibfk_1` FOREIGN KEY (`deptid`) REFERENCES `dept` (`did`))

mysql> UPDATE emp SET deptid=1000 WHERE eid=1;
ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`atguigudb`.`emp`, CONSTRAINT `emp_ibfk_1` FOREIGN KEY (`deptid`) REFERENCES `dept` (`did`))

mysql> DELETE FROM dept WHERE did=1001;
ERROR 1451 (23000): Cannot delete or update a parent row: a foreign key constraint fails (`atguigudb`.`emp`, CONSTRAINT `emp_ibfk_1` FOREIGN KEY (`deptid`) REFERENCES `dept` (`did`))

mysql> UPDATE dept SET did=1000 WHERE did=1001;
ERROR 1451 (23000): Cannot delete or update a parent row: a foreign key constraint fails (`atguigudb`.`emp`, CONSTRAINT `emp_ibfk_1` FOREIGN KEY (`deptid`) REFERENCES `dept` (`did`))

-- 合理的情况
mysql> UPDATE emp SET deptid=1002 WHERE eid=1;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0

mysql> DELETE FROM dept WHERE did=1003;
Query OK, 1 row affected (0.01 sec)

4.外键约束的约束等级

  • Cascade方式 :在父表上update/delete记录时,同步update/delete子表的匹配记录
  • Set null方式 :在父表上update/delete记录时,将子表上匹配记录的列设为NULL,但是要注意子表的外键列不能为NOT NULL
  • No action方式 :如果子表中有匹配的记录,则不允许对父表对应候选键进行update/delete操作
  • Restrict方式 :同No action, 都是立即检查外键约束
  • Set default方式 (在可视化工具SQLyog中可能显示空白):父表有变更时,子表将外键列设置成一个默认的值,但Innodb不能识别

如果没有指定等级,就相当于 Restrict 方式。 对于外键约束,最好是采用: ON UPDATE CASCADE ON DELETE RESTRICT的方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
mysql> CREATE TABLE dept (
-> did INT PRIMARY KEY, #部门编号
-> dname VARCHAR ( 50 ) #部门名称
-> );
Query OK, 0 rows affected (0.01 sec)

mysql> CREATE TABLE emp (
-> eid INT PRIMARY KEY, #员工编号
-> ename VARCHAR ( 5 ), #员工姓名
-> deptid INT, #员工所在的部门
-> FOREIGN KEY ( deptid ) REFERENCES dept ( did ) ON UPDATE CASCADE ON DELETE SET NULL
-> );
Query OK, 0 rows affected (0.01 sec)

mysql>
mysql> INSERT INTO dept VALUES(1001,'教学部'),(1002,'财务部'),(1003,'招生办');
Query OK, 3 rows affected (0.00 sec)
Records: 3 Duplicates: 0 Warnings: 0

mysql> INSERT INTO emp VALUES(1,'Tom',1001),(2,'Jerry',1002);
Query OK, 2 rows affected (0.00 sec)
Records: 2 Duplicates: 0 Warnings: 0

mysql> UPDATE dept SET did=1004 WHERE did=1001;
Query OK, 1 row affected (0.01 sec)
Rows matched: 1 Changed: 1 Warnings: 0

mysql> SELECT * FROM dept;
+------+--------+
| did | dname |
+------+--------+
| 1002 | 财务部 |
| 1003 | 招生办 |
| 1004 | 教学部 |
+------+--------+
3 rows in set (0.00 sec)

mysql> SELECT * FROM emp;
+-----+-------+--------+
| eid | ename | deptid |
+-----+-------+--------+
| 1 | Tom | 1004 |
| 2 | Jerry | 1002 |
+-----+-------+--------+
2 rows in set (0.00 sec)

mysql> DELETE FROM dept WHERE did=1004;
Query OK, 1 row affected (0.00 sec)

mysql> SELECT * FROM dept;
+------+--------+
| did | dname |
+------+--------+
| 1002 | 财务部 |
| 1003 | 招生办 |
+------+--------+
2 rows in set (0.00 sec)

mysql> SELECT * FROM emp;
+-----+-------+--------+
| eid | ename | deptid |
+-----+-------+--------+
| 1 | Tom | NULL |
| 2 | Jerry | 1002 |
+-----+-------+--------+
2 rows in set (0.00 sec)

5.外键约束的删除

1
2
3
4
5
6
7
-- 第一步先查看约束名和删除外键约束
SELECT * FROM information_schema.table_constraints WHERE table_name = '表名称'; #查看某个表的约束名
ALTER TABLE 从表名 DROP FOREIGN KEY 外键约束名;

-- 第二步查看索引名和删除索引(注意,只能手动删除)
SHOW INDEX FROM 表名称; #查看某个表的索引名
ALTER TABLE 从表名 DROP INDEX 索引名;

6.开发场景

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

答:不是的

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

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

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

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

答:没有

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

7.开发规范

强制 】不得使用外键与级联,一切外键概念必须在应用层解决。

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

七、检查约束

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

1
2
3
4
5
6
CREATE TABLE employee(
eid int PRIMARY KEY,
ename varchar(5),
gender char CHECK (gender IN ('男','女')),
height INT CHECK (height > 100)
);

八、默认值约束

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

1.默认值约束的添加

  • 创建表时添加默认值约束
1
2
3
4
5
6
CREATE TABLE 表名称(
字段名 数据类型 PRIMARY KEY,
字段名 数据类型 UNIQUE KEY NOT NULL,
字段名 数据类型 UNIQUE KEY,
字段名 数据类型 NOT NULL DEFAULT 默认值,
);
  • 创建表以后添加默认值约束
1
ALTER TABLE 表名称 MODIFY 字段名 数据类型 DEFAULT 默认值;

2.默认值约束的删除

1
ALTER TABLE 表名称 MODIFY 字段名 数据类型;