目录
前言
not null约束
default约束
同时设置not null约束和default约束
comment约束
zerofill约束
primary key约束(又称主键约束)
复合主键约束
auto_increment约束(又称自增长约束)
unique约束(又称唯一键约束)
foreign key约束(又称外键约束)
在<
然后要知道的是,光有数据类型的约束还不够,MySQL为了更好地保证数据的合法性,为了更好地从业务逻辑角度上保证数据的正确性,为了尽可能保证数据安全,减少用户的误操作可能性,于是MySQL中出现了表的约束这种概念。
说一下,如下图所示,不要认为一片空白就表示空,一片空白可能是一个空字符,可能是一个空字符串,它们其中都是有值的,比如都有一个\0,而只有NULL才表示空,才表示这个位置上没有任何值。
在MySQL中默认是允许一个属性的值为空的,但在实际开发中我们要尽可能保证某个属性的值不为空,因为空值无法参与运算,比如通过select可以看到null的值为null。如下:
由于空值无法参与运算,因此null值加一后得到的还是null。如下:
如果不允许某个属性的值为空,在创建表的时候就可以给该属性设置not null约束。
比如说,如下图1所示,我们创建一个班级表,表当中包含班级名class_name属性和该班级所在的教室class_room属性,因为在插入一条数据时,不管是class_name属性还是class_room属性,都不允许为空,所以我们在创建表时就需要给这两个属性设置not null约束。
创建表成功后desc查看表结构,如下图1红框处所示,可以看到Null约束上的值是NO,这就证明了我们已经成功地给class_name属性和class_room属性设置了not null约束,往后用户在插入数据时就必须给这两个属性设置一个值了,所以如下图2所示,向表中插入数据时只有这两个属性的值都不为空时才能插入成功,否则将会插入失败(如果没有not null约束,则我们无视其中一个也是能成功完成插入的)。
如果表中的某个属性会经常性的等于某个值,那么就可以将这个值设置成该属性的默认值(或者说将这个值设置成该属性的default约束)。这样一来,每次向表中插入数据时就可以不用再手动设置这个属性的值了,因为向表中插入数据时如果不手动给带有default约束(或者说带有默认值)的属性赋值,那么MySQL就会使用default约束上的值自动完成对该属性的赋值。
举个例子,如下:
这时再向表中插入数据时,如果不指明用户的年龄(即如果不指明age属性的值),如果不指明用户的性别(即不指明gender属性的值),那么就会使用对应的default约束字段上的值;反之如果指明了就会使用用户指定的值。如下:
是可以给一个属性同时设置not null约束和default约束的,如下图所示,创建表完毕后查看表结构,可以看到id属性对应的Null约束字段上的值是NO而不是YES,说明设置not null约束成功,id属性的值已经不允许为空了;并且可以看到id属性对应的Default约束字段上的值是0而不是NULL,说明设置default约束也成功了,id属性已经具有默认值0了。
此时在向表中插入数据时可以不指明id属性的值进行插入,此时会使用id属性的默认值。如下:
问题:一般来说,一旦给某一属性设置了默认值,该属性将不会等于空(即NULL),因为就算插入数据时没有指明该属性的值,MySQL也会使用该属性的默认值自动完成插入。问题来了,给某一属性设置not null约束是为了约束该属性的值不能为NULL,而在前面又说过一旦给某一属性设置了默认值,该属性将不会等于空(即NULL),这样一看,好像给一个属性设置了default约束后,再设置not null约束就没有意义了,那为什么还要同时设置not null约束和default约束呢?
答案:因为有一种特殊情况,比如给一个属性设置了default约束后,如果不设置not null约束,那么用户是可以指定把这个属性的值设置成NULL的,如下图就是只给属性id设置了default约束,没有设置not null约束,所以最后如下图红框处所示,是可以把id属性的值手动设置成NULL的。也正是因为存在这种特殊情况,所以上一段中的开头部分才只是说【一般来说】。
在创建表时可以给各个属性设置comment描述,其作用一般是给程序员或DBA了解表中的各个属性表示什么含义,相当于一种注释。
比如如下图所示,在创建一个用户表user时,在表中设置用户名(即name属性)、用户的年龄(即age属性)和用户的性别(即gender属性)时,就可以在每一个属性后面添加上对应的comment注释。创建表完毕后,通过【show create table 表名】即可看到曾经创建表时所写的SQL语句和comment注释。
问题:
答案如下:
为什么comment注释也是一种约束呢?比如说,其实从上一段中我们就理应能体会出答案,比如在上一段中的comment注释就能够提示用户在插入数据时不应该把一个属性的值设置成多少,这本质上就是对用户的一种约束。说一下,可以看到comment注释只是一种软性约束,而并不是一种强制性约束,比如说如果程序员或者DBA非要把18岁以上的用户插入到MySQL中,那么MySQL也没办法,它只能照办。
如下图所示,我们创建一个表,在创建表时设置一个类型为int的属性a并设置一个类型为int unsigned的属性b,创建完后desc查看表的结构,如下图红框处所示可以发现int后面有个(11),unsigned int后面有个(10),这是什么东西呢?
先说一下,如果在设置一个int、bigint、tinyint等类型的属性时,没有给该属性设置zerofill约束,则这些类型后面的圆括号加数字是没有意义的(即不会起到任何作用),比如上图红框处的int(11)和int(10)unsigned,因为在上图中create创建表时、在设置属性a和属性b时,没有为a和b设置zerofill约束,所以(11)和(10)就没有意义,不会起任何作用。
然后要说的是,圆括号中的数字,比如上图红框处的int(11)中的(11),其代表的是显示宽度。给int、bigint、tinyint等数值类型的属性设置zerofill约束后,往后在select查表时,如果该属性的值的宽度小于设定的宽度,则MySQL在显示该属性的值时会自动在其的高位填充0直到补齐宽度;如果该属性的值的宽度大于设定的宽度,则MySQL在显示该属性的值时就正常显示。咱们来实操证明一下本段和上一段的理论,如下:
需要注意的是,zerofill约束的作用仅仅只是让某属性的值以特定的方式进行显示,该属性的值并没有发生变化,比如说虽然上图中属性b的值显示的是00001,但通过hex函数可以看到在底层实际储存的属性b的值依然是1。如下:
问题:如下图所示,在创建表时,在设置int类型的属性a和unsigned int类型的属性b时明明没有指定它们的显示宽度,但创建表完成后,通过desc查看表结构时如下图红框处所示,却发现a竟然有显示宽度11,b也有显示宽度为10,这是什么情况呢?
答案:如果在创建表设置int、bigint、tinyint类型的属性时不指明该属性的值显示宽度,则MySQL会自动根据该属性的类型,把该属性的值的显示宽度设置成默认值。至于为什么unsigned int的默认显示宽度是10,是因为unsigned int类型的最大值是42亿9千万,它就是一个10位数的整数,用10个宽度即可表示;而int的默认显示宽度是11是因为相比于unsigned int,int多一个符号位,比如在-2147483647中,2147483647共有10位,再加上符号-,所以需要用10+1=11个宽度表示。
走到这里就讲完了zerofill约束,说一下,该约束是不怎么常用的。
就像在现实中有一种【在10w人中找1个特定的人】的需求,在MySQL中也是有一种【在海量的数据中找1条特定的数据】的需求的,而就像在现实中找一个特定的人我们至少要知道一个独属于他的特征(比如身份证能唯一标识这个人,我们就可以通过身份证来区分、来找这个人),在MySQL中找一个特定的数据我们也至少要知道一个独属于该条数据的特征。那么如何知道呢?
答案就是通过primary key约束(又称主键约束)。主键约束的介绍如下:
咱们来实操演示一下primary key主键约束,如下:
创建一个学生表,表当中包含学生的学号(id属性)和姓名(name属性),由于学生的学号(id属性)是不应该重复的,因此可以给id属性设置主键约束。如下:
如下图所示,创建表成功后desc查看表结构,可以看到id属性对应的Key字段上的值是PRI,这表示我们已经为id属性设置主键约束成功了。此外可以看到,虽然在创建表的时候没有给id属性设置not null约束,但因为被设置了主键约束的属性的值不能为NULL,所以MySQL会自动为该属性设置not null约束,所以下图中id属性对应的Null字段上的值依然是NO,表示id属性的值不能为NULL。
根据上面的理论我们可知,如果插入一条数据时,该数据在被设置了主键约束的属性id上的值和表中已有的数据的属性id的值重复,这时就会因为主键冲突而插入失败。如下:
使用SQL语句【alter table 表名 drop primary key】即可删除指定表的某个属性的主键约束,注意想要删除某个属性的主键约束时只需要指明要删除主键约束的是哪张表即可,不需要指明是哪个属性。如下图所示,这里删除属性id的主键约束后再desc查看表结构,可以看到属性id对应的Key字段上的PRI已经没有了,这就证明了删除主键约束成功。
需要注意的是,虽然该属性id的主键约束被删除了,但因为曾经主键约束被设置而导致跟着被设置的not null约束并没有被随之删除,下图中属性id对应的Null字段上的值依然是NO即可证明这一点。
注意,对于已经创建好了的、没有设置主键约束的表,如果想给该表的某个属性增设主键约束,则可以使用SQL语句alter table 表名 add primary key(属性名); 。但注意,此时想要成功为某个属性增设主键约束,是需要条件的,即【在该表所有的数据中,不能有一条数据在该属性上的值等于另一条数据,也不能有一条数据在该属性上的值等于NULL】,如果不符合条件,则设置主键约束就会失败。如下图红框处所示,可以看到我们表中的数据是符合条件的,所以重新为属性id增设主键约束是可以成功的,属性id对应的Key字段上的值是PRI即可证明重新增设主键约束成功。
说一下,在上文中描述的所有关于主键约束的内容都是针对【在一张表中,只给表中的某一个属性设置主键约束】这种情况。而现在我们要知道的是,可以给表中的一个属性设置主键约束,也可以给表中的多个属性设置主键约束(复合主键约束)。
比如说如果只给一个属性设置主键约束,那么每一条数据只需要在这一个属性上的值不重复,且不为NULL即可;而如果给表中的多个属性设置主键约束,则说明该表中的主键约束是复合主键约束,则每一条数据只需要在多个属性中有一个属性上的值不重复(比如每条数据都有name、sex、idcard共3个属性,并且3个属性都还设置了主键约束时,数据1和数据2可以在name和sex属性上的值相等,但此时在idcard属性上的值就不能再相等了),且每条数据的多个属性都不为NULL即可。
咱们来实操演示一下复合主键约束,如下:
创建一个进程表process_table,表当中包含进程的IP地址(即属性ip)、端口号(即属性port)和进程的相关信息(即属性info),并为属性ip和属性port设置复合主键约束,如下:
表创建完毕后desc查看表结构,可以看到属性ip和属性port对应的Key字段上的值都是PRI,这说明设置主键约束成功。并且还可以看到属性ip和属性port对应的Null字段上的值都是NO,这说明属性ip和属性port的值都是不允许为空的(给某属性设置主键约束成功后,该属性的值就不能为NULL了,所以MySQL会自动为该属性设置not null约束,所以属性ip和属性port对应的Null字段上的值都才是NO)。如下:
根据上面的理论我们可知,在向进程表中插入数据时,只有插入的数据在属性ip和属性port上的值全部和表中已有的数据冲突时才会产生主键冲突,否则就允许插入。可以看到下图中的情况也的确符合这个理论,比如只有红框处的insert into语句会失败就是因为插入的数据在属性ip和属性port上的值全部和黄框中的相等、冲突;而其他insert into语句因为只有一个属性的值互相冲突或者所有属性的值都互相不冲突,所以就能成功插入。
如下图select查看表中插入的数据时我们也可以看到,表当中有重复的IP地址,也有重复的端口号,但是不会出现IP和端口均重复的,这就是复合主键。
使用SQL语句【alter table 表名 drop primary key】即可删除指定表的复合主键,注意想要删除某些属性的复合主键约束时只需要指明要删除复合主键约束的是哪张表即可,不需要指明是哪些属性。如下图所示,这里删除属性ip和属性port的复合主键约束后再desc查看表结构,可以看到属性ip和属性port对应的Key字段上的PRI已经没有了,这就证明了删除复合主键约束成功。
需要注意的是,虽然属性ip和属性port的复合主键约束被删除了,但因为曾经复合主键约束被设置而导致跟着被设置的not null约束并没有被随之删除,下图中属性ip和属性port对应的Null字段上的值依然是NO即可证明这一点。
注意,对于已经创建好了的、没有设置复合主键约束的表,如果想给该表的某些属性增设复合主键约束,则可以使用SQL语句alter table 表名 add primary key(属性名1、属性名2,,属性名n); 。但注意,此时想要成功为某些属性增设复合主键约束,是需要条件的,即【在该表所有的数据中,不能有一条数据在这些属性上的值完全等于另一条数据(即在多个属性中至少要有一个属性的值不等于另一条数据的对应属性),也不能有一条数据在这些属性上的值等于NULL(即在多个属性中,不能有任何一个属性的值是NULL)】,如果不符合条件,则设置复合主键约束就会失败。如下图红框处所示,可以看到我们表中的数据是符合条件的,所以重新为属性ip和属性port增设复合主键约束是可以成功的,属性ip和属性port对应的Key字段上的值都是PRI即可证明重新增设复合主键约束成功。
如下图所示,创建一个表,在创建表时设置属性id和属性name并为属性id设置主键约束和自增长约束(这里为属性id设置主键约束是因为如果不设置主键约束,则无法为属性id设置自增长约束)。创建表完毕后desc查看表结构,可以看到属性id对应的Extra字段上的值为auto_increment,这就证明了设置自增长约束成功。
向表中插入第一条数据时如果没有指明被设置了自增长约束的属性id的值,那么属性id的值将会默认等于1,而不等于NULL。如下:
后续向表中插入数据时如果也不指明被设置了自增长约束的属性id的值,那么属性id的值就会依次递增1。如下:
如下图所示,如果向表中插入数据时指明了被设置了自增长约束的属性id的值,则此时就会插入指定的值,但注意指明的值不能和表中已有的值重复。
如下图所示,如果此后向表中插入数据时又不指明被设置了自增长约束的属性id的值,那么属性id的值又将会依次递增1。
问题:MySQL为什么能知道属性id的值自增长到哪了呢?是靠遍历整个表格知道的吗?
答案:不是靠遍历,这样效率太低了。实际上如果给表中的某个属性设置自增长约束后,MySQL是会给这张表设置一个计数器的,如下图红框处所示,使用show create table查看表的创建语句就可以看到该计数器,该计数器上的值就是【下一次插入数据时,如果不指明属性id的值,MySQL会自动给属性id设置的值】,也正是因为有这个计数器的存在,所以MySQL才能知道属性id的值自增长到了哪。
最后要知道的是:
唯一键用于在所有数据中唯一标识一条数据,比如说如果给某个属性设置了唯一键约束,则在整个表中,每条数据在该属性字段上的值一定是不相同的,即每条数据在该属性字段上的值一定是唯一的。
通过上一段的描述可以发现唯一键约束的作用和主键约束的作用是一模一样的,都是用于唯一标识一条数据,那么问题来了,通过主键约束我们已经能唯一标识一条数据了,为什么还要有唯一键约束的存在呢?
答案是,因为在实际业务中光有主键约束可能是不够的,还需要有唯一键约束来完善业务逻辑。举个例子,如下。
那么在上面的案例中,我们怎样就能解决这个问题呢?答案就是给属性qq设置唯一键约束,这样一来,曾经教务系统管理员在向数据库中插入数据时,如果误操作把两个同学的QQ号输成了一个,那么数据库就会报错、不让管理员插入该数据,这样一来后序辅导员也就不会遇到两个同学的QQ号相同的情况了,问题也就解决了。说一下,至于为什么不给属性qq设置主键约束以解决问题,是因为属性stu_id已经是主键了,在一张表中不能有两个主键约束。那可不可以给属性qq和属性stu_id设置复合主键约束呢?答案是也不行,因为复合主键约束是允许【两条数据在属性stu_id上的值不相等、在属性qq上的值相等】的情况发生的。
说一下,从上文中我们也能感受到为什么把唯一键称为一种约束,即就是因为,为某个属性设置唯一键约束后,如果用户在插入数据时该数据在该属性上的值和其他数据重复,则MySQL会不让用户完成插入,以此约束用户。
然后要知道的是,唯一键约束和主键约束都能保证数据的唯一性,它们的唯一区别在于:给某个属性A设置主键约束后,该属性A的值是不能为NULL的;但如果只是给某个属性A设置唯一键约束,该属性A上的值是可以为NULL的(那么多条数据在属性A上的值都是NULL,是否是一种不唯一、是否违反了唯一键约束呢?这里要说的是NULL不做唯一性比较,所以是不影响的)。所以以后我们就要知道,如果给一个属性A设置唯一键约束后再为其设置not null约束,那么即使因为已经有其他主键的存在导致属性A没有主键之名,属性A也是有主键之实的。从这里我们也就能感受到,主键具有唯一性并不是因为它是主键,而是因为,是某个唯一键被选择成为了主键。
最后咱们来实操演示一下unique唯一键约束,如下:
如下图所示,创建一个学生表,表中包含学生的学号、姓名和QQ号,我们为属性id设置主键约束,但同时因为每个学生的QQ号也应该具有唯一性,所以我们也为属性qq设置唯一键约束。
如下图所示,表创建完毕后desc查看表结构,可以看到属性qq对应的Key字段上的值是UNI,这就说明给属性qq设置唯一键成功了。
如下图所示,向表中插入数据时,如果该数据在qq属性上的值和表中已有的数据相同,则当前要插入的这条数据就会因为唯一键冲突而插入失败。
此外,因为在上文中说过【如果只是给某个属性设置唯一键约束,该属性上的值是可以为NULL的】,所以如下图所示,向表中插入数据时,可以不指明该数据在qq属性上的值,此时qq属性的值就会默认为NULL;当然也可以指明该数据在qq属性上的值为NULL,此时qq属性上的值也会为NULL。
如下图所示是一张学生表,该学生表中包含学号、学生姓名、班级id,班级名,老师1、老师2、老师3等信息。仔细观察我们能发现该表存在一个问题,只要两个学生属于同一个班级,那么如下图两个红框处所示,这两个学生在红框中的信息就会完全一样,而我们要知道,一个班级中是会有很多学生的,这就会造成一个问题,即该班中的学生越多,如下图红框处这样的重复信息就会越多。注意如果在MySQL中发生了这样的情况,那就是很不合理的,因为这样会很浪费MySQL的空间资源。
那如何解决上面的问题呢?答案如下图所示,我们可以创建两张表,一张学生表,一张班级表,然后让两张表通过属性class_id(即班号)关联起来,可以发现这样一来,无论在C++大牛班有多少名学生,都不会像上面那样增加一名学生就在上图的学生表中增加一行上图红框处的信息,而是永远只有一行上图红框处的信息被保存在下图的班级表中。可以看到通过这样的方式,就可以大大节省MySQL的空间,也就解决了上面的问题。
(结合上图思考)说一下,走到这里,上面的这种解决方案存在一个问题,那就是学生表和班级表之间虽然具有联系,但它们之间还不存在约束。举两个例子,如下。
可以看到,在上面例举的两个例子中,即使用户的行为是不符合现实中的逻辑的,但因为学生表和班级表之间还不存在约束,所以用户依然可以随意操作,不会受到MySQL的约束。问题来了,如果因为你MySQL不进行约束,导致用户误操作后出现多张表的数据不一致、数据不完整的问题,进而导致给用户造成麻烦、造成损失了,那事后怪谁呢?毕竟虽然用户的确是粗心误操作了,但你MySQL为什么不警告提示我呢?所以MySQL为了不背黑锅,就提供了一种叫做外键约束的约束来解决这个问题。
那么如何解决该问题呢?咱们直接实操演示一下外键约束。
如下图所示,因为是在演示如何解决上面的问题,所以这里咱们创建和上文中一样的班级表和学生表,同时因为上文中的班级表和学生表是通过属性class_id(即班号)被用户关联起来的,所以这里给两张表设置外键约束时要给属性class_id设置外键约束(准确地说是只给学生表中的属性class_id设置外键约束,无需给班级表中的class_id设置外键约束,这是因为班级表是主表,学生表是从表,详情在下文中再说)。创建表后再desc查看表结构,可以看到学生表中的属性class_id对应的Key字段上的值是MUL,这就证明了设置外键约束成功。
此时因为刚创建完两个表的表结构,两个表中的内容都还是空,就包括班级表,最重要的是又因为在学生表中对属性class_id设置了外键约束,所以如下图红框处所示,此时直接向学生表中插入班号class_id为10的学生Jay是会直接报错的,因为班级表中还不存在任何班级。如下图蓝框处所示,只有先向班级表中插入class_id为10的班级,才能插入该学生Jay。
如下图红框处所示,因为曾经咱们在学生表中对属性class_id设置了外键约束,所以在学生表中还有class_id为10的学生Jay的情况下,此时是无法直接在班级表中把class_id为10的C++大牛班给删除的。如下图蓝框处所示,只有先在学生表中把所有class_id为10的学生删除,然后才能在班级表中把class_id为10的C++大牛班给删除。
走到这里我们可以发现,在设置外键约束后,曾经在上文中可以做的不符合现实逻辑的操作,我们现在就无法再做了,会受到MySQL的约束,所以也就解决了上文中的问题。
解决上文的问题后,我们再对外键约束补充一些细节。
任意两张表在通过某个相同的属性被关联起来时,两个表之间是有一个主表和从表的关系的,一般来说,如果表A中的数据依赖于表B中的数据(即如果表B中的数据不存在,那么表A中的数据就无法存在,比如说在上文中如果班级表中的class_id为10的班级不存在,则在学生表中class_id为10的学生也就无法存在;如果表B中的数据不存在,那么表A就无法完成数据的插入,比如说如果商品表中的goods_id为1的商品不存在,那么在购物车表中就无法插入goods_id为1的商品),那么表B就是主表,表A就是从表(所以在上文的案例中班级表就是主表,学生表是从表)。
然后要知道的是,我们只能在从表中给【关联着两张表的属性】设置外键约束,并且如果想在从表中设置外键约束成功还有一个前提,就是主表中的【关联着两张表的属性】被设置了唯一键约束或者主键约束。
然后注意,当从表中的【关联着两张表的属性】没有被设置not null约束时,该属性上的值是可以为NULL的;当主表中的【关联着两张表的属性】被设置主键约束时,因为在上文中说过被设置了主键约束的属性的值不能为NULL,所以这时主表中的【关联着两张表的属性】就不能为NULL。但如果主表中的【关联着两张表的属性】只是被设置了唯一键约束,并且没有设置not null约束,那么这时主表中的【关联着两张表的属性】就能为NULL。如何证明本段理论呢?
如下图所示,咱们直接把之前创建的学生表和班级表拿来用,可以发现无论是在主表班级表中,还是在从表学生表中,【关联着两张表的属性】class_id都是可以为NULL的,这就证明了上一段的段理论是正确的。