MySQL————表的约束
1.概念
无约束写入(如文件编辑)可随意操作,但数据库插入数据必须依赖约束机制。例如 “年龄” 列填性别、“身高” 列录异常值、“学号” 列填无关信息等问题,即便非主观疏忽,也可能在批量录入时出现,就像 Excel 录入易出错一样。而 SQL 约束能在数据插入违规时直接报错,从根源保障数据安全准确。
表中一定要有各种约束,通过约束可以使我们插入的数据是符合预期的。
约束本质是通过技术手段倒逼程序员插入正确的数据,反过来,站在mysql的视角,凡是插入进来的数据都是符合数据约束的。
约束的最终目标:保证数据的完整性和可预期性。
2.空属性
- 两个值:null(默认的)和 not null(不为空)
- 数据库默认字段基本都是字段为空。但是实际开发时,尽可能保证字段不为空,因为数据为空没办法参与运算。
案例:
- 如果班级没有名字,你不知道你在哪个班级
- 如果教室名字可以为空,就不知道在哪上课
mysql> create table if not exists myclass(-> class_name varchar(20) not null,-> class_room varchar(20) not null,-> other varchar(20)-> );
Query OK, 0 rows affected (1.80 sec)mysql> desc myclass;
+------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+-------------+------+-----+---------+-------+
| class_name | varchar(20) | NO | | NULL | |
| class_room | varchar(20) | NO | | NULL | |
| other | varchar(20) | YES | | NULL | |
+------------+-------------+------+-----+---------+-------+
3 rows in set (0.02 sec)mysql> show create table myclass\G
*************************** 1. row ***************************Table: myclass
Create Table: CREATE TABLE `myclass` (`class_name` varchar(20) NOT NULL,`class_room` varchar(20) NOT NULL,`other` varchar(20) DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.02 sec)mysql> insert into myclass (class_name,class_room,other) values('高三2班','101','普通班');
Query OK, 1 row affected (0.12 sec)mysql> insert into myclass (class_name,class_room,other) values('高三1.班','102');
ERROR 1136 (21S01): Column count doesn't match value count at row 1mysql> insert into myclass (class_name,class_room) values('高三1班','102');
Query OK, 1 row affected (0.12 sec)mysql> select * from myclass;
+------------+------------+-----------+
| class_name | class_room | other |
+------------+------------+-----------+
| 高三2班 | 101 | 普通班 |
| 高三1班 | 102 | NULL |
+------------+------------+-----------+
2 rows in set (0.03 sec)
3.默认值
案例:
mysql> create table if not exists t11(-> name varchar(20) not null,-> age tinyint unsigned default 18,-> gender char(1) default '男'-> );
Query OK, 0 rows affected (0.59 sec)mysql> desc t11;
+--------+---------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+---------------------+------+-----+---------+-------+
| name | varchar(20) | NO | | NULL | |
| age | tinyint(3) unsigned | YES | | 18 | |
| gender | char(1) | YES | | 男 | |
+--------+---------------------+------+-----+---------+-------+
3 rows in set (0.00 sec)mysql> insert into t11 (name,age,gender) values('lynn',19,'女');
Query OK, 1 row affected (0.07 sec)mysql> insert into t11 (name) values('sam');
Query OK, 1 row affected (0.17 sec)mysql> select * from t11;
+------+------+--------+
| name | age | gender |
+------+------+--------+
| lynn | 19 | 女 |
| sam | 18 | 男 |
+------+------+--------+
2 rows in set (0.00 sec)
not null和default同时设置时,若插入数据时未明确指定某一列的值,系统会自动使用该列的default默认值填充;但如果建表时这一列既没设置default默认值,又被限定了not null(非空),就会因无法生成合法值而插入失败。
mysql> create table t12(-> name varchar(20) not null,-> age tinyint default 18,-> gender char(1) not null default '男'-> );
Query OK, 0 rows affected (0.56 sec)mysql> desc t12;
+--------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| name | varchar(20) | NO | | NULL | |
| age | tinyint(4) | YES | | 18 | |
| gender | char(1) | NO | | 男 | |
+--------+-------------+------+-----+---------+-------+
3 rows in set (0.12 sec)mysql> insert into t12 (name,age,gender) values(NULL,20,'男');
ERROR 1048 (23000): Column 'name' cannot be null
mysql> insert into t12 (age,gender) values(20,'男');
ERROR 1364 (HY000): Field 'name' doesn't have a default value
mysql> insert into t12 (name,age,gender) values(NULL,20,'男','NULL');
ERROR 1136 (21S01): Column count doesn't match value count at row 1
mysql> insert into t12 (name,age) values('lynn',20);
Query OK, 1 row affected (0.09 sec)
如果我们在建表时没有指定默认值,mysql会给我们进行优化,带上default null。
4.列描述
mysql> create table tt12 (
-> name varchar(20) not null comment '姓名',
-> age tinyint unsigned default 0 comment '年龄',
-> sex char(2) default '男' comment '性别'
-> );
mysql> desc tt12;
+-------+---------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+---------------------+------+-----+---------+-------+
| name | varchar(20) | NO | | NULL | |
| age | tinyint(3) unsigned | YES | | 0 | |
| sex | char(2) | YES | | 男 | |
+-------+---------------------+------+-----+---------+-------+
mysql> show create table t16\G
*************************** 1. row ***************************Table: t16
Create Table: CREATE TABLE `t16` (`name` varchar(20) NOT NULL COMMENT '用户名',`age` tinyint(3) unsigned DEFAULT '18' COMMENT '年龄',`gender` char(1) DEFAULT '男' COMMENT '性别'
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)
5.zerifill
mysql> create table if not exists t17(-> a int unsigned not null,-> b int unsigned not null-> );
Query OK, 0 rows affected (0.63 sec)mysql> desc t17;
+-------+------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+------------------+------+-----+---------+-------+
| a | int(10) unsigned | NO | | NULL | |
| b | int(10) unsigned | NO | | NULL | |
+-------+------------------+------+-----+---------+-------+
2 rows in set (0.10 sec)mysql> show create table t17\G
*************************** 1. row ***************************Table: t17
Create Table: CREATE TABLE `t17` (`a` int(10) unsigned NOT NULL,`b` int(10) unsigned NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.08 sec)
我们插入数据试试:
mysql> insert into t17 (a,b) values(1,2);
Query OK, 1 row affected (0.06 sec)mysql> select * from t17;
+---+---+
| a | b |
+---+---+
| 1 | 2 |
+---+---+
1 row in set (0.00 sec)
mysql> alter table t17 modify b int unsigned zerofill;
Query OK, 0 rows affected (1.65 sec)
Records: 0 Duplicates: 0 Warnings: 0mysql> show create table t17\G
*************************** 1. row ***************************Table: t17
Create Table: CREATE TABLE `t17` (`a` int(10) unsigned NOT NULL,`b` int(10) unsigned zerofill DEFAULT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)
mysql> select * from t17;
+---+------------+
| a | b |
+---+------------+
| 1 | 0000000002 |
+---+------------+
1 row in set (0.00 sec)
mysql> select b,hex(b) from t17;
+------------+--------+
| b | hex(b) |
+------------+--------+
| 0000000002 | 2 |
+------------+--------+
1 row in set (0.00 sec)
6.主键
案例:
mysql> create table if not exists test_key( id int unsigned primary key comment '学号', name varchar(20)) not null );
Query OK, 0 rows affected (0.57 sec)mysql> desc test_key;
+-------+------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+------------------+------+-----+---------+-------+
| id | int(10) unsigned | NO | PRI | NULL | |#key 中 pri表示该字段是主键
| name | varchar(20) | NO | | NULL | |
+-------+------------------+------+-----+---------+-------+
2 rows in set (0.10 sec)
mysql> insert into test_key values(1,'lynn');
Query OK, 1 row affected (0.07 sec)mysql> insert into test_key values(1,'lynn');
ERROR 1062 (23000): Duplicate entry '1' for key 'PRIMARY'
mysql> alter table test_key drop primary key;
Query OK, 1 row affected (3.57 sec)
Records: 1 Duplicates: 0 Warnings: 0mysql> insert into test_key values(1,'lynn');
Query OK, 1 row affected (0.07 sec)mysql> select * from test_key;
+----+------+
| id | name |
+----+------+
| 1 | lynn |
| 1 | lynn |
+----+------+
2 rows in set (0.01 sec)
添加主键:
mysql> select * from test_key;
+----+------+
| id | name |
+----+------+
| 1 | sam |
| 1 | lynn |
+----+------+
2 rows in set (0.05 sec)mysql> alter table test_key add primary key(id);
ERROR 1062 (23000): Duplicate entry '1' for key 'PRIMARY' #字段添加主键之前需要保证该字段下没有重复的值
mysql> delete from test_key where name='sam'; #删除重复字段
Query OK, 1 row affected (0.07 sec)mysql> alter table test_key add primary key(id); #添加主键
Query OK, 0 rows affected (1.43 sec)
Records: 0 Duplicates: 0 Warnings: 0
复合主键:一个主键不仅可以被添加到一列,还可以添加到多列上。
mysql> create table pick_course(-> id int unsigned,-> course_id int unsigned comment '课程编号',-> score tinyint unsigned comment '课程分数',-> primary key (id,course_id) #id和course_id成为复合主键-> );
Query OK, 0 rows affected (0.79 sec)mysql> desc pick_course;
+-----------+---------------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------+---------------------+------+-----+---------+-------+
| id | int(10) unsigned | NO | PRI | NULL | | #这两列合成主键
| course_id | int(10) unsigned | NO | PRI | NULL | |
| score | tinyint(3) unsigned | YES | | NULL | |
+-----------+---------------------+------+-----+---------+-------+
3 rows in set (0.09 sec)mysql> insert into pick_course values(1234,40,90);
Query OK, 1 row affected (0.07 sec)mysql> insert into pick_course values(1234,41,90); #同一个学号可以选择不同的课程
Query OK, 1 row affected (0.09 sec)mysql> insert into pick_course values(1230,40,95); #不同的学号可以选择同一个课程
Query OK, 1 row affected (0.08 sec)mysql> insert into pick_course values(1234,40,95); #同一个学号不可以重复选择同一个课程
ERROR 1062 (23000): Duplicate entry '1234-40' for key 'PRIMARY'
7.自增长
auto_increment:当对应的字段不给值,会自动的被系统触发,系统会从当前字段中已经有的最大值进行 +1操作得到一个新的不同的值。通常和主键搭配使用,作为逻辑主键。
自增长的特点:
- 任何一个字段要做自增长,前提是本身是一个索引(key一栏有值)
- 自增长字段必须是整数
- 一张表最多只能有一个自增长
案例:
mysql> create table if not exists tt21(-> id int unsigned primary key auto_increment,-> name varchar(20) not null-> );
Query OK, 0 rows affected (0.67 sec)mysql> show create table tt21\G
*************************** 1. row ***************************Table: tt21
Create Table: CREATE TABLE `tt21` (`id` int(10) unsigned NOT NULL AUTO_INCREMENT,`name` varchar(20) NOT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
1 row in set (0.00 sec)mysql> desc tt21;
+-------+------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+------------------+------+-----+---------+----------------+
| id | int(10) unsigned | NO | PRI | NULL | auto_increment |
| name | varchar(20) | NO | | NULL | |
+-------+------------------+------+-----+---------+----------------+
2 rows in set (0.00 sec)mysql> insert into tt21 (name) values('lynn');
Query OK, 1 row affected (0.05 sec)mysql> insert into tt21 (name) values('sam');
Query OK, 1 row affected (0.07 sec)mysql> select * from tt21;
+----+------+
| id | name |
+----+------+
| 1 | lynn |
| 2 | sam |
+----+------+
2 rows in set (0.06 sec)
当我们在插入时自定义被自增长修饰的字段值,该字段会按自定义值设置,但若之后再以自增方式插入数据,则会从现有最大值开始自增。它的增长值会依据表结构中AUTO_INCREMENT参数的设定来实现增长。
mysql> insert into tt21 (id,name) values(11,'tom');
Query OK, 1 row affected (0.07 sec)mysql> select * from tt21;
+----+------+
| id | name |
+----+------+
| 1 | lynn |
| 2 | sam |
| 11 | tom |
+----+------+
3 rows in set (0.00 sec)mysql> insert into tt21 (name) values('amy');
Query OK, 1 row affected (0.06 sec)mysql> select * from tt21;
+----+------+
| id | name |
+----+------+
| 1 | lynn |
| 2 | sam |
| 11 | tom |
| 12 | amy |
+----+------+
4 rows in set (0.01 sec)mysql> insert into tt21 (id,name) values(5,'jack');
Query OK, 1 row affected (0.10 sec)mysql> select * from tt21;
+----+------+
| id | name |
+----+------+
| 1 | lynn |
| 2 | sam |
| 5 | jack |
| 11 | tom |
| 12 | amy |
+----+------+
5 rows in set (0.00 sec)mysql> insert into tt21 (name) values('lin');
Query OK, 1 row affected (0.68 sec)mysql> select * from tt21;
+----+------+
| id | name |
+----+------+
| 1 | lynn |
| 2 | sam |
| 5 | jack |
| 11 | tom |
| 12 | amy |
| 13 | lin |
+----+------+
6 rows in set (0.00 sec)mysql> show create table tt21\G
*************************** 1. row ***************************Table: tt21
Create Table: CREATE TABLE `tt21` (`id` int(10) unsigned NOT NULL AUTO_INCREMENT,`name` varchar(20) NOT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8 #AUTO_INCREMENT为14,所以接下来默认是14
1 row in set (0.00 sec)
我们也可以一开始就手动设置初始增长值:
mysql> create table tt22(-> id int unsigned primary key auto_increment,-> name varchar(20) not null-> )auto_increment=50;
Query OK, 0 rows affected (2.94 sec)mysql> show create table tt22\G
*************************** 1. row ***************************Table: tt22
Create Table: CREATE TABLE `tt22` (`id` int(10) unsigned NOT NULL AUTO_INCREMENT,`name` varchar(20) NOT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=50 DEFAULT CHARSET=utf8 #从50开始自增长
1 row in set (0.00 sec)
在插入后获取上次插入的 AUTO_INCREMENT 的值(批量插入获取的是第一个值):
mysql> select last_insert_id();
+------------------+
| last_insert_id() |
+------------------+
| 13 |
+------------------+
1 row in set (0.00 sec)
索引其实是加速mysql查找的策略。
8.唯一键
主键与唯一键的区别:主键用于唯一标识表中的记录,且不允许为空值;唯一键仅用于保证字段值的唯一性,允许存在一个空值(NULL)。主键只能设置一个,但是唯一键可以设置多个。
我们可以简单理解成,主键更多的是标识唯一性的。而唯一键更多的是保证在业务上,不要和别的信息出现重复。
当需要修改用户密码时,会通过主键 user_id = 1001 精准找到目标用户;而当用户注册时,系统会通过唯一键校验 phone = 138xxxx1234 是否已存在,避免重复注册——这就是两者在实际业务中的分工差异。
mysql> create table student(-> id char(20) unique comment '学生唯一键',-> name varchar(32) not null,-> );
Query OK, 0 rows affected (0.69 sec)mysql> insert into student (id,name) values('12345','lynn');
Query OK, 1 row affected (0.06 sec)mysql> desc student;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | char(20) | YES | UNI | NULL | |
| name | varchar(32) | NO | | NULL | |
+-------+-------------+------+-----+---------+-------+
2 rows in set (0.08 sec)mysql> insert into student (id,name) values('12345','lynn');
Query OK, 1 row affected (0.06 sec)mysql> insert into student (id,name) values(NULL,'sam');
Query OK, 1 row affected (0.08 sec)mysql> insert into student (id,name) values(NULL,'sam');
Query OK, 1 row affected (0.74 sec)mysql> select * from student;
+-------+------+
| id | name |
+-------+------+
| 12345 | lynn |
| NULL | sam |
| NULL | sam |
+-------+------+
3 rows in set (0.00 sec)
9.外键
外键用于定义主表和从表之间的关系:外键约束主要定义在从表上,主表则必须是有主键约束或unique约束。当定义外键后,要求外键列数据必须在主表的主键列存在或为null。
语法:
foreign key (字段名) references 主表(列)
案例:
先创建主键表:
mysql> create table if not exists class(-> id int primary key,-> name varchar(32) not null-> );
Query OK, 0 rows affected (0.65 sec)
再创建从表:
mysql> create table if not exists student(-> id int unsigned primary key,-> name varchar(20) not null,-> talephone varchar(32) unique key,-> class_id int,-> foreign key(class_id) references class(id)-> );
Query OK, 0 rows affected (1.34 sec)
正常插入数据:
mysql> insert into class values (1,'通信101');
Query OK, 1 row affected (0.17 sec)mysql> insert into class values (2,'通信102');
Query OK, 1 row affected (0.13 sec)
各班级插入学生:
mysql> insert into student values (1,'张三','13899990000',1);
Query OK, 1 row affected (0.34 sec)mysql> insert into student values (2,'李四','13899990001',1);
Query OK, 1 row affected (0.04 sec)mysql> insert into student values (3,'王五','13899990002',2);
Query OK, 1 row affected (0.11 sec)mysql> insert into student values (4,'赵六','13899990003',2);
Query OK, 1 row affected (0.06 sec)mysql> select * from student;
+----+--------+-------------+----------+
| id | name | talephone | class_id |
+----+--------+-------------+----------+
| 1 | 张三 | 13899990000 | 1 |
| 2 | 李四 | 13899990001 | 1 |
| 3 | 王五 | 13899990002 | 2 |
| 4 | 赵六 | 13899990003 | 2 |
+----+--------+-------------+----------+
4 rows in set (0.00 sec)
外键约束阻止直接删除被引用的班级记录,删除引用该班级的学生记录后即可成功删除该班级。
mysql> delete from class where id=1;
ERROR 1451 (23000): Cannot delete or update a parent row: a foreign key constraint fails (`test_db`.`student`, CONSTRAINT `student_ibfk_1` FOREIGN KEY (`class_id`) REFERENCES `class` (`id`))
mysql> select * from student;
+----+--------+-------------+----------+
| id | name | talephone | class_id |
+----+--------+-------------+----------+
| 1 | 张三 | 13899990000 | 1 |
| 2 | 李四 | 13899990001 | 1 |
| 3 | 王五 | 13899990002 | 2 |
| 4 | 赵六 | 13899990003 | 2 |
+----+--------+-------------+----------+
4 rows in set (0.00 sec)mysql> delete from class where id=1;
ERROR 1451 (23000): Cannot delete or update a parent row: a foreign key constraint fails (`test_db`.`student`, CONSTRAINT `student_ibfk_1` FOREIGN KEY (`class_id`) REFERENCES `class` (`id`))
mysql> delete from student where class_id=1;
Query OK, 2 rows affected (0.12 sec)mysql> select * from student;
+----+--------+-------------+----------+
| id | name | talephone | class_id |
+----+--------+-------------+----------+
| 3 | 王五 | 13899990002 | 2 |
| 4 | 赵六 | 13899990003 | 2 |
+----+--------+-------------+----------+
2 rows in set (0.00 sec)mysql> delete from class where id=1;
Query OK, 1 row affected (0.12 sec)
首先我们承认,这个世界是数据很多都是相关性的。理论上,在上面的例子中,如果我们不创建外键约束,就正常建立学生表以及班级表,该有的字段我们都有。此时,在实际使用的时候,可能会出现什么问题?有没有可能插入的学生信息中有具体的班级,但是该班级却没有在班级表中?
比如某学校只开设了100班和101班,但是在上课的学生里面竟然有102班的学生(这个班目前并不存在),这很明显是有问题的。
因为此时两张表在业务上是有相关性时,但是在业务上没有建立约束关系,就可能出现问题。解决方案就是通过外键完成的。建立外键的本质其实就是把相关性交给mysql去审核了,提前告诉mysql表之间的约束关系,那么当用户插入不符合业务逻辑的数据的时候,mysql不允许插入。