MySQL入门
表的相关操作
说明
-
新增语法:
create table 表名(字段名2 字段类型, 字段名2 字段类型, ....);
-
删除语法:
drop table 表名;
或drop table if exists 表名;
-
建表属于 DDL 语句,DDL 包括:create、drop、alter
-
建议:
1、表名:建议以 t_ 或者 tbl_ 开始,可读性强,见名知意
2、字段名:见名知意即可
3、数据类型:MySQL中的数据类型有很多,但是不需要全部熟悉,只需要熟悉一些常用的即可(详情可看下面的类型表)
数据类型
int 整数型(java中的int)
bigint 长整型(java中的long)
float 单精度浮点型(java中的float)
double 双精度浮点型(java中的double)
char 定长字符串(String)
varchar 可变长字符串(StringBuffer/StringBuilder)
date 日期类型 (对应Java中的java.sql.Date类型)
BLOB 二进制大对象(存储图片、视频等流媒体信息) Binary Large OBject (对应java中的Object)
CLOB 字符大对象(存储较大文本,比如,可以存储4G的字符串。) Character Large OBject(对应java中的Object)
......
类型 | 描述 |
---|---|
char(长度) | 定长字符串,存储空间大小固定,适合作为主键或外键,最长 255 |
varchar(长度) | 可变长读的字符串,存储空间等于实际数据空间,最长 255 |
double(有效数字位数,小数位) | 数值型 |
float(有效数字位数,小数位) | 数值型 |
int(长度) | 整型,最长 11 |
bigint(长度) | 长整型 |
date | 日期型 年月日 |
datetime | 日期型 年月日 时分秒 毫秒 |
time | 日期型 时分秒 |
blob | Binary Large OBject(二进制大对象),往 blob 中插入图片、视频等时,需要使用 IO流 |
clob | Character Large OBject(字符大对象),最大可存储 4GB 的字符串 超过 255 个字符的都要使用 clob 字符大对象来存储 |
其它… | 可参考菜鸟教程:https://www.runoob.com/mysql/mysql-data-types.html |
表格中的数据类型都需要掌握,其它的数据类型了解即可
char 和 varchar 的区别和优缺点是什么?
-
varchar:可变长度的字符串,比较智能,更容易节省空间,因为会根据实际的数据长度动态分配空间。
优点:节省空间
缺点:需要动态分配空间,速度较慢
-
char:定长字符串(固定的),不管实际的数据长度是多少,都会分配固定长度的空间去存储数据,使用不恰当时可能会导致空间浪费
优点:不需要动态分配空间,速度较快
缺点:使用不恰当,可能会导致空间的浪费
-
如何选择:
存储固定长度的字符串时,使用 char 类型。
存储不固定长度的字符串时,使用 varchar 类型。
案例
表的创建
案例:创建 t_movie 表存储学生信息,属性:id、name、sex、age、email
create table t_student(
id int comment '学号',
name varchar(50) comment '姓名',
sex char(2) default '男' comment '性别',
age int(3) comment '年龄',
email varchar(255) comment '邮箱'
);
default:字段默认值
comment:字段说明
说明:创建表的时候,表中有字段,每一个字段都包含:字段名、字段数据类型、字段长度限制和字段约束。
表的快速创建(了解即可)
-- 将查询出来的所有字段复制到 emps 表中,并创建 emps 表
create table emps as select * from emp;
原理:将一个查询结果当做一张表新建!
这个操作可以实现表的快速复制;表创建出来后,同时表中的数据也存在了。
-- 将查询的部分字段数据复制到 mytable 表中,并创建 mytable 表
create table mytable as select empno, ename from emp;
添加字段
案例:在 t_student 表中添加 contact_tel 字段
alter table t_student add contact_tel varchar(40);
修改字段
案例:将 t_student 表中的 name 字段长度修改为 varchar(100)
alter table t_student modify name varchar(100);
删除字段
案例:删除 t_student 表中的 contact_tel 字段
alter table t_student drop contact_tel;
表的删除
案例:删除 t_student表
-- 方式一
drop table t_student; -- 如果这张表不存在的话,就会报错
-- 方式二(推荐)
drop table if exists t_student; -- 如果这张表存在的话,就删除该表,不存在的话,则不会执行删除
建议:在新增表的前面可以先使用方式二进行判断删除原有的表,然后在创建新的表进行使用
插入数据(insert)
说明
-
insert 属于 DML 语句
-
语法格式:
insert into 表名(字段名1, 字段名2, ... 字段名n) values(值1, 值2, ... 值n)
insert 语句中的字段名可以省略,省略后默认是所有字段,值必须和该表中的所有字段对应
-
注意:使用时字段名和值要一一对应(不仅数量要对应,数据类型也要对应!)
-
insert 只要执行成功了,就一定会在数据表中插入一条新的数据
案例
案例:插入单行数据(完整数据)
-- 方式一:指定所有字段插入数据
insert into t_student(id, name, sex, age, email)
values(1, '张三', '男', 18, 'zhangsan@email.com');
-- 方式二:默认所有字段插入数据(不建议使用,因为数据库表中的字段可能会发生改变)
insert into t_student
values(1, '张三', '男', 18, 'zhangsan@email.com');
说明:前面的字段名省略的话,就相当于都写上了,所以值也要都写上,否则就会报错
案例:插入单行数据(部分数据)
insert into t_student(id, name, email)
values(4, '赵六', 'zhangsan@email.com');
-- 错误写法:
-- 1、字段名和值的数量对应不上
insert into t_student(id, name, sex, age, email)
values(4, '赵六', 'zhangsan@email.com');
-- 2、字段类型和值的类型不一致
insert into t_student(id, name, email)
values(4, '赵六', 123456);
案例:插入多行数据
insert into t_student(id, name, sex, age, email)
values(2, '李四', '男', 20, 'lisi@email.com'), (3, '王五', '女', 30, 'wangwu@email.com');
将查询结果插入到一张表中(了解即可)
-- 创建bapt_bak 表,并将的dept查询出来的数据复制到 dept_bak 表中。
create table dept_bak as select * from dept;
-- 将 dept 查询出来的数据插入到 dept_bak 表中
insert into dept_bak select * from dept;
这种方式基本上不会用到,所以了解一下就好了。
日期类型及格式说明
Mysql 日期格式
年:%Y
月:%m
日:%d
时:%h
分:%i
秒:%s
date 和 datetime 的区别?
创建 t_user 表用来测试:
drop table if exists t_user;
create table t_user(
id int,
name varchar(32),
birth date comment '生日',
create_time datetime comment '创建日期'
);
date
- date 是短日期:只包含年月日信息
- mysql 短日期默认格式:
%Y-%m-%d
-- 错误写法:
insert into t_user
values(1, '张三', '2021-04-27 12:05:30');
-- 正确写法:
insert into t_user
values(1, '张三', '2021-04-27');
datetime
- datetime 是长日期:包含年月日时分秒信息
- mysql 默认长日期格式:
%Y-%m-%d %h:%i:%s
-- 使用指定时间作为创建时间的值
insert into t_user
values(1, '张三', '2021-04-27', '2021-06-18 12:05:30');
-- 使用系统时间作为创建时间的值
insert into t_user
values(1, '张三', '2021-04-27', now());
可能会使用到的函数
str_to_date:字符串(varchar)转日期(date)
- str_to_date 函数可以把字符串 varchar 转换成日期 date 类型数据,通常使用在插入(insert)方面,因为插入的时候需要一个日期类型的数据,需要通过该函数将字符串转换成 date
- 语法:
str_to_date('字符串日期', '日期格式')
- 如果提供的日期格式为 %Y-%m-%d,就不需要使用 str_to_date 函数
案例
-- 错误写法:表中的生日类型为 date(格式为:%Y-%m-%d),而这里的值因为格式不一致,所以值的类型为 varchar,最后导致类型不一致错误
insert into t_user
values(1, '张三', '11-12-2021');
-- 如何解决? 使用 str_to_date 函数
insert into t_user
values(1, '张三', str_to_date('11-12-2021', '%d-%m-%Y'));
注意:设置的日期格式需要对应上日期字符串中的数据,所以这里不能直接写成 '%Y-%m-%d'
说明:%d 对应 11,%m 对应 12,%Y 对应 2021
date_format:日期(date)转字符串(varchar)
- 将 date 类型转换成具有一定格式的 varchar 类型
- 语法:`date_format(日期类型数据, ‘日期格式’)
案例
select id, name, date_format(birth, '%m/%d/%Y') as births from t_user;
-- 了解:
-- 使用默认的日期格式化,自动将数据库中的 date 类型转换成 varchar 类型,并且采用的格式是 mysql 默认的日期格式:%Y-%m-%d
select id, name, birth from t_user;
now:获取当前系统时间
- 获取系统时间的类型为:datetime
insert into t_user
values(1, '张三', '2021-04-27', now());
修改数据(update)
说明
-
update 属于 DML 语句
-
语法格式:
update 表名 set 字段名1=值1, 字段名2=值2...字段名n=值n where 修改条件
注意:如果没有条件限制,会导致数据全部修改!!!
案例
案例:将 t_user 表中 id 为 4 的用户姓名改为 tom,生日改为 1937-08-09
update t_user set name = 'tom', birth = '1937-08-09' where id = 4;
案例:将 t_user 表中 id 为 1 的用户创建时间改为当前系统时间
update t_user set create_time = now() where id = 1;
案例:将 t_user 表中的所有用户创建时间改为当前系统时间
update t_user set create_time = now()
删除数据(delete)
说明
-
delete 属于 DML 语句
-
语法格式:
delete from 表名 where 删除条件
注意:如果没有删除条件,整张表的数据都会被删除!!!
案例
案例:删除 t_user 表中 id = 4 的用户信息
delete from t_user where id = 4;
案例:删除 t_user 表中所有的用户数据
delete from t_user;
快速删除表中的数据
delete 删除表数据
-
原理:表中的数据被删除了,但是这个数据在硬盘上的真实存储空间不会被释放!
delete 是一个一个字段的值进行删除,并且只是删除值本身,而值的真实存储空间是不会被释放的!
-
好处:删除之后还有机会可以回滚(简单理解就是可以恢复删除的数据)
-
坏处:删除效率较低
-
delete 删除属于 DML 语句
-- 删除表中所有数据,这种方式比较慢
delete from dept_bak;
truncate 删除表数据
- 原理:这种删除效率较高,表被一次截断(一次性删整个表数据),物理删除!!!
- 优点:删除效率较高
- 缺点:无法回滚
- truncate 删除属于 DDL 操作
- truncate 只能删指定表中的所有数据,如果是想删部分数据,则还是使用 delete
truncate table dept_bak;
-- 错误写法
truncate table dept_bak where deptno = 10;
假设现在一张表中有上亿条记录,使用 delete 进行删除的话,可能需要执行一个小时才能删完,效率较低。
可以使用 truncate 进行删除,只需要不到 1 秒钟的时间就删完!!!
注意:在使用 truncate 之前,必须仔细询问客户是否真的要删除,并警告删除之后不可恢复!
增删改查语句小总结
查询
select 字段名1, 字段名2...字段名n from 表名
where 查询条件
group by 分组
having 分组函数判断
order by 排序
limit 限制数据量
新增
-- 单条数据插入
insert into 表名(字段名1, 字段名2...字段名n)
values(值1, 值2...值n);
-- 多条数据插入
insert into 表名(字段名1, 字段名2...字段名n)
values(值1, 值2...值n), (值1, 值2...值n)...(值1, 值2...值n);
修改
update 表名 set 字段名1 = 值1, 字段名2 = 值2...字段名n = 值n
where 修改条件;
删除
delete from 表名
where 删除条件;
表结构的增删改
什么是对表结构的修改?
- 添加一个字段,删除一个字段,修改一个字段 都属于对表结构的修改。
-- 添加字段
alter table t_student add contact_tel varchar(40);
-- 删除字段
alter table t_student modify name varchar(100);
-- 修改字段
alter table t_student drop contact_tel;
说明
-
在实际开发中,需求一旦确定下来,表一旦设计好之后,很少会进行表结构的修改。因为开发进行中的时候,修改表结构,那么对应的 java 代码可能面临教大的改动,成本是比较高的!!
这个责任应该有设计数据库的相关人员来承担(如果不是自己设计的,就别背锅…)
-
由于修改表结构的操作很少,所以我们不需要掌握,如果有一天真的要修改表结构了,可以使用可视化工具来进行修改
修改表结构的操作是不需要写到 java 程序中的,实际上也不算是 java 程序员的范畴。
sql 脚本文件说明
-
xxx.sql 这种文件被称为 sql 脚本文件,sql 脚本文件中编写了大量 sql 语句,执行 sql 脚本的时候,该文件中所有的 sql 语句会全部执行。
-
需要批量执行 sql 语句的时候,就可以使用 sql 脚本。
-
在 mysql 当中,如何执行 sql 脚本?
source xxx/xxx.sql
约束
什么是约束?
- 约束(constraint):在创建表的时候,我们可以给表中的字段加上一些约束,来保证这个表中数据的完整性、有效性。
- 主要作用:用于保证表中的数据有效!
常见约束
- 非空约束(not null)
- 唯一性约束(unique)
- 主键约束(primary key,简称 PK)
- 外键约束(foreign key,简称 FK)
- 检查约束(check,mysql不支持,oracle 支持)
重点了解四个约束:非空、唯一性、主键、外键
说明
-
约束直接添加在列后面的,这种约束被称为列级约束。
比如:
drop table if exists t_vip; create table t_vip( id int, name varchar(30) not null -- 这种就是列级约束 );
-
约束没有添加在列后面的,这种约束被称为表级约束。
比如:
drop table if exists t_vip; create table t_vip( id int, name varchar(30), email varchar(50), unique(name, email) -- 这种就是表级约束 );
-
什么时候使用表级约束?
需要给多个字段联合起来添加某一个约束的时候,需要使用表级约束。
非空约束(not null)
- 被非空约束给约束的字段不能为 NULL
- not null 只有列级约束,没有表级约束
案例:创建 t_vip 表,字段为:id(int)、name(varchar(30)),要求给 name 字段设置非空约束
drop table if exists t_vip;
create table t_vip(
id int,
name varchar(30) not null
);
-- 执行失败,原因:name 字段设置了非空约束,所以新增时必须给 name 字段赋值,否则就会报错
insert into t_vip(id)
values(1);
-- 执行成功
insert into t_vip(id, name)
values(1, 'zs');
-- 因为 id 字段没有设置非空约束,所以新增时即使不给他赋值,最后也能新增成功
insert into t_vip(name)
values('ls');
唯一性约束(unique)
- 被唯一性约束所约束的字段不能重复,但是可以为 NULL
- unique 可以是列级约束,也可以是表级约束
案例:创建 t_vip 表,字段为:id(int)、name(varchar(30)),要求给 name 字段设置唯一性约束
drop table if exists t_vip;
create table t_vip(
id int,
name varchar(30) unique
);
-- 执行成功
insert into t_vip(id, name)
values(1, 'zs'), (2, 'ls'), (3, 'ww');
-- 唯一性的字段可以为 NULL,且 NULL 不存在重复
insert into t_vip(id)
values(4), (5);
-- 执行失败
insert into t_vip(id, name)
values(4, 'zs');
两个字段联合唯一性
案例:创建 t_vip 表,字段为:id(int)、name(varchar(30)、email(50)),要求给 name 和 email 字段设置联合唯一性
drop table if exists t_vip;
create table t_vip(
id int,
name varchar(30) unique,
email varchar(50) unique
);
-- 以上的创建方式,是不符合案例要求的!这样创建表示 name 具有唯一性,email 具有唯一性,各自唯一。
-- 根据案例要求,以下的新增语句是应该是可以正常执行的,但是如果按上面的方式创建表,则无法正常执行以下的新增语句
insert into t_vip(id, name, email)
values(1, 'zs', 'zs@123.com'), (2, 'zs', 'zs@sina.com');
-- 正确创建表的方式
drop table if exists t_vip;
create table t_vip(
id int,
name varchar(30),
email varchar(50),
unique(name, email)
);
insert into t_vip(id, name, email)
values(1, 'zs', 'zs@123.com'), (2, 'zs', 'zs@sina.com');
-- 如果插入的是 name 和 email 都相同的数据,则会新增失败,因为有联合唯一性约束进行限制
insert into t_vip(id, name, email)
values(3, 'zs', 'zs@sina.com');
not null 和 unique 的联合使用
- 在 mysql 当中,如果一个字段同时被 not null 和 unique 约束的话,该字段自动变成主键字段!(
注意:oracle 不一样
)
------------------------------------- 案例 -------------------------------------
drop table if exists t_vip;
create table t_vip(
id int,
name varchar(30) not null unique
);
-- 正常执行
insert into t_vip(id, name)
values(1, 'zs'), (2, 'ls');
-- 错误执行:name 不能重复
insert into t_vip(id, name)
values(3, 'zs');
-- 错误执行:name 不能为 NULL
insert into t_vip(id)
values(3);
主键约束(primary key,简称 PK)
- primary key 可以是列级约束,也可以是表级约束
主键相关术语
- 主键约束:就是一种约束
- 主键字段:该字段上添加了主键约束,这样的字段就叫做 “主键字段”
- 主键值:主键字段中的每一个值都叫做 “主键值”
emmmm…有点像废话文学
什么是主键?有什么用?
- 主键值是每一行记录的唯一标识(就类似于每个人都有自己唯一的身份证号)
- 任何一张表都应该包含主键,没有主键,表无效!!!!
- 主键特征:
not null + unique
(主键值不能为 NULL,也不能重复!)
案例
案例:创建 t_vip 表,字段为:id(int)、name(varchar(30)),要求给 id 设置主键约束
------------------------------------- 列级约束 -------------------------------------
drop table if exists t_vip;
create table t_vip(
id int primary key,
name varchar(30)
);
-- 正常执行
insert into t_vip(id, name)
values(1, 'zs'), (2, 'ls');
-- 错误执行:id 不能重复
insert into t_vip(id, name)
values(1, 'ww');
-- 错误执行:id 不能为 NULL
insert into t_vip(name)
values('zl');
------------------------------------- 表级约束 -------------------------------------
drop table if exists t_vip;
create table t_vip(
id int,
name varchar(30),
primary key(id)
);
-- 正常执行
insert into t_vip(id, name)
values(1, 'zs'), (2, 'ls');
-- 错误执行:id 不能重复
insert into t_vip(id, name)
values(1, 'ww');
-- 错误执行:id 不能为 NULL
insert into t_vip(name)
values('zl');
两个字段联合主键约束(复合主键,了解即可)
- 一个字段做主键,叫做单一主键
- 两个字段联合起来做主键,叫做复合主键
- 在实际开发中,不建议使用复合主键!建议使用单一主键,因为主键存在的意义就是这行记录的唯一性&非空,单一主键可以做到,复合主键比较复杂,所以不建议!
案例:创建 t_vip 表,字段为:id(int)、name(varchar(30)),要求给 id 和 name 设置联合主键约束
drop table if exists t_vip;
create table t_vip(
id int,
name varchar(30),
primary key(id, name)
);
-- 正常执行
insert into t_vip(id, name)
values(1, 'zs'), (2, 'ls');
-- 错误执行:id 和 name 不能同时重复
insert into t_vip(id, name)
values(1, 'zs');
-- 错误执行:id 不能为 NULL
insert into t_vip(name)
values('zl');
-- 错误执行:name 不能为 NULL
insert into t_vip(id)
values(4);
一个表中主键约束能加两个吗?
不能,因为在一张表中,主键约束只能添加一个!(主键只能有一个)
-- 执行报错
drop table if exists t_vip;
create table t_vip(
id int primary key,
name varchar(30) primary key
);
主键值推荐类型
- 建议使用 int、bigint、char 等类型作为主键。
- 不建议使用 varcha 来做主键,主键值一般都是数字,一般都是定长的。
自然主键和业务主键的说明
-
自然主键:主键值是一个自然数的就是自然主键,和相关业务没什么关系。
-
业务主键:主键值和业务紧密相连(比如:拿银行卡账号作为主键值)
-
在实际开发中自然主键用的比较多,还是业务主键用的比较多?
解:自然主键使用的比较多,因为主键只要做到不重复即可,不需要有意义;业务主键不太好,因为主一旦和业务挂钩,那么当业务发生变动的时候,可能会影响到主键值,所以不建议使用业务主键,尽量使用自然主键!
mysql 中主键的自动维护机制(auto_increment)
- auto_increment:自增,从 1 开始,依次递增。
-- 正确创建表的方式
drop table if exists t_vip;
create table t_vip(
id int primary key auto_increment,
name varchar(30)
);
-- 正常执行:id 自增,相当于有一个默认值,所以这里可以不用给 id 赋值
insert into t_vip(name)
values('zs'), ('ls'), ('ww');
-- 正常执行:手动给 id 赋值也是可以的
insert into t_vip(id, name)
values(4, 'zl');
外键约束(foreign key,简称 FK)
外键相关术语
- 外键约束:一种约束
- 外键字段:该字段上添加了外键的约束
- 外键值:外键字段当中的每一个值
业务背景
案例:请设计一个数据库表,来描述 “班级和学生” 的信息?
方案一:班级和学生存储在一张表(t_student)中
no(pk) name classno classname
--------------------------------------------------------
1 jaok 100 北京第一中学高山1班
2 lucy 100 北京第一中学高山1班
3 tom 101 北京第一中学高山2班
这种方案的缺点:数据冗余,空间浪费!
> 这种设计是比较失败的
方案二:班级一张表(t_class),学生一张表(t_student)
t_class 表:
classno(pk) classname
--------------------------------------
100 北京第一中学高山1班
101 北京第一中学高山2班
t_student 表:
no(pk) name cno(班级编号)
--------------------------------------
1 jaok 100
2 lucy 100
3 tom 101
说明:
当 cno 字段没有任何约束的时候,可能会导致数据无效,比如出现了一个 102,但是 102 班级不存在。
为了保证 cno 字段中的值都是 100 和 101,需要给 cno 字段添加外键约束(那么:cno 字段就是外键字段,cno 字段中的每一个值都是外键值)
t_student 表:
no(pk) name cno(FK 引用 t_class 这张表的 classno)
--------------------------------------
1 jaok 100
2 lucy 100
3 tom 101
注意:
t_class 是父表
t_student 是子表
删除表的顺序是:先删子表,在删父表
创建表的顺序是:先创父表,在创子表
删除表数据的顺序是:先删表子数据,在删父表数据
插入表数据的顺序是:先插父表,在插子表
具体实现
drop table if exists t_class;
create table t_class(
classno int primary key,
classname varchar(50)
);
drop table if exists t_student;
create table t_student(
no int primary key auto_increment,
name varchar(30),
cno int,
foreign key(cno) references t_class(classno)
);
-- 正常执行
insert into t_class(classno, classname)
values(100, '北京第一中学高山1班'), (101, '北京第一中学高山2班');
insert into t_student(name, cno)
values('jaok', 100), ('lucy', 100),('tom', 101);
-- 错误执行
insert into t_student(name, cno)
values('jack', 102);
问题思考
-
一、子表中的外键引用的父表中的某个字段,被引用的这个字段必须是主键吗?
不一定,但是至少具有唯一性!
-
二、外键字段值可以为 NULL 吗?
可以
存储引擎(了解)
什么是存储引擎?有什么用?
- 存储引擎是 MySQL 中特有的一个术语,其它数据库中没有(oracle 中有,但是不叫这个名字)
- 存储引擎是一个表 存储/组织 数据的方式,不同的存储引擎,表存储数据的方式不同。
怎么给表指定 “存储引擎” 呢?
-
可以在建表的时候给表指定存储引擎。
-
在建表的时候可以在最后小括号的 ”)“ 的右边使用
ENGINE:指定存储引擎
CHARSET:指定表的字符编码方式
mysql 默认的存储引擎是:InnoDB
mysql 默认的字符编码方式是:utf8
-
show create table 表名;
(作用:展示当时创建该表时所执行的语句)
show create table t_student;
-- 通过 show create table 表名; 查出来的语句如下:
CREATE TABLE `t_student` (
`no` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(30) COLLATE utf8_bin DEFAULT NULL,
`cno` int(11) DEFAULT NULL,
PRIMARY KEY (`no`),
KEY `cno` (`cno`),
CONSTRAINT `t_student_ibfk_1` FOREIGN KEY (`cno`) REFERENCES `t_class` (`classno`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COLLATE=utf8_bin
-- 如果在创建表的时候没有指定存储引擎和字符编码方式,那么默认使用 InnoDB 和 utf8
-- 手动设置存储引擎
drop table if exists t_student;
CREATE TABLE `t_student` (
`no` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(30) COLLATE utf8_bin DEFAULT NULL,
`cno` int(11) DEFAULT NULL,
PRIMARY KEY (`no`),
KEY `cno` (`cno`),
CONSTRAINT `t_student_ibfk_1` FOREIGN KEY (`cno`) REFERENCES `t_class` (`classno`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
MySQL 支持哪些存储引擎?
-
查看当前 MySQL 数据库支持哪些存储引擎:
show engines \G
这个命令只能在 cmd 命令行中执行,不能再 Navicat 中执行!
-
MySQL 支持九大存储引擎,mysql 5.7 支持八个存储引擎(不同版本支持情况不同!)
MyISAM 存储引擎
使用该存储引擎的表具有以下特征
-
使用三个文件表示每隔表
格式文件:存储表结构的定义(mytable.frm)
数据文件:存储表行的内容(mytable.MYD)
索引文件:存储表上的索引(mytable.MYI)
索引就相当于一本书的目录,主要作用就是提高查询效率。
了解:对于一张表来说,只要是主键,或者加有 unique 约束的字段就会自动创建索引。
-
可被转换为压缩、只读表来节省空间
-
注意:MyISAM 是不支持事务的,所以安全性较低!
优势:可被转换为压缩、只读表来节省空间
案例
drop table t_student;
CREATE TABLE `t_student` (
`no` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(30) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
`cno` int(11) DEFAULT NULL,
PRIMARY KEY (`no`),
KEY `cno` (`cno`),
CONSTRAINT `t_student_ibfk_1` FOREIGN KEY (`cno`) REFERENCES `t_class` (`classno`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 -- 设置存储引擎
InnoDB 存储引擎(5.7默认的存储引擎)
说明
- InnoDB 是 mysql 默认的存储引擎,同时也是一个重量级的存储引擎!
- InnoDB 支持事务,支持数据库崩溃后自动恢复机制。
- InnoDB 最主要的特点是:非常安全!!
使用该存储引擎的表具有以下特征
- 每个 InnoDB 表在数据库目录中以
.frm
格式文件表示 - InnoDB 表空间 tablespace 被用于存储表的内容(表空间是一个逻辑名称,表空间存储数据 + 索引)
- 提供一组用来记录事务性活动的日志文件
- 用 COMMIT(提交)、SAVEPOINT 及 ROLLBACK(回滚)支持事务处理
- 提供全 ACID 兼容
- 在 MySQL 服务器崩溃后提供自动恢复
- 多版本(MVCC)和行级锁定
- 支持外键及引用的完整性,包括级联删除和更新
InnoDB 最大的特点就是支持事务,以保证数据的安全性!
因为 InnoDB 不能被压缩,也不能转换为只读,所以不能很好的节省存储空间。
案例
drop table t_student;
CREATE TABLE `t_student` (
`no` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(30) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
`cno` int(11) DEFAULT NULL,
PRIMARY KEY (`no`),
KEY `cno` (`cno`),
CONSTRAINT `t_student_ibfk_1` FOREIGN KEY (`cno`) REFERENCES `t_class` (`classno`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 -- 设置存储引擎
MEMORY 存储引擎
说明
- 使用 MEMORY 存储引擎的表,其数据存储在内存中,且行的长度固定,这两个特点使得 MEMORY 存储引擎非常快。
- MEMORY 存储引擎以前被称为 HEAP 引擎。
- 优点:查询效率是最高的!
- 缺点:不安全,关机之后数据消失,因为数据和索引都是存储在内存中的
存储引擎管理的表具有下列特征
- 在数据库目录内,每个表均以.frm 格式的文件表示。
- 表数据及索引被存储在内存中。
- 表级锁机制。
- 不能包含 TEXT 或 BLOB 字段。
案例
drop table t_student;
CREATE TABLE `t_student` (
`no` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(30) CHARACTER SET utf8 COLLATE utf8_bin DEFAULT NULL,
`cno` int(11) DEFAULT NULL,
PRIMARY KEY (`no`),
KEY `cno` (`cno`),
CONSTRAINT `t_student_ibfk_1` FOREIGN KEY (`cno`) REFERENCES `t_class` (`classno`)
) ENGINE=MEMORY DEFAULT CHARSET=utf8 -- 设置存储引擎
如何选择合适的存储引擎?
- MyISAM 表最适合于大量的数据读而少量数据更新的混合操作。MyISAM 表的另一种适用情形是使用压缩的只读表。
- 如果查询中包含较多的数据更新操作,应使用 InnoDB。其行级锁机制和多版本的支持为数据读取和更新的混合操作提供了良好的并发机制。
- 可使用 MEMORY 存储引擎来存储非永久需要的数据,或者是能够从基于磁盘的表中重新生成的数据。
事务(transaction,重点)
什么是事务?
一个事务其实就是一个完整的业务逻辑(本质上来说,一个事务其实就是多条 DML 语句同时成功,或者同时失败)
如果世界上所有的业务逻辑都只需要一条 DML 语句就能搞定,那么事务就没有存在的必要了。
完整业务逻辑举例:
假设转账,从 A 账户向 B 账户中转账 10000,将 A 账户的钱减去 10000,将 B 账户的钱增加 10000,这就是一个完整的业务逻辑。
注意:只有 DML 语句才有事务这个说法,其它语句和事务都没有关系!因为只有 DML 中的三个语句是对数据库表中数据进行增、删、改的;只要执行的操作涉及到数据的增、删、改,那么就一定要考虑安全问题,因为数据安全要排在第一位!
事务是如何做到多条 DML 语句同时成功或者同时失败的呢?
- InnoDB 存储引擎:提供了一组用来记录事务性活动的日志文件。
- 在事务的执行过程中,每一条 DML 的操作都会记录到 “事务性活动的日志文件” 中。
- 在事务的执行过程中,我们可以提交事务,也可以回滚事务。
提交事务:
-
清空事务性活动的日志文件,将数据全部彻底持久化到数据库表中。
-
提交事务标志着事务的结束,并且是一种全部成功的结束。
回滚数据:
- 将之前所有的 DML 操作全部撤销,并且清空事务性活动的日志文件。
- 回滚事务标志着事务的结束,并且是一种全部失败的结束。
事务执行顺序:
1. 事务开启
2. 执行 DML 语句
3. 事务结束
如何提交事务和回滚事务
-
提交事务:commit 语句
-
回滚语句:rollback 语句(回滚永远都只能提交到上一次的提交点!)
-
mysql 默认情况下是支持自动提交事务的
自动提交:每执行一条 DML 语句,就提交一次事务(比如我们平常执行的增删改,一旦执行就会立刻生效,并且没办法回滚)
-
如何关闭 mysql 自动提交:可以使用
start transaction;
语句关闭 mysql 自动提交关闭自动提交以后,可以进行测试:
1、插入几条数据
2、然后在查看表数据(此时表中应该会有插入的数据)
3、执行回滚
4、然后看看插入的数据是否还在表中(此时刚才插入的数据已经不存在表中了)
-- 开启事务(也可以被称为:关闭mysql自动提交)
start transaction;
-- DML操作
insert into ...
update tablename set ...
delete from ...
-- 回滚事务:执行回滚之后,刚才执行的 DML 操作,都会当中没有执行过
rollback;
说明:比如之前的表中有 3 条数据,执行后有 5 条,但是执行回滚后,又会变回原来的那 3 条数据。
-- 开启事务(也可以被称为:关闭mysql自动提交)
start transaction;
-- DML操作
insert into ...
update tablename set ...
delete from ...
-- 提交事务:如果想得到 DML 操作之后的数据,可以把事务进行提交
commit;
说明:比如之前的表中有 3 条数据,执行后有 5 条,提交事务后,得到的就是执行后的 5 条数据。
事务四大特性(ACID)
-
A:原子性(说明事务是最小的工作单元,不可再分)
-
C:一致性(所有事务要求,在同一个事务当中,所有操作必须同时成功,或者同时失败,以保证数据的安全性)
-
I:隔离性(A 事务和 B 事务之间具有一定的隔离(比如:教室 A 和教室 B 中间有一道墙,这道墙就是隔离性))
-
D:持久性(事务最终结束的一个保障,事务提交,就相当于将没有保存到硬盘上的数据保存到硬盘上)
ACID 分别是四个特性,别搞错了,别误以为 ACID 是一个东西!!!
事务隔离级别
这里就重点说明一下隔离性
隔离级别的案例解释:
A 教室和 B 教室中间有一道墙,这道墙可以很厚,也可以很薄,这就是事务的隔离级别。
隔离级别的作用范围
事务隔离级别的作用范围分为两种:
- 全局级:对所有的会话有效
- 会话级:只对当前的会话有效
-- 案例:设置会话级隔离级别为 READ COMMITTED :
SET TRANSACTION ISOLATION LEVEL READ COMMITTED;
或:
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
-- 案例:设置全局级隔离级别为 READ COMMITTED :
SET GLOBAL TRANSACTION ISOLATION LEVEL READ COMMITTED;
可以用可视化工具和cmd分别测试一下。
事务和事务之间的隔离级别有哪些?
- 读未提交:
read uncommitted
(最低的隔离级别) - 读已提交:
read committed
- 可重复读:
repeatable read
- 序列化/串行化:
serializable
(最高的隔离级别)
读未提交
-
读未提交:
read uncommitted
(最低的隔离级别)比如:事务 A 可以读取到事务 B 未提交的数据(
简单理解:事务还没提交就可以读取到数据
) -
这种隔离级别存在的问题:脏读现象(Dirty Read),一般理解为读取到了脏数据。
-
这种隔离级别一般都是理论上的,大多数的数据库隔离级别都是二挡起步(简单理解就是:一般不会使用最低的隔离级别)。
读已提交
-
读已提交:
read committed
比如:事务 A 可以读取到事务 B 提交后的数据(
简单理解:事务提交后才能读取到
) -
这种隔离级别解决了什么问题?
解决了脏读的现象!
-
这种隔离级别存在什么问题?
不可以重复读取数据!
-
什么是不可重复读取数据呢?
在事务开启之后,第一次读取的数据是 3 条,当前事务还未结束,可能第二次在读取的时候变成了 4 条,而 3 不等于 4,这就称为不可重复读
-
读已提交这种隔离级别是比较真实的数据,每一次读到的数据是绝对的真实!
可重复读
-
可重复读:
repeatable read
比如:事务 A 开启之后,不管多久,每一次在事务 A 中读取到的数据都是一致的,即使事务 B 将数据已经修改,并且提交了,事务 A 读取到的数据还是没有发生改变,这就是可重复读。
(
简单理解:事务提交后读取不到数据,永远读取的都是开启事务时的数据
,比如:早上 9 点开启事务,只要事务不结束,到晚上 9 点,读取到的数据还是开启事务时的数据。) -
这种隔离级别解决了什么问题?
解决了不可重复读的问题
-
这种隔离级别存在什么问题?
可能会出现幻影读(幻影读:每一次读取到的数据都是幻象的,不够真实!)
序列化/串行化
- 序列化/串行化:
serializable
(最高的隔离级别) - 这个是最高的隔离级别,效率最低,解决了所有的问题!
- 这种隔离级别表示事务排队,不能并发。(简单理解就是,我操作 A 数据的时候,别人不能来操作 A 数据;有点类似于线程同步:synchronized)
- 每一次读取到的数据都是最真实的,并且效率是最低的!
测试 mysql 四大隔离级别
- 查看事务隔离级别:
select @@tx_isolation;
读未提交:read uncommited
测试步骤:
- 创建测试需要的表(t_user)
- 将默认隔离级别设置为 read uncommited
- 打开两个执行窗口
- 两个窗口都开启事务(开启事务后可查询一下 t_user 表中是否有数据)
- 第二个窗口插入一行数据
- 第一个窗口查询插入的数据(此时可以查到,这就是所谓的 “读未提交”)
-- 创建测试需要的表
drop table if exists t_user;
create table t_user(
name varchar(50)
);
-- 设置全局的隔离级别为:读未提交 read uncommited(设置好后需要关闭数据库,然后重新打开,最好在查看一下当前的隔离级别)
set global transaction isolation level read uncommitted;
-- 关于打开两个窗口的步骤这里省略
-- 建议查看一下两个窗口中当前的隔离级别是否为 read uncommitted
select @@tx_isolation;
-- 窗口一开启事务
start transaction;
-- 窗口二开启事务
start transaction;
-- 窗口二插入数据
insert into t_user
values('zhangsan');
-- 窗口一查询数据(此时可以查询到刚才插入的 zhangsan,但是此时事务还没有进行提交)
select name from t_user;
读已提交:read committed
测试步骤:
- 创建测试需要的表(t_user)
- 将默认隔离级别设置为 read committed
- 打开两个执行窗口
- 两个窗口都开启事务(开启事务后可查询一下 t_user 表中是否有数据)
- 第二个窗口插入一行数据
- 第一个窗口查询插入的数据(此时窗口一是查询不到数据的,因为窗口二虽然新增了一行数据,但是事务还未提交(窗口二可以查到))
- 窗口二提交事务
- 窗口一再次查询(此时可以查询到刚才插入的数据)
-- 创建测试需要的表
drop table if exists t_user;
create table t_user(
name varchar(50)
);
-- 设置全局的隔离级别为:读已提交 read committed(设置好后需要关闭数据库,然后重新打开,最好在查看一下当前的隔离级别)
set global transaction isolation level read committed;
-- 关于打开两个窗口的步骤这里省略
-- 建议查看一下两个窗口中当前的隔离级别是否为 read committed
select @@tx_isolation;
-- 窗口一开启事务
start transaction;
-- 窗口二开启事务
start transaction;
-- 窗口二插入数据
insert into t_user
values('zhangsan');
-- 窗口一查询数据(此时窗口一是查询不到数据的,因为窗口二虽然新增了一行数据,但是事务还未提交(窗口二可以查到))
select name from t_user;
-- 窗口二提交事务
commit;
-- 窗口一再次查询(此时可以查询出 zhangsan)
select name from t_user;
可重复读:repeatable read
测试步骤:
- 创建测试需要的表(t_user)
- 将默认隔离级别设置为 repeatable read
- 打开两个执行窗口
- 两个窗口都开启事务(开启事务后可查询一下 t_user 表中是否有数据)
- 第二个窗口插入一行数据
- 第一个窗口查询插入的数据(此时窗口一是查询不到新增的数据的,因为窗口二虽然新增了一行数据,但是当前隔离级别只会查询到开启事务时的数据,所以窗口一只能查询到 zhansgan,而查询不到 lisi)
- 窗口二提交事务
- 窗口一再次查询(此时还是查询不到刚才插入的数据)
- 窗口一提交事务
- 窗口一再次查询(此时查询到的数据就是最新的数据)
-- 创建测试需要的表
drop table if exists t_user;
create table t_user(
name varchar(50)
);
-- 插入一行测试数据
insert into t_user
values('zhangsan');
-- 设置全局的隔离级别为:可重复读 repeatable read(设置好后需要关闭数据库,然后重新打开,最好在查看一下当前的隔离级别)
set global transaction isolation level repeatable read;
-- 关于打开两个窗口的步骤这里省略
-- 建议查看一下两个窗口中当前的隔离级别是否为 repeatable read
select @@tx_isolation;
-- 窗口一开启事务
start transaction;
-- 窗口二开启事务
start transaction;
-- 窗口二插入数据
insert into t_user
values('lisi');
-- 窗口一查询数据(此时窗口一是查询不到新增的数据的,因为窗口二虽然新增了一行数据,但是当前隔离级别只会查询到开启事务时的数据,所以窗口一只能查询到 zhansgan,而查询不到 lisi)
select name from t_user;
-- 窗口二提交事务
commit;
-- 窗口一再次查询(此时窗口一还是只能查询出 zhangsan)
select name from t_user;
说明:只要窗口一不提交事务,那么窗口一就永远只会查询到 zhangsan(这种就是所谓的 “幻影读”)
-- 窗口一提交事务
commit;
-- 窗口一再次查询(此时查询的数据就是 zhangsan 和 lisi)
select name from t_user;
序列化/串行化:serializable
测试步骤:
- 创建测试需要的表(t_user)
- 将默认隔离级别设置为 repeatable read
- 打开两个执行窗口
- 两个窗口都开启事务(开启事务后可查询一下 t_user 表中是否有数据)
- 窗口一新增数据
- 窗口二查询数据(此时因为窗口一还在使用 t_user 表,所以这里的查询不能马上查询出 t_user 表中的数据,所以会进入阻塞状态)
- 窗口一提交事务(窗口一提交之事务之后,窗口二的查询语句就会被执行)
-- 创建测试需要的表
drop table if exists t_user;
create table t_user(
name varchar(50)
);
-- 设置全局的隔离级别为:序列化/串行化 serializable(设置好后需要关闭数据库,然后重新打开,最好在查看一下当前的隔离级别)
set global transaction isolation level serializable;
-- 关于打开两个窗口的步骤这里省略
-- 建议查看一下两个窗口中当前的隔离级别是否为 repeatable read
select @@tx_isolation;
-- 窗口一开启事务
start transaction;
-- 窗口二开启事务
start transaction;
-- 窗口一插入数据
insert into t_user
values('zhangsan');
-- 窗口二查询数据(此时因为窗口一还在使用 t_user 表,所以这里的查询不能马上查询出 t_user 表中的数据,除非窗口一使用完了)
select name from t_user; -- 此时窗口二的查询语句会进入阻塞状态
-- 窗口一提交事务(相当于使用完了对应的数据,这里指的就是 t_user 表)
commit;
说明:窗口一提交之后,窗口二就会执行刚才被阻塞的查询语句