一. 什么是约束?
在上一篇介绍数据类型时就引入“约束”这个概念,约束字段的是数据类型,但是数据类型的约束很单一,还需要有一些额外的约束以更好的保证数据的合法性,这篇文章将重点说明MySQL中的几种约束。
下面给出约束的定义:约束本质上是MySQL通过限制用户操作的方式,来达到维护数据安全的一套完整性方案,即约束的最终目的是从业务逻辑的角度保证数据的正确性、安全性。
二. 为什么要有约束?
我们知道MySQL是一套数据存储容灾解决方法,除了完成基本的数据存储功能之外,还要保证数据尽可能的安全,减少用户误操作的可能性。
三. 如何约束?
1. 空属性 — not null
我们在注册账号或填写用户信息时,有些属性是必填的,有些是选填的:
对应到数据库层面,我们在定义表中的每一个字段时,它们有两种可供选择的属性:null(默认的)和not null(不为空)。数据库字段的默认属性是null,即允许为空;但是实际开发时,尽可能保证字段不为空,因为数据为空没办法参与运算。
举例:
创建一张Person表,字段包括人的姓名、性别,且都允许为空:
创建一张PersonNotNull表,也包含两个字段:姓名、性别,但是不都允许为空:
2. 默认值 — default
通常我们在注册社交平台账号时,需要绑定你的电话号码,对于电话号码,不同的国家有不同的国际区号。比如中国的国际区号就是+86,如果这个社交媒体平台是中国本土的,则默认中国人使用的最多,所以默认区号就是+86了:
默认值:某一个字段会经常性的用到某个具体的值,我们可以一开始就指定好这个值,在对该字段插入时,用户可以直接使用这个默认值而不需要再去指定。
使用方法:定义字段时在后面加上default 默认值。
举例:
// 1、创建一张表 mysql> create table if not exists Person( -> name varchar(20), -> nationality varchar(20) default '中国', -> age tinyint unsigned default 0 -> ); Query OK, 0 rows affected (0.03 sec) // 2、国籍和年龄字段设定了默认值 mysql> desc Person; +-------------+---------------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------------+---------------------+------+-----+---------+-------+ | name | varchar(20) | YES | | NULL | | | nationality | varchar(20) | YES | | 中国 | | | age | tinyint(3) unsigned | YES | | 0 | | +-------------+---------------------+------+-----+---------+-------+ 3 rows in set (0.01 sec) // 3、插入一条数据,国籍和年龄字段使用默认值 mysql> insert into Person (name) values ('张三'); Query OK, 1 row affected (0.00 sec) mysql> select * from Person; +--------+-------------+------+ | name | nationality | age | +--------+-------------+------+ | 张三 | 中国 | 0 | +--------+-------------+------+ 1 row in set (0.00 sec)
问题:not null 和 default 同时设置,会发生什么情况?
下面这段代码对该问题进行了测试。我们建表时,同时设定nationality字段为not null和default;发现在插入数据时,即使不手动插入该字段的数据MySQL也不会报错,而且该字段最终采用了我们设定的默认值:
// 1、新建表,nationality字段同时设定了not null和 default mysql> create table if not exists Person( -> nationality varchar(20) not null default '中国', -> age tinyint unsigned -> ); Query OK, 0 rows affected (0.03 sec) // 2、插入一条数据,nationality字段不手动插入 mysql> insert into Person (age) values (18); Query OK, 1 row affected (0.01 sec) // 3、观察结果,nationality即使不手动插入也没有报错,且最终插入了默认值 mysql> select * from Person; +-------------+------+ | nationality | age | +-------------+------+ | 中国 | 18 | +-------------+------+ 1 row in set (0.00 sec)
总结:在not null和default同时设置的情况下,插入数据时我们依然可以对该字段不填而采用默认值,且不会报错。但不建议一起使用,通常在必须填入数据的字段中只用not null,在事先确定好具体默认值的情况下只用default。
3. 列描述 — comment
列描述(comment):用来描述某个字段的信息,作用相当于对该字段进行注释。它会保存在表的创建语句里,用来给程序员或DBA来进行字段的了解。
查看方式:列描述信息在用desc关键字查看表结构时不会显示,只能通过show查看表创建语句时出现:
// 1、新建表,每一个字段都用comment进行信息描述 mysql> create table if not exists Person( -> name varchar(20) comment '这是用户的姓名,必填', -> gender char(1) default '男' comment '这是用户的性别,默认为男性', -> age tinyint unsigned default 18 comment '这是用户的年龄,默认为18' -> ); Query OK, 0 rows affected (0.02 sec) // 2、通过desc查看不到描述信息 mysql> desc Person; +--------+---------------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +--------+---------------------+------+-----+---------+-------+ | name | varchar(20) | YES | | NULL | | | gender | char(1) | YES | | 男 | | | age | tinyint(3) unsigned | YES | | 18 | | +--------+---------------------+------+-----+---------+-------+ 3 rows in set (0.00 sec) // 3、通过show查看表的创建语句时可以看到描述信息 mysql> show create table Person \G *************************** 1. row *************************** Table: Person Create Table: CREATE TABLE `Person` ( `name` varchar(20) DEFAULT NULL COMMENT '这是用户的姓名,必填', `gender` char(1) DEFAULT '男' COMMENT '这是用户的性别,默认为男性', `age` tinyint(3) unsigned DEFAULT '18' COMMENT '这是用户的年龄,默认为18' ) ENGINE=InnoDB DEFAULT CHARSET=utf8 1 row in set (0.00 sec)
使用建议:列描述信息最好放到字段的最后位置写,因为它不像not null或default那样对字段具有很强的影响性,这样我们在查看时也更容易解读字段信息。
4. zerofill
到目前为止,我们学习到如下两种类型后面可以加圆括号:
字符类型:char(len)、varchar(len)。这里圆括号中的数字决定了该字段存储字符个数的上限。
数值类型:int(num)、tinyint(num)等。这里圆括号中的数字决定了显示数据的位宽,只有加上zerofill关键字才会起作用。
举例:
我们创建一张表,其中包含了两个int类型的字段a和b,规定它们的位宽都是10,但是只有b字段添加了关键字zerofill,然后向该表中插入一行记录,观察插入后数据的显示结果:
// 1、新建表 mysql> create table if not exists Number( -> a int(10), -> b int(10) zerofill -> ); Query OK, 0 rows affected (0.06 sec) // 2、观察表结构 mysql> desc Number; +-------+---------------------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------+---------------------------+------+-----+---------+-------+ | a | int(10) | YES | | NULL | | | b | int(10) unsigned zerofill | YES | | NULL | | +-------+---------------------------+------+-----+---------+-------+ 2 rows in set (0.00 sec) // 3、插入一行数据 mysql> insert into Number values (10, 20); Query OK, 1 row affected (0.00 sec) // 4、发现a字段的数据按插入的样子正常显示,b字段数据按位宽为10的格式显示 mysql> select * from Number; +------+------------+ | a | b | +------+------------+ | 10 | 0000000020 | +------+------------+ 1 row in set (0.00 sec)
可以看到b字段数据的值显示位宽是10,而a的值就是正常的显示。这就是zerofill属性的作用,如果数据本身宽度小于设定的宽度(这里设置的是10),就会自动填充0已达到设定位宽。要注意的是,这只是最后显示的结果,在MySQL中实际存储的还是20,我们可以用hex函数来证明,该函数是以16进制形式显示数值的:
mysql> select b, hex(b) from Number; +------------+--------+ | b | hex(b) | +------------+--------+ | 0000000020 | 14 | +------------+--------+ 1 row in set (0.00 sec)
可以看出MySQL内部存储的还是数字20,0000000020只是设置了zerofill属性后的一种格式化输出而已。
5. 主键 — primary key
主键(primary key)是一张表中唯一标识一行记录的字段。如果把表比作红黑树的话,表的每一行记录对应红黑树的每一个节点,表的主键字段就对应节点中的key,而表的其他字段对应节点中的value。
表的主键有如下三个特点:
一张表只有一个主键字段,该字段唯一标识表中的一行记录。
主键字段默认是not null属性。
主键字段的值不能重复。
举例:
复合主建
创建一个Address表,把ip字段和port字段组合成为复合主键,用来标识公网内唯一的一个进程:
// 1、新建表,复合主建单独一行写出来 mysql> create table if not exists Address( -> ip varchar(20) comment '主机的公网IP', -> port int unsigned comment '进程在主机内的唯一标识', -> name varchar(20) comment '主机用户名', -> primary key(ip, port) -> ); Query OK, 0 rows affected, 1 warning (0.00 sec) // 2、查看表结构,发现ip和port字段都标识了PRI mysql> desc Address; +-------+------------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------+------------------+------+-----+---------+-------+ | ip | varchar(20) | NO | PRI | NULL | | | port | int(10) unsigned | NO | PRI | NULL | | | name | varchar(20) | YES | | NULL | | +-------+------------------+------+-----+---------+-------+ 3 rows in set (0.00 sec)
只有复合主键中所有字段的值完全一致时,才算主键冲突:
mysql> insert into Address values ('127.0.1', 8080, '张三'); Query OK, 1 row affected (0.01 sec) mysql> insert into Address values ('127.0.1', 1111, '张三'); Query OK, 1 row affected (0.17 sec) mysql> select * from Address; +---------+------+--------+ | ip | port | name | +---------+------+--------+ | 127.0.1 | 1111 | 张三 | | 127.0.1 | 8080 | 张三 | +---------+------+--------+ 2 rows in set (0.00 sec)
主键修改操作
1、追加主键
如果表已经创建好但是没有主键的话,可以使用如下语句追加主键,但要确保追加成为主键的字段的数据唯一性和非空:
alter table 表名 add primary key(字段列表);
2、删除主键
一张表中只会有一个主键(普通主键或复合主建),所以在删除表的主键时不需要指明具体字段:
alter table 表名 drop primary key;
设置表主键的建议
一般而言,我们建议将主键设计成为和当前业务无关的字段,这样,当业务调整的时候,我们可以尽量不对主键做过大的调整。
6. 自增长 — auto_increment
auto_increment:设为自增长的字段必须和主键搭配使用。在插入时该字段可以不给值,这时自增长属性会自动的被系统触发,从当前表的自增长字段中已经有的最大值进行+1操作,得到一个新的且不同的值。
自增长字段的特点:
自增长字段必须是整数类型。
自增长字段必须是主键或复合主键中的一个字段。
一张表最多只能有一个自增长字段。
使用方法:在创建表时,在字段后加上auto_increment。如果是复合主键,则需要把自增长列放在复合主键的第一个位置,也就是最左边,如下面代码所示:
// 把复合主建中的a字段设为自增长 // 这样插入的话,只需要指定非自增长的字段的值即可 create table t( a int auto_increment, b int, primary key (a,b) );
举例:
创建一张学生表,把学号设为自增长,这样每次插入一行数据时只需指定学生姓名即可,他们的学号按自增长给出的值分配:
当前表中一行数据都没有,我们插入一个学生叫“张三”,学号使用自增长给的值,发现自增长初始时默认给值是数字1:
继续插入数据,观察到自增长给的值是在原来已经有的最大值基础上+1:
我们这次插入一行自己的“学号,姓名”记录,其中学号值为20204912,然后观察自增长值的后续变化:
发现自增长值的分配规律:按当前自增长字段的最大值+1分配。
7. 唯一键 — unique
一张表中有往往有很多字段需要保证唯一性,即在一张表中该字段的值不能重复,被设为主键的字段有唯一性的约束,但是一张表中只能有一个主键字段,那么其他需要保证唯一性的字段该怎么办呢?唯一键就可以解决表中有多个字段需要唯一性约束的问题。
比如在学校,我们需要一个学生管理系统,系统中有一个学生表,学生表中有两个字段,一个身份证号码,一个是学号,我们可以选择以身份号码作为主键。这样学号就只是一个普通的字段,MySQL没有对其提供一个安全性约束,万一不小心输入了重复的学号系统也检测不到,直到业务出问题后才改就很麻烦。所以我们设计学生学号的时候,需要一种约束:学生的学号都不能重复,我们可以在设计表的时候将学生学号设计成为唯一键。
唯一键设置方法:定义字段时加上unique关键字。
唯一键和主键的区别:
唯一键允许为空,且空字段不做为唯一性的判断依据。
主键的侧重点在于它作为一行数据的唯一标识,而唯一键的侧重点在于保证表中该字段数据的唯一性。
举例:
创建一张学信息表,其中把学号设为唯一键并通过一系列插入操作验证唯一键的特性:
// 1、创建一张学生信息表 mysql> create table if not exists Student( -> name varchar(20) not null comment '学生的姓名,不允许为空', -> st_id int unsigned unique comment '学生的学号,设为唯一键', -> telephone varchar(11) primary key comment '学生的电话号码,设为主键' -> ); Query OK, 0 rows affected (0.03 sec) // 2、观察表的结构,唯一键字段会被标识出UNI mysql> desc Student; +-----------+------------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-----------+------------------+------+-----+---------+-------+ | name | varchar(20) | NO | | NULL | | | st_id | int(10) unsigned | YES | UNI | NULL | | | telephone | varchar(11) | NO | PRI | NULL | | +-----------+------------------+------+-----+---------+-------+ 3 rows in set (0.00 sec) // 测试1:唯一键字段的数据不能重复 mysql> insert into Student (name, st_id, telephone) values ('张三', 20204912, '111xxx'); Query OK, 1 row affected (0.00 sec) mysql> insert into Student (name, st_id, telephone) values ('李四', 20204912, '222xxx'); ERROR 1062 (23000): Duplicate entry '20204912' for key 'st_id' // 测试二:唯一键字段可以为空 mysql> insert into Student (name, telephone) values ('张三', '222xxx'); Query OK, 1 row affected (0.01 sec) mysql> insert into Student (name, telephone) values ('李四', '333xxx'); Query OK, 1 row affected (0.01 sec) // 3、观察最终插入结果 mysql> select * from Student; +--------+----------+-----------+ | name | st_id | telephone | +--------+----------+-----------+ | 张三 | 20204912 | 111xxx | | 张三 | NULL | 222xxx | | 李四 | NULL | 333xxx | +--------+----------+-----------+ 3 rows in set (0.00 sec)
8. 外键 — foreign key
外键用于定义主表和从表之间的关系:外键定义在从表上,主表则必须是有主键约束或unique约束。
当定义外键后两张表就有了外键约束,要求从表外键字段的值必须存在于主表的主键或唯一键数据中,当然也可以为NULL。
理解外键:
外键设置方法:
在创建从表时单起一行来专门设置外键:
foreign key (从表里的字段名) references 主表名(主表里的字段名)
举例:
创建一张学生表,里面有学生的名字和班级号;再创建一张班级表,里面存有各个班级对应的科目信息。
用外键把学生表里的班级号和班级表里的班级号关联起来:
// 1、主表必须先创建出来 mysql> create table if not exists ClaTable( -> id int unsigned primary key comment '班级号', -> course varchar(20) not null comment '班级课程' -> ); Query OK, 0 rows affected (0.02 sec) // 2、从表在创建时可以选择自己的某个字段与主表中的字段建立外键约束关系 mysql> create table if not exists StuTable( -> name varchar(20) not null comment '学生姓名', -> class_id int unsigned comment '学生所在班级', -> foreign key(class_id) references ClaTable(id) -> ); Query OK, 0 rows affected (0.03 sec) // 3、观察主表结构,班级号设为了主键 mysql> desc ClaTable; +--------+------------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +--------+------------------+------+-----+---------+-------+ | id | int(10) unsigned | NO | PRI | NULL | | | course | varchar(20) | NO | | NULL | | +--------+------------------+------+-----+---------+-------+ 2 rows in set (0.00 sec) // 4、观察从表结构观,从表中的class_id字段键信息为MUL,代表它被设为了外键 mysql> desc StuTable; +----------+------------------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +----------+------------------+------+-----+---------+-------+ | name | varchar(20) | NO | | NULL | | | class_id | int(10) unsigned | YES | MUL | NULL | | +----------+------------------+------+-----+---------+-------+ 2 rows in set (0.00 sec)
我们先在主表ClaTable中插入两条班级相关的数据:
mysql> insert into ClaTable (id, course) values (401, '物理、化学、地理'); Query OK, 1 row affected (0.00 sec) mysql> insert into ClaTable (id, course) values (405, '历史、政治、生物'); Query OK, 1 row affected (0.00 sec) mysql> select * from ClaTable; +-----+--------------------------+ | id | course | +-----+--------------------------+ | 401 | 物理、化学、地理 | | 405 | 历史、政治、生物 | +-----+--------------------------+ 2 rows in set (0.00 sec)
接下来向从表StuTable中插入数据来验证外键的特点:
插入一个班级号为500班的学生,MySQL检测到主表中没有这个班级,所以插入失败,这是外键约束在起作用:
从表中外键字段的数据是允许为NULL的。在从表中插入班级id为null的一个学生,意为来了一个新学生,目前还没有分配班级:
理解外键和外键约束
还是上面的例子,我们不创建外键约束,就正常建立学生表,以及班级表,该有的字段我们都有,然后通过对照学生表和班级表的id信息也能让两张表关联起来,为什么还要有外键存在呢?
有没有可能插入的学生信息中不小心输错了班级号,但是班级表中并没有这个班级,这时MySQL是不会去检查的,因为两张表没有建立约束关系,这样在后面业务中可能出现问题。
解决方案就是建立两张表的外键约束关系。建立外键约束的本质其实就是把相关性交给MySQL去审核了,提前告诉MySQL表之间的约束关系,那么当用户插入不符合业务逻辑的数据的时候,MySQL就会检查出来,不允许你插入。