范式是设计数据库表结构时主要遵守的规则,一般情况下,遵守前三个范式,设计出的表结构就是合理;若设计的表结构违反了前三个范式任意之一,表结构一定不合理
百度百科描述
设计关系数据库中表结构时,遵从不同的规范要求,设计出合理的关系型数据库,这些不同的规范要求被称为不同的范式,各种范式呈递次规范,越高的范式数据库冗余越小
关系数据库有六种范式:第一范式(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的基础上,非码属性必须完全依赖于候选码(在1NF基础上消除非主属性对主码的部分函数依赖)
- 候选码:在表中可以用于唯一的表示一条记录,作为主键的候选存在
- 非码属性:非候选码
第二范式(2NF)是在第一范式(1NF)的基础上建立起来的,即满足第二范式(2NF)必须先满足第一范式(1NF)。第二范式(2NF)要求数据库表中的每个实例或记录必须可以被唯一地区分。选取一个能区分每个实体的属性或属性组,作为实体的唯一标识。例如在员工表中的身份证号码即可实现每个员工的区分,该身份证号码即为候选键,任何一个候选键都可以被选作主键。在找不到候选键时,可额外增加属性以实现区分,如果在员工关系中,没有对其身份证号进行存储,而姓名可能会在数据库运行的某个时间重复,无法区分出实体时,设计辟如ID等不重复的编号以实现区分,被添加的编号或ID选作主键。(该主键的添加是在ER设计时添加,不是建库时随意添加)
第二范式(2NF)要求实体的属性完全依赖于主关键字,所谓完全依赖是指存在仅依赖主关键字一部分的属性,如果存在,那么这个属性和主关键字的这一部分应该分离出来形成一个新的实体,新实体与原实体之间是对多的关系,为实现区分通常需要为表加上一个列,以存储各个实例的唯一标识。简而言之,第二范式就是在第一范式的基础上属性完全依赖于主键。
解读:非码属性必须完全依赖于候选码,消除非主属性对主码的部分函数依赖
在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)死锁是一种现象,是由于排他锁相互作用,锁添加不合适导致出现的问题现象。该现象为多事务并发执行,出现了事务之间彼此阻塞,彼此无法继续执行的现象
不同版本对死锁处理方式可能不同,整体分为两种处理方式:
悲观锁和乐观锁是两种思想
悲观锁是基于数据库中的排他锁实现的,乐观锁没有使用数据库中的锁
悲观锁:在多线程并发执行时,有可能会产生线程安全问题,为了保证线程安全,悲观锁是指:某个线程总是悲观的认为在自己执行期间,总有其他线程与之并发执行,此时可能会产生线程安全问题,为了保证线程安全,当前线程直接给操作数据加排它锁,从而保证线程安全。这种方式在保证线程安全的同时并发执行效率低
synchronized
,数据库中的排他锁等乐观锁:在多线程并发执行时,某线程总是乐观的认为,在其执行期间没有人与之并发执行,所以不会给对象加锁,但是实际上确实可能存在线程与之并发,就有可能产生线程安全问题,此时为了保证线程安全,采用版本号机制。这种方式可以实现在保证线程安全的同时提高并发执行效率
ConcurrentHashMap
索引:(index)索引是作用于列上,为该列的数据形成目录,从而提高该列数据的查询效率。索引通常作用于数据量大的表中
索引的数据结构是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
时的特点
如果没有创建id,非聚集索引中的value
值,系统会默认创建id进行填充吗?
同样,没有主键存储引擎会自动为表添加一个主键,类型为
long
,长度为 6 ,为该主键添加聚集索引,那么非聚集索引的value
值指向这个主键,再次查到主键对应的值
非聚集索引的底层是聚集索引的话,那为啥还要分非聚集索引呢?
真实查询条件根据业务需求进行查询,业务中不止是只通过id进行查询数据,若数据量大情况下,业务中还会使用到如名字等其他字段,此时若添加索引即添加非聚集索引
索引是如何提高查询效率的
创建索引:
create index index_name on table(col)
查询索引:
show index from table_name
删除索引:
drop index index_name on table_name
索引失效不是指索引消失了,而是在一些场景会放弃使用索引,执行全表扫描
对添加了索引的字段进行了运算,此时会放弃索引,执行全表扫描
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)