原文连接:Hzy 博客
1.数据库三大范式,解释和举例
1.1 第一范式1NF,要求数据的原子性,也就是不能在分解了,这个很容易理解
- 手机(电话号码(卡1号码,卡2号码))这样就不符合FN1。
- 手机(卡1号码,卡2号码),这样就符合第一范式。
1.2 第二范式2NF,第二范式是在第一范式的基础上,要求,非主键都
要依赖主键,是都
不是部分
喔.
举个例子,现在我有这样一个表:(学生表)[学号,学生姓名,班级编号,班级信息]
- 可以看到这里,学生姓名是跟学号挂钩的
- 但,班级信息是跟班级钩挂的,而且班级不跟学号挂钩的!
- ,班级,不依赖学号,所以不符合2NF的要求
正确的做法:一对多的关系
- 第一张表:(学生表)[学号,学生姓名,班级编号]
- 第二张表:(班级表)[班级编号,班级信息]
上面的表可能出现的问题:
减少数据的记录相同的数据
,如果一个班级有多个学生,有多少个学生,我们就要记录几次班级的信息!更新可能导致数据不一致的结果
,假如我们更新班级的编号,就必须先找出,所有属于这个班级的学生的行,然后再来改编号,否则,就会出现部分数据不一致的情况。删除操作
,假如删除某个班级,可能连带学生的信息也删除了!
1.3 第三范式3NF,是第二范式的子集,就是满足第二范式的条件,还要求,非主键之间不能有传递依赖,第三范式跟第二范式实际有点像。
不符合第三范式,但符合第二范式的例子
- (学生表)[学号,学生姓名,学院编号,学院名称]
- 这里面,一个学生,有姓名,有自己所在的学校编号及名称,非主键都依赖主键,符合第二范式。
- 但不符合第三范式,原因是,学院名称依赖学院编号,具有传递关系,这样是不行的。
正确的做法,把传递关系抽出来,建立一个表,多个多关系,多个学院,多个学生。
- (学生表)[学号,学生姓名]
- (学院表)[学院编号,学院]
- (学院学生表)[学号,学院编号]
参考文章:https://segmentfault.com/a/1190000013695030
2.上面讲三大范式,提到了一对一,一对多,多对多,现在来举个例子了解下。
2.1 一对一,举例:每个人都有自己的身份证。
- 一个人对应一张身份证
- 一张身份证对应一个人
- 表:人[人名,身份证编号]
2.2 一对多,举例:一门语言有多个人都会
一门语言有多个人都会
- 表:人[人名,身份证编号,语言编号]
- 表:语言[语言编号,语言名称]
- 这里:人是多的一方,语言为1,多的一方应该包含1的一方的主键,所以语言编号,应该包含在,人的表中。
反过来,每个人都会多门语言
- 表:人[人名,身份证编号]
- 表:语言[语言编号,语言名称,身份证编号]
- 可以看到,这里表换了个位置,但意思表达就不一样了!每个语言都包含了一个人。
- 多门语言都有同一个身份证编号,就代表,这个身份证编号的人会多门语言啦。
2.3 多对多,我们接着上面来讲,一个人会多门语言,每个人会多门语言,那不就是,多个人会多门语言吗,这就是多对多
- 我们用一个中间表来,记录,人和语言之间的关系。
- 表:人[人名,身份证编号]
- 表:语言[语言编号,语言名称]
- 表:人语言表[身份证编号,语言编号]
- 这里语言编号和身份证编号都是主键。
- 可以通过语言编号,找到会这们语言的所有人。
- 也可以通过身份证编号,找到这个人都会什么语言。
3 讲到了一对一等关系,那肯定也要了解主键啊,外键啊这些东西。
3.1 主键:一行中能够唯一标识这行的字段
- 一个表中只能有一个主键,主键会生成一个唯一索引,并且可以跟外键构成对数据进行约束。
- 注意主键不能为空,但唯一索引是可以为空的,这就是他们两的差别。
3.2 外键:一张表中,通过某个列可以跟另一张表关联起来,这个列就是外键
3.3 知道什么是主键和外键以后,我们通过sql语句来实现写写看
现在我们要创建一个学生表,和一个课程表。
创建一个名为students的表,其中使用primary key设置name为主键
1
2
3
4
5
CREATE TABLE STUDENTS(
name varchar(20),
sex varchar(10),
primary key(name)
);
创建一个名为courses的表,其中使用foregin key设置了一个关联students表中的字段。
- 只有在students里有的学生,才能存在课程表中,换句话说就是,课程表中不能有
压根不存在的学生
。 - 想要删除sutdent表,之前必须先删除课程表!
1
2
3
4
5
6
CREATE TABLE COURSE(
course_id varchar(20),
course_name varchar(20),
student_name varchar(20),
foregin key(student_name) references STUDENTS(name)
)
3.4 知道什么是主键和外键后,考虑一个问题,如果一个sutdents表中,有学生重名怎么办?这时候复合主键,联合主键就派上用场了。
复合主键
:主键中包含一个以上的字段,这里通过name,id来确认一个学生
1
2
3
4
5
6
create table students(
id mediumint auto_increment,
name varchar(20),
sex varchar(10),
primary key(id,name);
)
联合主键
:联合主键呢其实和复合主键挺像,区别就是主要体现在多个表上。
比如我有一张学生表和一张课程表,那我在建一张学生课程表,我如何避免学生选择相同的课程两次,那我创建一个联合索引(stu_id,courses_id)就能避免这种情况了,跟复合主键不同的地方就在于,stu_id和courses_id都对应了一张表。
通常能一个字段解决的主键就用一个字段,而不是用多个字段来做主键。
4. 了解完主键后,上文提到了索引,那索引是什么呢?我们都知道索引能加快查询速度,那索引是如何加快查询的,有什么优点缺点….
4.1 索引的分类
主键索引(primary)
:顾名思义,就是主键上用的索引,不能有空值
普通索引(index)
: 这个就是普通索引~~复合索引(index (a,b,c))
:就是一个字段以上的索引,支持三种方式索引查询,[a],[a,b],[a,b,c],没错就是从左到右三种组合,不支持[b,c]这样的查找。唯一索引(unique)
:唯一索引就是索引的那一列中的值不能有相同的,要唯一,所以叫唯一索引!全文索引(fulltext)
:就是从数据库中筛选中我们想要的搜索结果,类似搜索引擎的功能。
4.2 索引的创建,查看,删除
1
2
3
4
5
6
7
创建普通索引:create table stu(id int,name varchar(20),index(name));
创建唯一索引:create table stu(id int,name varchar(20),unique index(name));
create index <索引名> on <表名>[字段]
创建:create table stu(id int,name varchar(20),fulltext key name_fulltext(name) );
创建全文索引:create fulltext index <全文索引名> on <表名>[字段名1,字段名2..]
查看索引:show index from <表名> [from<数据库名>]
删除索引:drop index <索引名> on <表名>
其实记住下面这个就行了
1
2
CREATE TABLE table_name[col_name data type]
[unique|fulltext|spatial][index|key][index_name](col_name[length])[asc|desc]
4.3 现在知道怎么创建索引啦,那我们什么情况下应该使用索引呢?
加索引的情况
- 当我们经常频繁使用某个字段的时候,我们可以考虑加索引,提高查询速度。
- 在需要order by,group,distinct,where的字段上可以考虑加索引。
- 当一张表,尽可能的不需要修改,只需要查询时,可以加索引,加快查询。
- 简单说就是,对经常需要使用的字段,又发现速度不高的时候,可以考虑加索引。
加索引的优点
- 显而易见的,提高了查询效率。
- 提高表跟表之间的连接。
- 对排序,分组友好,也就是上面说的情况。
加索引的缺点
- 每增加一个索引,索引都会占用一定的空间,表的体积会变大,占用更多的磁盘空间。
- 当进行增加,删除,修改操作时,还需要维护索引的,降低数据库的性能。
4.4 知道了索引的优缺点,顺便记录一些常用的索引优化。
like参数,以通配符开头
:这样数据库进行全盘扫描,而不是使用索引。where语句后符合最左前缀的原则
:比如,学生表中,学号的索引用的最频繁,那学号索引就排在最左边,名称索引用的少就排后边。- 查询时,使用学号就会使用索引,速度更快,
- 但使用名称就会全表扫描,速度慢。
少用!= 或<>操作符
,要不然数据库就会进行全表扫描。索引参与计算
类似 select * from balance where money+1>0;就会进行全表扫描。对字段进行null值判断
类似 select * from balance where money is null, 解决办法:null可以设置成0,然后判断是否等于0。用or连接条件
:用or会进行全表扫描,可以尝试用union all
来代替。
4.5 为什么索引能加快查询速度,索引是怎么实现的。
先占位置
5.mysql增删查改操作
6. 了解sql中的存储过程
6.1 什么是存储过程?
存储过程就是把一些常用的比较繁琐的操作,编写成一个函数,然后我们使用的时候,直接调用这个函数就好了,不同写一长串的sql语句.
- 好处:
- 1.减少重复操作
- 2.统一接口
6.1 创建和调用存储过程
1
2
3
4
5
6
7
创建: create procedure <过程名> [[in|out|inout]参数,类型][标签]: begin <sql语句> end [标签]
举例:
delimiter //
create procedure test()
label1:begin
select * from students;
end label1 //
- in 类似函数输入的参数
- out 类似函数返回的参数
- inout 传进来的参数也是返回值。
调用:使用call来调用存储过程
1
2
调用 call 存储过程名称[参数]
call test();
7. 了解sql中的游标(cusor)
7.1为什么要使用游标,游标是啥玩意。
游标可以帮助我们一行一行的遍历一个表中的内容记录,充当指针的作用,然后我们可以在遍历的过程中进行相应想要的操作!
7.2 游标的定义,使用举例
- 游标定义:
declare
游标名称 cursor for - 定义完游标后的使用
1
2
3
4
5
6
7
declare done int default false;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done=1; --防止游标循环溢出。
open 游标名称;
[while (条件) do|循环名称:loop|repeat]
fetch xxx,xxx into 游标名称; --这里是取出游标中的记录
[end while|end loop|until (条件) end repeat]
close 游标名称;
- 游标的使用例子,在存储过程中使用游标,查看某个学生名字出现的次数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
create producre findName(in name1 varchar(20))
begin
declare stu cursor for select name form students; -- 定义学生表的游标
declare total int default 0;
declare done int default false;
declare continue handler for not found set done=1;
open stu;
repeat
fetch name in stu;
if name1 == name
set total = total +1;
end if;
until done end repeat;
close stu;
select total;
end
8. 了解sql中的视图(view)
视图可以理解成由select语句结果组合成起来的一张
虚拟表
。
- 可以对视图进行增删查改操作,但有条件限制。
- 当原先的表中的数据发生改变,视图也会动态改变。
使用视图的好处。
- 视图配合grant,绑定用户权限和视图,可以隐藏一些敏感信息。
- 写短一点的sql语句。
视图的创建,删除,修改
1
2
3
创建视图 create [algorithm=[megre|temptable|undefined]] view 视图名 as select 查询语句;
修改视图 alter view 视图名 as 新的select查询语句;
删除视图 drop view 视图名;
9. 了解sql中的事务
9.1 什么是事务
事务就是数据库中的一组操作。要保证原子性,要么成功,要么失败回滚,不能只执行了一部分。
事务中有四个特性
- 原子性:就是上面所说的。
- 一致性:就是查看出来的结果是一致的,比如a给b200,一致性就是a-200,b就必定+了200,而不是a-200,b却还没加200.
- 隔离性:不同事务一起操作同一个资源时,进行的隔离级别。
- 持久性:就是能永久的存到数据库中。
9.2 事务隔离级别,以及在并发时可能出现的问题。
- 脏读:两个事务同时操作一个资源时,事务A还没修改完成,事务B就读取了资源并进行处理,这个资源就是”脏数据”
- 幻读:同一个事务,同样的查询条件,前次和后次多或少了字段。
强调数据的新增或删除
- 不可重复读:同一个事务,同样的查询条件,前次和后次不一样,
强调的是数据的被修改
- 更新丢失:两个事务同时操作一个资源时,先后覆盖,其中一个数据的更新丢失。
数据的隔离级别
读取未提交内容
:事务间可以看到对方未提交
的执行结果,可能出现脏读
的问题读取提交内容
:事务间可以只能看到对方提交后的执行结果
,这样就不会有脏读的问题啦,但可能出现不可重读
的问题,第二次跟第一次读取的结果可能不一致,因为可能变其他事务修改过了。同样也可能出现幻读
。可重读
:可重读解决了同一sql语句前后执行结果不一致的情况,但幻读
依然会发生!可串行化
:最强的隔离级别,事务中每次读写都加锁,但这样速度就会慢很多,以及一些锁的问题。
数据库中是如何解决这些问题的。
- 加锁
- 使用MVCC,多版本并发控制(
行级锁的变种
)
9.3 上面中提到了锁,现在就来说说锁。
- 乐观锁:默认不是并发环境,不加锁,先跑,失败后在根据策略处理
- 悲观锁,默认就是并发环境,都上锁!
共享锁(读锁),排他锁(写锁)
- 共享锁,同一时刻,允许多个连接读取同一资源。
- 排他锁,同一时刻,只能有一个连接可以进行对资源进行写操作。
表锁,行锁
- 表锁:某个连接占用整个表的写锁,导致其他连接这个表的都会阻塞。
- 行锁: 某个连接占用了表中的行的写锁,其他连接这个行的连接会阻塞。
select操作
- select * from table 使用的mvvc,不用加锁
- select * from table lock in share mode; 加了共享锁。
- select * from table for update; 加了排他锁。
insert,update,detele,操作都会加写锁。
- select * from table where ? for update
- insert into table values;
- update table set ? where?;
- detele from table where?;
死锁问题,双方都在等待对方的资源,导致无限等待。
- 可能发生的情况,两个事务中都互相占用了对方接下来准备获得的锁,就会发生死锁。
- 简单的办法,我们让其中给事务设置一个
等待超时回滚时间
,用·
innodb_lock_wait_timeout`来设置超时时间 - 使用wait-for graph算法来主动进行死锁检测,这个不太清楚。
避免死锁的情况
- sql按顺序来访问表和行
- 事务避免太大!
结论就是在
可重读
级别下,我们通过加锁,可以防止幻读的问题。
疑问1,mvvc是如何实现的?
10. 了解sql中的触发器
10.1 什么是触发器
触发器就是与表有关的数据库对象,满足一定的条件时就会触发,并执行其中的语句!
创建触发器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
CREATE
trigger 触发器名称
trigger_time trigger_event
ON tbl_name for each row
[trigger_order]
trigger_body
trigger_time: { BEFORE | AFTER } --事件前执行,事件后执行
trigger_event: { INSERT | UPDATE | DELETE } --插入或更新或删除时执行触发器
trigger_order: { FOLLOWS | PRECEDES } 其他触发器名称
follows,在触发器之后
Precedes,在触发器之前
查看触发器
触发器信息都储存在information_schema数据库下的triggers表,可以使用select来查看。
参考文章
- https://juejin.im/post/5c2c8dace51d455d382ee046
- https://zhuanlan.zhihu.com/p/23624390
- https://zhuanlan.zhihu.com/p/27700617
- https://blog.csdn.net/qq_30546099/article/details/86610107
- https://juejin.im/post/5a9ca0d6518825555c1d1acd
- https://blog.csdn.net/qq_22222499/article/details/79060495