数据库高级🦅
文章目录
- 数据库高级🦅
- 范式🦍
- 什么是范式🐊
- 第一范式——1NF🦖
- 第二范式——2NF🐟
- 第三范式——3NF🐉
- 总结🐙
- 五大约束🐏
- 主键约束🐡
- 外键约束🐚
- 唯一性约束🐞
- 非空约束🐜
- 检查约束🐝
- 存储过程(了解)🐄
- 创建存储过程🦋
- 存储过程的优缺点🦕
- 视图🦥
- 视图的使用🦆
- 视图的创建🦚
- 查看视图是否存在🦜
- 查看视图中的数据🐧
- 删除视图🦉
- 注意🐤
- 事务🐇
- 手动事务管理🐘
- 事务的特点🦡
- 事务的隔离级别🦙
- 隔离级别以及可能会产生的问题🦛
- 不可重复读和幻读的区别🦄
- 数据库的默认隔离级别🐆
- 锁🦃
- 锁的分类🐫
- 死锁🐅
- 模拟死锁的步骤🐕
- 数据库对死锁的处理🦌
- 悲观锁和乐观锁🐕🦺
- 索引🦔
- 索引形成目录的原理(B+Tree)🐎
- `BTree`和`B+Tree`的不同🦘
- 索引的分类🐿️
- 聚集索引 / 主索引——给主键添加的索引🐂
- 非聚集索引 / 辅助索引——给非主键字段添加的索引🐩
- 索引的操作🐖
- 索引的使用——什么时候用🦓
- 索引的失效场景(面试高频)🦧
范式🦍
什么是范式🐊
范式是设计数据库表结构时主要遵守的规则,一般情况下,遵守前三个范式,设计出的表结构就是合理;若设计的表结构违反了前三个范式任意之一,表结构一定不合理
百度百科描述
设计关系数据库中表结构时,遵从不同的规范要求,设计出合理的关系型数据库,这些不同的规范要求被称为不同的范式,各种范式呈递次规范,越高的范式数据库冗余越小
关系数据库有六种范式:第一范式(1NF)、第二范式(2NF)、第三范式(3NF)、巴斯-科德范式(BCNF)、第四范式(4NF)和第五范式(5NF,又称完美范式)
范式来自英文
Normal form
,简称NF。要想设计一个好的关系,必须使关系满足一定的约束条件,此约束已经形成了规范,分成几个等级,一级比一级要求得严格。满足这些规范的数据库是简洁的、结构明晰的,同时,不会发生插入(insert)、删除(delete)和更新(update)操作异常。反之则是乱七八糟,不仅给数据库的编程人员制造麻烦,而且面目可憎,可能存储了大量不需要的冗余信息关系数据库有六种范式:第一范式(1NF)、第二范式(2NF)、第三范式(3NF)、巴斯-科德范式(BCNF)、第四范式(4NF)和第五范式(5NF,又称完美范式)。满足最低要求的范式是第一范式(1NF)。在第一范式的基础上进一步满足更多规范要求的称为第二范式(2NF),其余范式以次类推。一般来说,数据库只需满足第三范式(3NF)就行了
第一范式——1NF🦖
所谓第一范式(1NF)是指在关系模型中,对于添加的一个规范要求,所有域都是原子性的,即数据库表的每一列都是不可分割的原子数据项,不是集合,数组,记录等非原子数据项。即实体中的某个属性有多个值时,必须拆分为不同的属性。在符合第一范式(1NF)表中的每个域值只能是实体旳一个属性或一个属性的一部分。简而言之,第一范式就是无重复的域
说明:在任何一个关系数据库中,第一范式(1NF)是对关系模式的设计基本要求,一般设计中都必须满足第一范式(1NF)。不过有些关系模型中突破了1NF的限制,这种称为非1NF的关系模型。换句话说,是否必须满足1NF的最低要求,主要依赖于所使用的关系模型
解读:所有的列必须是原子性的
第二范式——2NF🐟
在1NF的基础上,非码属性必须完全依赖于候选码(在1NF基础上消除非主属性对主码的部分函数依赖)
- 候选码:在表中可以用于唯一的表示一条记录,作为主键的候选存在
- 非码属性:非候选码
第二范式(2NF)是在第一范式(1NF)的基础上建立起来的,即满足第二范式(2NF)必须先满足第一范式(1NF)。第二范式(2NF)要求数据库表中的每个实例或记录必须可以被唯一地区分。选取一个能区分每个实体的属性或属性组,作为实体的唯一标识。例如在员工表中的身份证号码即可实现每个员工的区分,该身份证号码即为候选键,任何一个候选键都可以被选作主键。在找不到候选键时,可额外增加属性以实现区分,如果在员工关系中,没有对其身份证号进行存储,而姓名可能会在数据库运行的某个时间重复,无法区分出实体时,设计辟如ID等不重复的编号以实现区分,被添加的编号或ID选作主键。(该主键的添加是在ER设计时添加,不是建库时随意添加)
第二范式(2NF)要求实体的属性完全依赖于主关键字,所谓完全依赖是指存在仅依赖主关键字一部分的属性,如果存在,那么这个属性和主关键字的这一部分应该分离出来形成一个新的实体,新实体与原实体之间是对多的关系,为实现区分通常需要为表加上一个列,以存储各个实例的唯一标识。简而言之,第二范式就是在第一范式的基础上属性完全依赖于主键。
解读:非码属性必须完全依赖于候选码,消除非主属性对主码的部分函数依赖
第三范式——3NF🐉
在2NF基础上,任何非主属性不依赖于其它非主属性(在2NF基础上消除传递依赖)
第三范式(3NF)是第二范式(2NF)的一个子集,即满足第三范式(3NF)必须满足第二范式(2NF)。简而言之,第三范式(3NF)要求一个关系中不包含已在其它关系已包含的非主关键字信息。例如,存在一个部门信息表,其中每个部门有部门编号(dept_id)、部门名称、部门简介等信息。那么在员工信息表中列出部门编号后就不能再将部门名称、部门简介等与部门有关的信息再加入员工信息表中。如果不存在部门信息表,则根据第三范式(3NF)也应该构建它,否则就会有大量的数据冗余。简而言之,第三范式就是属性不依赖于其它非主属性,也就是在满足2NF的基础上,任何非主属性不得传递依赖于主属性。
总结🐙
- 第一范式:指所有的列必须是原子性的
- 第二范式:非码属性必须完全依赖于候选码
- 第三范式:非主属性不能依赖于其他非主属性
五大约束🐏
约束:(constraint)添加在表中的某个 / 某些字段上,使得字段的值必须遵循某一些特定的约束
主键约束🐡
primary key
# 特点 元素唯一且非空
eg:
create table t_name(
id int primary key,
...
)
外键约束🐚
foreign key
# 作用 A表中的非主键字段引用自另一个表中的主键字段,则该字段称为外键 一个表可设置多个外键
# 特点 若某个主键值作为外键使用,则外键中使用该主键值的期间,该主键值无法删除(除非将外键值中的引用全部删除)
eg:
create table t_name(
id int primary key
name varchar(20),
dept_id int,
foreign key(dept_id) references dept(id)
)
唯一性约束🐞
unique
# 特点 字段的值唯一存在,不可重复
eg:
create table t_name(
id int primary key,
name varchar(20) unique, #可以为空,但空只有一次
age int
)
非空约束🐜
not null
# 特点 字段的值不允许为null
eg:
create table t_name(
id int primary key,
name varchar(20) unique,
age int not null
)
检查约束🐝
check
# 作用 添加给某字段,向该字段添加值时,会检查添加的值是否满足检查约束给出的条件
# 注意 mysql不支持检查约束,sql server,Oracle,Mariiadb10.xx均支持
eg:
create table t_name(
id int primary key,
name varchar(20),
age int check(age between 10 and 20) #两个边界值均包含
)
存储过程(了解)🐄
存储过程:(procedure)用于封装一段过程,封装之后,后续若要再次调用该过程,调用名称即可,在有多个sql语句的业务中,若后期其他地方也要执行该操作,若将所有sql语句重写一遍,可实现结果,但若后期是多次执行以上操作,多次重写以上sql,编写代码效率相当低效,应该将过程进行提取,就可以使用存储过程来封装该过程,后期若想再次执行该过程,直接调用存储过程名即可
创建存储过程🦋
create procedure name(xx,xx,xx)
begin
select...;
delete...;
update...;
...
end$$
#修改结束符,存储过程结束符默认是; 但是每条sql语句后也是,结束符需要进行修改 设置方式: delimiter $$ 设置结束符为$$
# 调用存储过程
call name(...)
存储过程的优缺点🦕
优点:
- 存储过程可封装,并隐藏复杂的商业逻辑。
- 存储过程可以回传值,并可以接受参数。
- 存储过程无法使用
SELECT
指令来运行,因为它是子程序,与查看表,数据表或用户定义函数不同。 - 存储过程可以用在数据检验,强制实行商业逻辑等。
缺点:
- 存储过程,往往定制化于特定的数据库上,可移植性差。因为支持的编程语言不同。当切换到其他厂商的数据库系统时,需要重写原有的存储过程。
- 存储过程的性能调校与撰写,受限于各种数据库系统。
视图🦥
视图:(view)用于保存查询的结果集,视图中并不保存数据,视图是张虚拟表,但其数据来源于真实表中。视图实质上时用于封装查询语句的。如:列表页面中要查询2个表中满足一些条件的部分数据,此时就会使用两表联查,将结果展示给用户,假设在其他位置也要显示刚才的查询结果,若将刚才的sql语句重写多次,不符合编程规范,此时就应该将以上的查询语句进行提取,提取之后哪里需要再次查询该结果,直接调用该sql语句即可。此时使用视图来保存查询的结果集,其实质为使用视图来封装刚才的查询语句,后期其他地方若要再次查询该结果,直接调用视图即可,即可查询到对应的数据。
视图的使用🦆
视图的创建🦚
# 查询所有员工数据,包括部门名称
select emp1.*,d.name from dept d join emp1 e on d.id = e.dept.id
# 创建视图
create view v_emp as select...
create view v_emp(col1,col2,...) as select... #解决视图中的列名冲突
查看视图是否存在🦜
show tables
查看视图中的数据🐧
select * from v_emp
删除视图🦉
drop view v_emp
注意🐤
- 视图是虚拟表,并不保存数据,其数据来源于真实表中
- 真实表中的数据发生改变,视图的结果也会发生改变
- 视图是用来保存查询结果的,并不用于执行写操作,逻辑角度上,不应该对视图进行增删改操作,但物理角度上,如果一定要对视图执行写操作,一定程度上是可以成功的
- 对视图执行改操作,不论视图来源于单表还是多表 ,都成功
- 对视图执行增删操作,视图来源于单表可成功,来源于多表不成功
事务🐇
事务:事务是数据库中最小的执行单元,不可再分,要么全部成功,要么全部失败
手动事务管理🐘
-- 事务管理: 开启事务 -> 提交事务 / 事务回滚
-- 数据库中的事务管理默认是开启的
-- 增删改执行时,在他们执行之前,事务自动开启,执行成功后,事务自动提交
-- 查不涉及事务
-- 如何查看数据库的事务管理是否开启
show variables like 'autocommit'
-- 设置事务管理自动提交为关闭
set autocommit = off
-- 事务相关命令
begin #开启事务
commit #提交事务
rollback #事务回滚
事务的特点🦡
- 原子性:事务是最小的执行单元,不可再分,要么全部成功,要么全部失败
- 一致性:事务从一个 有效的状态(
valid
)切换到另一个有效的状态 - 隔离性:事务之间彼此隔离,互不影响(事务的内部对外不可见)
- 持久性:事务提交后,所做的操作会持久化到数据库中
事务的隔离级别🦙
-
读未提交:
read uncommited
两个事务并发执行时,一个事务可读取到了另一个事务未提交前的数据
隔离级别为 读未提交,此时会产生 脏读,产生原因是读取到了其他事务未提交 / 回滚前的数据,如何解决脏读:将隔离级别设置为 读已提交
-
读已提交:
read commited
事务只能读取其他事务提交 / 回滚后的数据
隔离级别为 读已提交,解决了 脏读 问题,但是产生了新的问题 不可重复读,产生原因是在事务A执行期间,其他事务对用户1数据进行了修改,如何解决不可重复读:给事务操作的数据加锁,将隔离级别设置为 可重复读
-
可重复读:
repeatable read
给事务操作的数据加锁
隔离级别为 可重复读,解决了 脏读 、不可重复读 问题,问题,但是产生了新的幻读 问题 产生原因是在事务A执行期间,其他事务允许操作本事务的表进行增删操作,如何解决幻读:给表加锁,将隔离级别设置为 可串行化
-
可串行化:
serializable
两事务同时并发执行,执行同一张表时,事务A先给表加锁,事务B阻塞,等待事务A执行结束,事务B开始执行,其隔离级别最高,并发执行效率最低,(此时事务变成同步执行)
隔离级别以及可能会产生的问题🦛
脏读 | 不可重复读 | 幻读 | |
---|---|---|---|
读未提交 | √ | √ | √ |
读已提交 | × | √ | √ |
不可重复读 | × | × | √ |
可串行化 | × | × | × |
注意:隔离级别越高,事务并发执行效率越低
不可重复读和幻读的区别🦄
- 不可重复读和幻读很像,都是同一事务前后两次查询的数据不一致
- 不可重复读:指同一事务查询同一条数据结果不同,即同一条数据不能重复读,针对的是其他事务对本事务的修改操作
- 幻读:指同一事务前后两次读取到的数据总量不同,即总数据量不同,针对的是其他事务对本事务原来数据的增删操作
数据库的默认隔离级别🐆
mysql
默认的隔离级别是 可重复读sql server
和oracle
的默认隔离级别是 读已提交
注意:mysql
的隔离级别为可重复读,但是不会产生幻读,其解决方法是,mysql
在事务中会采用快照读保留第一次查询到的数据,后续若再次执行相同的查询,直接快照即可
锁🦃
锁的分类🐫
-
按锁的粒度分:表锁和行锁
-
按锁的类型分:共享锁和排他锁(独占锁) 意向共享锁、意向排他锁
-
共享锁:给 表 / 行 添加上共享锁后,允许在此基础上再次添加共享锁,但是不允许在此基础上添加排他锁。共享锁通常作用于表
-
排它锁:给 表 / 行 添加上排他锁后,不允许在此基础上添加任何锁。共享锁通常作用于行,也可能会作用于表。事务中对某条数据进行增删改操作,会自动给数据加排他锁
-
手动添加 共享 / 排他锁:
select ... for update #表示添加排他锁 select ... for share #表示添加共享锁
-
死锁🐅
死锁:(DeadLock)死锁是一种现象,是由于排他锁相互作用,锁添加不合适导致出现的问题现象。该现象为多事务并发执行,出现了事务之间彼此阻塞,彼此无法继续执行的现象
模拟死锁的步骤🐕
- 开启两个客户端,并设置自动提交为off
- 在事务A中修改A表中的某条数据,在事务B中修改B表中的某条数据
- 事务A中删除B表中操作的那条数据,此时会出现阻塞
- 事务B中删除A表中操作的那条数据,此时会检测到死锁,数据库会对死锁进行处理
数据库对死锁的处理🦌
不同版本对死锁处理方式可能不同,整体分为两种处理方式:
- 发现死锁的位置,让当前事务回滚,让另一个事务继续执行
- 发现死锁后,互相阻塞一段时间,若超过规定的时间,给出超时提醒
悲观锁和乐观锁🐕🦺
悲观锁和乐观锁是两种思想
-
悲观锁是基于数据库中的排他锁实现的,乐观锁没有使用数据库中的锁
-
悲观锁:在多线程并发执行时,有可能会产生线程安全问题,为了保证线程安全,悲观锁是指:某个线程总是悲观的认为在自己执行期间,总有其他线程与之并发执行,此时可能会产生线程安全问题,为了保证线程安全,当前线程直接给操作数据加排它锁,从而保证线程安全。这种方式在保证线程安全的同时并发执行效率低
- 悲观锁的应用:Java中的
synchronized
,数据库中的排他锁等
- 悲观锁的应用:Java中的
-
乐观锁:在多线程并发执行时,某线程总是乐观的认为,在其执行期间没有人与之并发执行,所以不会给对象加锁,但是实际上确实可能存在线程与之并发,就有可能产生线程安全问题,此时为了保证线程安全,采用版本号机制。这种方式可以实现在保证线程安全的同时提高并发执行效率
- 乐观锁的应用:Java中的
ConcurrentHashMap
- 乐观锁的应用:Java中的
索引🦔
索引:(index)索引是作用于列上,为该列的数据形成目录,从而提高该列数据的查询效率。索引通常作用于数据量大的表中
索引形成目录的原理(B+Tree)🐎
索引的数据结构是B+tree
,该数据结构可将数据形成目录
-
B+Tree
是对BTree
进行了优化BTree
:BTree
对所有元素进行了排序,是一颗度可以自定义的树,每个节点位置保存的是一个数据块,而不是每个元素,定义好度n后,则每个数据块中最多可以保存n-1个元素,当数据块中的元素数量到达n时,此时这个数据块会进行分裂提取,将最中间的元素进行提取,提取到上一级的数据块中,中间元素两侧分裂成2个数据块,依然存在这一层中好处:数据量大的数据形成
BTree
,查询的次数会降低,从而提高查询效率 -
B+Tree
是在BTree
基础上扩展而来的B+Tree
:B+Tree
是基于BTree
扩展而来的,都实现了元素的排序,和BTree
一样都会进行分裂提取,度设置好后,每个数据块中保存的最大元素数量也确定,为n-1个元素,当数据块中的元素数量达到n,此时进行分裂提取,但和BTree
不同的是,若从叶子节点中提取元素,提取后,该元素依旧存在于叶子节点中,若从非叶子节点中提取元素,提取到上一级后,该元素不再存在原来那一层中。
BTree
和B+Tree
的不同🦘
- 提取后的元素是否存在于原层级
- 若从叶子节点中提取元素,提取到上一级后,该元素还存在于叶子节点上
- 若从非叶子节点中提取元素,提取到上一级后,该元素不再存在于原来那一级
B+Tree
中叶子节点的数据块之间有链表进行维护(有序的),好处是可以提高区间范围内数据查询的效率(eg:查询11-15之间所有元素,B+Tree
在叶子节点查到11后根据叶子节点上的链表即可顺序查找直到15的全部元素,不用像BTree
可能还要多次从数据结构中查询多个值)
索引的分类🐿️
聚集索引 / 主索引——给主键添加的索引🐂
mysql
的存储引擎:
- 从
mysql5.x
开始,存储引擎为Innodb
,该存储引擎支持事务和行锁,会自动给每张表的主键字段添加索引(聚集索引),若表中没有主键,则自动为添加lunique
约束的字段添加索引,若都没有,此时Innodb
会自动为表添加一个主键,类型为long
,长度为 6 ,为该主键添加聚集索引
聚集索引构建B+Tree
时的特点
-
所有的索引构建的树中所有的叶子节点数据块中每个元素保存的都是
key-value
,聚集索引的叶子节点中key-value
分别保存的是:key
保存的是主键,value
保存的是该主键对应的一行数据 -
根据主键查询数据时,可以很快的找到对应的行数据
非聚集索引 / 辅助索引——给非主键字段添加的索引🐩
非聚集索引构建B+Tree
时的特点
-
非聚集索引的叶子节点中
key-value
分别保存的是:key
保存的是添加了索引的字段的值,value
主键 -
非聚集索引添加给某字段后,后期查询时根据字段找到id,再根据id去聚集索引中查询对应的数据
如果没有创建id,非聚集索引中的value
值,系统会默认创建id进行填充吗?
同样,没有主键存储引擎会自动为表添加一个主键,类型为
long
,长度为 6 ,为该主键添加聚集索引,那么非聚集索引的value
值指向这个主键,再次查到主键对应的值
非聚集索引的底层是聚集索引的话,那为啥还要分非聚集索引呢?
真实查询条件根据业务需求进行查询,业务中不止是只通过id进行查询数据,若数据量大情况下,业务中还会使用到如名字等其他字段,此时若添加索引即添加非聚集索引
索引是如何提高查询效率的
- 构建树结构,可以大大降低对磁盘的 IO操作(对磁盘进行 IO操作 时间级别为ms)
- 读取到数据块后,会将数据块缓存到内存中,之后对内存进行 IO操作,来遍历元素从而进行大小判断,也会提高查询效率(对内存的 IO操作 时间级别为ns)
索引的操作🐖
-
创建索引:
create index index_name on table(col)
-
查询索引:
show index from table_name
-
删除索引:
drop index index_name on table_name
索引的使用——什么时候用🦓
- 当表中数据量大时,为了提高查询效率,应该使用索引,即表数据量小时,不应选择索引
- 当某字段的值会被频繁修改时,不应该使用索引,因为值修改后会导致索引的重构,某些字段的值几乎是不变,或被修改的频率很低,此时这个字段就适合添加索引
- 索引并不是随意添加给某个字段的,通常是作为查询条件的字段添加索引的
- 一个表的索引不是越多越好,适量就行,最多最多5、6个,因为索引的创建也是需要耗时间的,且索引多占用的额外内存也越多
索引的失效场景(面试高频)🦧
索引失效不是指索引消失了,而是在一些场景会放弃使用索引,执行全表扫描
-
对添加了索引的字段进行了运算,此时会放弃索引,执行全表扫描
eg: select ... from ... where age + 4 > 12
-
对添加了索引的字段使用了函数,此时会放弃索引,执行全表扫描
eg: select ... from ... group by .. having max(age) = 12 eg: select ... sum(xx) from ...
-
对添加了索引的字段使用了左模糊查询,此时会放弃索引,执行全表扫描
eg: select ... from ... where xx like '%_xx'
-
对添加了索引的字段使用了
not in
,此时会放弃索引,执行全表扫描eg: select ... from ... where age not in (11,12,23)