【SQL】之约束
- 约束分类
- 面试题目
约束:为保证数据完整性,对表中字段的限制。
如何添加/删除约束?
create table 时添加约束
alter table 时添加约束,删除约束
如何查看表中约束?
SELECT * FROM information_schema.`TABLE_CONSTRAINTS`
WHERE table_name='employees';
约束分类
- 单列约束,多列约束
- 列级约束:将此约束声明在对应字段的后面
表级约束:在表中所有字段都声明完,在所有字段的后面声明的约束 - not null 非空约束
unique 唯一性约束
primary key 主键约束
foreign key 外键约束
check 检查约束
default 默认值约束
一、非空约束 NOT NULL
限定某个字段/某列的值不允许为空。
关键字:not null
create table 时添加约束
#create table 时添加约束
CREATE DATABASE dbtest;
USE dbtest;
CREATE TABLE test1(
id INT NOT NULL,
last_name VARCHAR(15) NOT NULL,
email VARCHAR(25),
salary DECIMAL(10,2)
);
DESC test1;
INSERT INTO test1(id,last_name,email,salary)
VALUES(1,'tom','tom@163.com',6000);
#错误
INSERT INTO test1(id,last_name,email,salary)
VALUES(1,NULL,'tom@163.com',6000);
INSERT INTO test1(id,last_name,email,salary)
VALUES(NULL,'jerry','tom@163.com',6000);
INSERT INTO test1(id,email)
VALUES(2,'abc@163.com'); #Field 'last_name' doesn't have a default value
alter table 时添加/删除约束
#alter table 时添加约束
SELECT * FROM test1;
DESC test1;
ALTER TABLE test1
MODIFY email VARCHAR(25) NOT NULL;
#alter table 时删除约束
ALTER TABLE test1
MODIFY email VARCHAR(25) NULL;
二、唯一性约束 UNIQUE
限制某个字段或某列的值不能重复
关键字:unique
表级约束:constraint 约束名称 关键字(字段名)
,如:CONSTRAINT uk_test2_email UNIQUE(email)
create table时添加约束
-- 在create table时添加约束
CREATE TABLE test2(
id INT UNIQUE, #(列级约束)
last_name VARCHAR(20),
email VARBINARY(25),
salary DECIMAL(10,2),
#表级约束
CONSTRAINT uk_test2_email UNIQUE(email)
);
#在创建唯一约束时,如果不给唯一约束命名,就默认和列明相同
DESC test2;
SELECT * FROM information_schema.`TABLE_CONSTRAINTS`
WHERE table_name='test2';
#向表中添加数据
INSERT INTO test2(id,last_name,email,salary)
VALUES(1,'tom','tom@163.com',4500);
SELECT * FROM test2;
#错误
INSERT INTO test2(id,last_name,email,salary)
VALUES(1,'tom1','tom@163.com',4500);
#可以向声明为unique的字段上添加null值,且可以多次添加null值
INSERT INTO test2(id,last_name,email,salary)
VALUES(2,'tom1',NULL,4600);
INSERT INTO test2(id,last_name,email,salary)
VALUES(3,'tom2',NULL,4500);
alter table时添加约束
-- alter table时添加约束
DESC test2;
UPDATE test2
SET salary=5000
WHERE id=3;
#方式1 add
ALTER TABLE test2
ADD CONSTRAINT uk_test2_sal UNIQUE(salary);
#方式2 modify
ALTER TABLE test2
MODIFY last_name VARCHAR(15) UNIQUE;
复合的唯一性约束
只要两个唯一性约束的字段的值不完全相同,就能添加成功。
-- 复合的唯一性约束
CREATE TABLE `user`(
id INT,
`name` VARCHAR (15),
`password` VARCHAR(25),
#表级约束
CONSTRAINT uk_user_name_pwd UNIQUE(`name`,`password`)
);
#插入数据
INSERT INTO `user`
VALUES(1,'tom','abc');
#成功
INSERT INTO `user`
VALUES(1,'tom1','abc');
SELECT * FROM `user`;
#复合的唯一性约束案例:
#学生表
CREATE TABLE student(
sid INT, #学号
sname VARCHAR(20), #姓名
tel CHAR(11) UNIQUE KEY, #电话
cardid CHAR(18) UNIQUE KEY #身份证号
);
#课程表
CREATE TABLE course(
cid INT, #课程编号
cname VARCHAR(20) #课程名称
);
#选课表
CREATE TABLE student_course(
id INT,
sid INT,#学号
cid INT,#课程编号
score INT,
UNIQUE KEY(sid,cid) #复合唯一
);
INSERT INTO student VALUES(1,'张三','13710011002','101223199012015623');#成功
INSERT INTO student VALUES(2,'李四','13710011003','101223199012015624');#成功
INSERT INTO course VALUES(1001,'Java'),(1002,'MySQL');#成功
SELECT * FROM student;
SELECT * FROM course;
INSERT INTO student_course VALUES
(1, 1, 1001, 89),
(2, 1, 1002, 90),
(3, 2, 1001, 88),
(4, 2, 1002, 56);#成功
SELECT * FROM student_course;
#学号,课程编号相同,已有该课程的成绩,就不能再录入
#Duplicate entry '2-1002' for key 'student_course.sid'
INSERT INTO student_course
VALUES(5,2,1002,67);
删除唯一性约束
- 添加唯一性约束的列上也会自动创建唯一索引。
- 删除唯一约束只能通过删除唯一索引的方式删除。
- 删除时需要指定唯一索引名,唯一索引名就和唯一约束名一样。
- 如果创建唯一约束时未指定名称,如果是单列,就默认和列名相同;如果是组合列,那么默认和()中排在第一个的列名相同。也可以自定义唯一性约束名。
#查看索引
SELECT * FROM information_schema.`TABLE_CONSTRAINTS`
WHERE table_name='test2';
#删除唯一性约束:
DESC test2;
#删除唯一性索引
ALTER TABLE test2
DROP INDEX last_name;
ALTER TABLE test2
DROP INDEX uk_test2_sal;
三、主键约束 PRIMARY KEY
用来唯一标识表中的一行记录。
关键字:primary
主键约束相当于唯一约束+非空约束的组合,主键约束不允许重复,也不允许出现空值。
一个表只能有一个主键约束。
根据主键索引查询数据,效率最高。
create table时添加约束
-- 主键约束
-- create table时添加约束
CREATE TABLE test3(
id INT PRIMARY KEY, #列级约束
last_name VARCHAR(15),
salary DECIMAL(10,2),
email VARCHAR(25)
);
CREATE TABLE test4(
id INT,
last_name VARCHAR(15),
salary DECIMAL(10,2),
email VARCHAR(25),
#表级约束
CONSTRAINT pk_test4_id PRIMARY KEY(id) #没有必要起名字
);
SELECT * FROM information_schema.`TABLE_CONSTRAINTS`
WHERE table_name='test4';
INSERT INTO test3
VALUES(1,'tom',3000,'tom@163.com');
SELECT * FROM test3;
复合主键约束
-- 复合主键约束:主键约束对应着表中的一列或者多列
CREATE TABLE user1(
id INT,
NAME VARCHAR(15),
PASSWORD VARCHAR(25),
PRIMARY KEY (NAME,PASSWORD)
);
INSERT INTO user1
VALUES(1,'tom','abc');
INSERT INTO user1
VALUES(1,'tom1','abc');
#多列组合的复合主键约束,这些列都不允许为空值,并且组合的值不允许重复
SELECT * FROM user1;
alter table时添加/删除约束
-- alter table时添加约束
CREATE TABLE test(
id INT,
last_name VARCHAR(15),
salary DECIMAL(10,2),
email VARCHAR(25)
);
DESC test;
ALTER TABLE test
ADD PRIMARY KEY (id);
-- 删除主键约束(实际开发中不会删除主键约束!)
ALTER TABLE test
DROP PRIMARY KEY;
自增列 AUTO_INCREMENT
某个字段的值自增。
关键字:auto_increment
(1)一个表最多只能有一个自增长列
(2)当需要产生唯一标识符或顺序值时,可设置自增长
(3)自增长列约束的列必须是键列(主键列,唯一键列)
(4)自增约束的列的数据类型必须是整数类型
(5)如果自增列指定了 0 和 null,会在当前最大值的基础上自增;如果自增列手动指定了具体值,直接赋值为具体值。
create table时添加
-- 自增列
#某个字段的值自增
-- create table时添加
CREATE TABLE test2(
id INT PRIMARY KEY AUTO_INCREMENT,
last_name VARCHAR(15)
);
#开发中,一旦主键作用的字段上声明有auto_increment,则我们在添加数据时,就不要给主键对应的字段赋值。
INSERT INTO test2(last_name)
VALUES('tom');
#多次执行上述添加语句,会以id自增添加多行
SELECT * FROM test2;
#当我们的主键的字段上添加0或null时,实际上会自动的往上添加指定的字段的数值
INSERT INTO test2(id,last_name)
VALUES(0,'tom');
INSERT INTO test2(id,last_name)
VALUES(NULL,'tom');
#指明一个值,这个值在表中没有出现过,则可以成功
INSERT INTO test2(id,last_name)
VALUES(10,'tom');
INSERT INTO test2(id,last_name)
VALUES(-10,'tom');
#在此之上继续执行:
INSERT INTO test2(last_name)
VALUES('tom');#继续累加 11
alter table时添加
-- alter table时添加
CREATE TABLE test3(
id INT PRIMARY KEY,
last_name VARCHAR(15)
);
DESC test3;
ALTER TABLE test3
MODIFY id INT AUTO_INCREMENT;
-- 删除
ALTER TABLE test3
MODIFY id INT;
mysql8.0新特性:自增变量的持久化
有如下场景:
向表中添加id字段,作为主键,设置auto_increment -> 添加数据values(0),(0),(0),(0) -> 此时表中应显示 id 1 2 3 4 -> 删除 where id=4 -> 再查看表中 id 1 2 3 -> 添加values(0) -> 表中id 1 2 3 5(从结果可以看出,虽然删除了id为4的记录,但是再次插入空值时,并没有重用被删除的4,而是分配了5。) -> 删除 where id=5 -> 重启数据库 -> 插入一个空值values(0) ->
【mysql5.7版本中:】
id 1 2 3 4
从结果可以看出,新插入的0值分配的是4,按照重启前的操作逻辑,此处应该分配6。
出现上述结果的主要原因是自增主键没有持久化。 在MySQL 5.7系统中,对于自增主键的分配规则,是由InnoDB数据字典内部一个计数器来决定的,而该计数器只在内存中维护,并不会持久化到磁盘中。当数据库重启时,该计数器会被初始化。
【mysql8.0版本中:】
id 1 2 3 6
从结果可以看出,自增变量已经持久化了。
MySQL 8.0将自增主键的计数器持久化到重做日志中。每次计数器发生改变,都会将其写入重做日志中。如果数据库重启,InnoDB会根据重做日志中的信息来初始化计数器的内存值。
四、外键约束 FOREIGN KEY
限定某个表的某个字段的引用完整性。
关键字:FOREIGN KEY
表级约束:CONSTRAINT 约束名 FOREIGN KEY (字段x) REFERENCES 主表(字段y)
【注意:】
- 从表的外键列,必须引用/参考主表的主键或唯一约束的列
- 创建(CREATE)表时就指定外键约束的话,先创建主表,再创建从表
- 删表时,先删从表(或先删除外键约束),再删除主表
- 在创建外键约束时,如果不给外键约束命名,默认名不是列名,而是自动产生一个外键名(例如student_ibfk_1;),也可以指定外键约束名。
- 当创建外键约束时,系统默认会在所在的列上建立对应的普通索引。但是索引名是外键的约束名。(根据外键查询效率很高)
- 删除外键约束后,必须手动删除对应的索引
create table时添加
-- 外键约束
#create table时添加
#创建主表
CREATE TABLE detp1(
dept_id INT,#无主键
dept_name VARCHAR(15)
);
#创建从表
CREATE TABLE emp1(
emp_id INT PRIMARY KEY AUTO_INCREMENT,
emp_name VARCHAR(15),
department_id INT,
#表级约束
CONSTRAINT fk_emp1_dept_id FOREIGN KEY (department_id) REFERENCES detp1(dept_id)
);#主表无主键,失败
ALTER TABLE detp1
MODIFY dept_id INT PRIMARY KEY; #添加主键后,emp1创建成功
DESC detp1;
DESC emp1;
#外键效果
INSERT INTO emp1
VALUES(1001,'tom',10); #添加失败,没有10号部门
#主表中添加10号部门
INSERT INTO detp1
VALUES(10,'it');
INSERT INTO emp1
VALUES(1001,'tom',10); #添加成功
#失败情况:删改父表的10号部门
DELETE FROM detp1
WHERE dept_id=10;
UPDATE FROM detp1
SET dept_id=20
WHERE dept_id=10;
alter table时添加
CREATE TABLE detp2(
dept_id INT PRIMARY KEY,
dept_name VARCHAR(15)
);
CREATE TABLE emp2(
emp_id INT PRIMARY KEY AUTO_INCREMENT,
emp_name VARCHAR(15),
department_id INT
);
#用表级约束添加外键
ALTER TABLE emp2
ADD CONSTRAINT fk_emp2_detp_id FOREIGN KEY(department_id) REFERENCES detp2(dept_id);
SELECT * FROM information_schema.`TABLE_CONSTRAINTS`
WHERE table_name='emp2';
约束等级
Cascade
方式:在父表上update/delete记录时,同步update/delete子表的匹配记录Set null
方式:在父表上update/delete记录时,将子表上匹配记录的列设为null,但是要注意子表的外键列不能为not nullNo action
方式:如果子表中有匹配的记录,则不允许对父表对应候选键进行update/delete操作Restrict
方式:同no action, 都是立即检查外键约束Set default
方式(在可视化工具SQLyog中可能显示空白):父表有变更时,子表将外键列设置成一个默认的值,但Innodb不能识别
如果没有指定等级,就相当于Restrict方式。
对于外键约束,最好是采用: ON UPDATE CASCADE ON DELETE RESTRICT
的方式。
create table dept(
did int primary key, #部门编号
dname varchar(50) #部门名称
);
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
#把修改操作设置为级联修改等级,把删除操作设置为set null等级
);
删除外键约束
-- 删除外键约束
#一个表中可以声明有多个外键约束
SELECT * FROM information_schema.table_constraints
WHERE table_name='emp1';
#删除外键约束
ALTER TABLE emp1
DROP FOREIGN KEY fk_emp1_dept_id;
#再手动删除外键约束对应的普通索引
SHOW INDEX FROM emp1;
ALTER TABLE emp1
DROP INDEX fk_emp1_dept_id;
开发场景
在 MySQL 里,外键约束是有成本的,需要消耗系统资源。对于大并发的 SQL 操作,有可能会不适合。比如大型网站的中央数据库,可能会因为外键约束的系统开销而变得非常慢。所以, MySQL 允许你不使用系统自带的外键约束,在应用层面完成检查数据一致性的逻辑。也就是说,即使你不用外键约束,也要想办法通过应用层面的附加逻辑,来实现外键约束的功能,确保数据的一致性。
五、check约束
检查某个字段的值是否符合xx要求,一般指的是值的范围。
关键字:check
MySQL5.7不支持
-- check约束
CREATE TABLE test1(
id INT,
last_name VARCHAR(15),
salary DECIMAL(10,2) CHECK(salary>2000)
);
INSERT INTO test1
VALUES(1,'tom',2500);
INSERT INTO test1
VALUES(1,'tom',1500);
SELECT * FROM test1;
六、default约束
-- default约束
CREATE TABLE test2(
id INT,
last_name VARCHAR(15),
salary DECIMAL(10,2) DEFAULT 2000
);
DESC test2;
INSERT INTO test2
VALUES(1,'tom',3000);
SELECT * FROM test2;
#自动添加2000
INSERT INTO test2(id,last_name)
VALUES(2,'tom');
-- alter table时添加约束
CREATE TABLE test3(
id INT,
last_name VARCHAR(15),
salary DECIMAL(10,2)
);
ALTER TABLE test3
MODIFY salary DECIMAL(10,2) DEFAULT 2500;
DESC test3;
-- 删除约束
ALTER TABLE test2
MODIFY salary DECIMAL(10,2);
面试题目
-
面试1、为什么建表时,加 not null default ‘’ 或 default 0
答:不想让表中出现null值。 -
面试2、为什么不想要 null 的值
答:(1)不好比较。null是一种特殊值,比较时只能用专门的is null 和 is not null来比较。碰到运算符,通常返回null。
(2)效率不高。影响提高索引效果。因此,我们往往在建表时 not null default ‘’ 或 default 0 -
面试3、带AUTO_INCREMENT约束的字段值是从1开始的吗?
在MySQL中,默认AUTO_INCREMENT的初始值是1,每新增一条记录,字段值自动加1。设置自增属性(AUTO_INCREMENT)的时候,还可以指定第一条插入记录的自增字段的值,这样新插入的记录的自增字段值从初始值开始递增,如在表中插入第一条记录,同时指定id值为5,则以后插入的记录的id值就会从6开始往上增加。添加主键约束时,往往需要设置字段自动增加属性。 -
面试4、并不是每个表都可以任意选择存储引擎? 外键约束(FOREIGN KEY)不能跨引擎使用。
MySQL支持多种存储引擎,每一个表都可以指定一个不同的存储引擎,需要注意的是:外键约束是用来保证数据的参照完整性的,如果表之间需要关联外键,却指定了不同的存储引擎,那么这些表之间是不能创建外键约束的。所以说,存储引擎的选择也不完全是随意的