约束时一种限制,用于限制表中的数据,为了保证表中的数据的准确可靠性。
约束的分类:
- NOT NULL:非空约束,用于保证该字段的值不能为空,比如姓名、学号 DEFAULT:默认,用于保证该字段有默认值,比如性别
- PRIMARY KEY:主键,用于保证该字段的值具有唯一性
- UNIQUE:唯一,用于抱枕该字段的值具有唯一性,可以为空
- CHECK:检查约束(MySQL中不支持),比如限定年龄的范围
- FOREIGN KEY:外键约束,用于限定两个表的关系,用于保证该字段的值必须来自于主表的关联列的值。在从表中添加外键约束,引用从表的字段中的值。
添加约束的时机:
- 建表时添加约束
- 建表之后,数据添加之前,添加约束
约束的添加分类
- 列级约束:六大约束都可以写,但外键约束没有效果
- 表级约束:除了非空和默认,其它的都支持
7.1 创建表时添加约束
语法: ```sql CREATE TABLE stu_info( id INT PRIMARY KEY,# 唯一约束 stuName VARCHAR(20) NOT NULL ,# 非空约束 gender char(1) CHECK(gender=’男’ OR gender = ‘女’),#检查约束 stu_phone char(11) UNIQUE,#唯一约束 age INT DEFAULT 18 ,# 默认约束 major_id INT REFERENCES major(id)# 外键约束
) CREATE TABLE major( id INT PRIMARY KEY, majorName VARCHAR(20) ); DESC stu_info;
> 直接在字段名和类型后面追加约束类型即可。> 只支持:默认、非空、主键、唯一。> 外键约束添加在列上没有意义和效果,检查约束MySQL不支持。<a name="o3LjA"></a># 7.2 添加表级约束**语法:**```sqlCREATE TABLE stuinfo(id INT,stuname VARCHAR(20),gender char(1),seat INT,age INT,majorid int,## 开始添加约束CONSTRAINT pk primary KEY(id),#主键CONSTRAINT uq UNIQUE(seat),#唯一键CONSTRAINT ck CHECK(gender='男' OR gender='女'),CONSTRAINT fk_stuinfo_major FOREIGN KEY(majorid) REFERENCES major(id) #外键)
可以省略约束的名字,系统会按照规则赋予一个名字
CREATE TABLE stuinfo(id INT,stuname VARCHAR(20),gender char(1),seat INT,age INT,majorid int,## 开始添加约束primary KEY(id),#主键UNIQUE(seat),#唯一键CHECK(gender='男' OR gender='女'),FOREIGN KEY(majorid) REFERENCES major(id) #外键)
表级约束不支持非空和默认
总结
# 在各个字段的最下面【constraint 约束名】 约束类型(字段名)
7.3 通用的写法
CREATE TABLE IF NOT EXISTS stuinfo(id int primary key,stuname varchar(20) NOT NULL,gender char(1),seat INT UNIQUE,majorid int,CONSTRAINT fk_stuinfo_major FOREIGN KEY(majorid) REFERENCES major(id))
外键一般用表级约束,其他的约束一般用列级约束。
7.4 逐渐约束和唯一约束的对比
- 主键唯一非空;唯一约束,唯一,但是可以为空。且可以有多个空值(MySQL 5.7)
- 主键一张表中只能有一个;唯一约束一个表中可以有多个
可以有联合主键和联合唯一键。
CREATE TABLE stuinfo(id INT,stuname VARCHAR(20),gender char(1),seat INT,seat2 INT,age INT,## 开始添加约束primary KEY(id,stuname),#主键UNIQUE(seat,seat2),#唯一键CHECK(gender='男' OR gender='女'))
联合唯一约束中,null值还是可以重复,不论是一个null还是两个null。 联合主键中,必须两个一摸一样才会被限制插入,不然就视为不重复。
7.5 外键的特点
- 要求在从表设置外键关系
- 从表的外键列的类型和主表的关联列的类型要求一致或兼容
- 主表的关联列必须是一个key(主键约束的字段、唯一约束的字段),也就是关联唯一。
- 插入数据时,应该先插入主表,再插入从表(拿 专业—学生 举例,先添加专业才能添加专业中所属的学生)
- 删除数据时,先删除从表,再删除主表。
问题:为什么实际的业务场景中,不适合使用外键约束?
引言
其实这个话题是老生常谈,很多人在工作中确实也不会使用外键。包括在阿里的JAVA规范中也有下面这一条
【强制】不得使用外键与级联,一切外键概念必须在应用层解决。
但是呢,询问他们原因,大多是这么回答的
每次做DELETE 或者UPDATE都必须考虑外键约束,会导致开发的时候很痛苦,测试数据极为不方便。
坦白说,这么说也是对的。但是呢,不够全面,所以开一文来详细说明。
正文
首先我们明确一点,外键约束是一种约束,这个约束的存在,会保证表间数据的关系“始终完整”。因此,外键约束的存在,并非全然没有优点。
比如使用外键,可以
- 保证数据的完整性和一致性
- 级联操作方便
- 将数据完整性判断托付给了数据库完成,减少了程序的代码量
然而,鱼和熊掌不可兼得。外键是能够保证数据的完整性,但是会给系统带来很多缺陷。正是因为这些缺陷,才导致我们不推荐使用外键,具体如下
性能问题
假设一张表名为user_tb。那么这张表里有两个外键字段,指向两张表。那么,每次往user_tb表里插入数据,就必须往两个外键对应的表里查询是否有对应数据。如果交由程序控制,这种查询过程就可以控制在我们手里,可以省略一些不必要的查询过程。但是如果由数据库控制,则是必须要去这两张表里判断。
并发问题
在使用外键的情况下,每次修改数据都需要去另外一个表检查数据,需要获取额外的锁。若是在高并发大流量事务场景,使用外键更容易造成死锁。
扩展性问题
这里主要是分为两点
- 做平台迁移方便,比如你从
Mysql迁移到Oracle,像触发器、外键这种东西,都可以利用框架本身的特性来实现,而不用依赖于数据库本身的特性,做迁移更加方便。 - 分库分表方便,在水平拆分和分库的情况下,外键是无法生效的。将数据间关系的维护,放入应用程序中,为将来的分库分表省去很多的麻烦。
技术问题
使用外键,其实将应用程序应该执行的判断逻辑转移到了数据库上。那么这意味着一点,数据库的性能开销变大了,那么这就对DBA的要求就更高了。很多中小型公司由于资金问题,并没有聘用专业的DBA,因此他们会选择不用外键,降低数据库的消耗。
相反的,如果该约束逻辑在应用程序中,发现应用服务器性能不够,可以加机器,做水平扩展。如果是在数据库服务器上,数据库服务器会成为性能瓶颈,做水平扩展比较困难。
6. 修改表时添加约束
语法
#列级约束ALTER TABLE 表名 MODIFY COLUMN 列名 列类型 约束;#表级约束ALTER TABLE 表名 ADD 约束名(列名);
举例
CREATE TABLE stuinfo(id INT,stuname VARCHAR(20),gender char(1),seat INT)#列级约束的写法ALTER TABLE stuinfo MODIFY COLUMN id INT PRIMARY KEY;ALTER TABLE stuinfo MODIFY COLUMN stuname VARCHAR(20) UNIQUE;#表级约束的写法ALTER TABLE stuinfo ADD UNIQUE(seat);# 添加外键,添加外键时,必须保证主表存在,并且关联的字段是唯一字段,并且类型一致或者兼容ALTER TABLE stuinfo ADDCONSTRAINT fk_stuinfo_majorFOREIGN KEY(majorid) REFERENCES major(id);
7. 删除约束
语法
##删除非空ALTER TABLE stuinfo MODIFY stuname VARCHAR(20) null;## 删除默认约束ALTER TABLE stuinfo MODIFY COLUMN age int;##删除主键ALTER TABLE stuinfo DROP PRIMARY KEY;## 删除唯一键ALTER TABLE stuinfo DROP INDEX seat;## 删除外键约束ALTER TABLE stuinfo DROP FOREIGN KEY fk_stuinfo_major
7.6 标识列
又称为自增长列 含义:可以不用手动的插入值,系统提供默认的序列值
创建表时设置标识列
CREATE TABLE stuinfo(id INT PRIMARY KEY auto_increment,stuname VARCHAR(20),gender char(1),seat INT);INSERT INTO stuinfo VALUES(null,'l1','男',10);INSERT INTO stuinfo VALUES(null,'l2','男',10);INSERT INTO stuinfo VALUES(null,'l3','男',10);INSERT INTO stuinfo VALUES(null,'l4','男',10);
设置自增长的步长
# 查看自增长的起始值 offset 和步长 incrementSHOW VARIABLES LIKE '%auto_increment%';# 起始值不可被修改,但是步长可以被修改SET auto_increment_increment=3;
需要删除之前表中的全部数据,才会有这样的效果。
特点:
- 标识列必须是一个键,主键或者是唯一键
- 仅仅只能有一个增长列
- 标识列的类型必须是数值类型(小数和整数都可以)
- 标识列可以设置步长,但是不支持设置起始值,不过可以通过手动插入第一条数据的时候给予值的形式来给定值。
修改表时设置标识列
ALTER TABLE stuinfo MODIFY COLUMN id INT auto_increment;
修改表时删除标识列
ALTER TABLE stuinfo MODIFY COLUMN id INT;
