1.基本概念
1.1.名词
元组 : 元组(tuple)是关系数据库中的基本概念,关系是一张表,表中的每行(即数据库中的每条记录)就是一个元组,每列就是一个属性。 在二维表里,元组也称为行。
码 :码就是能唯一标识实体的属性,对应表中的列。
候选码 : 若关系中的某一属性或属性组的值能唯一的标识一个元组,而其任何、子集都不能再标识,则称该属性组为候选码。例如:在学生实体中,“学号”是能唯一的区分学生实体的,同时又假设“姓名”、“班级”的属性组合足以区分学生实体,那么{学号}和{姓名,班级}都是候选码。
主码 : 主码也叫主键。主码是从候选码中选出来的。 一个实体集中只能有一个主码,但可以有多个候选码。
外码 : 外码也叫外键。如果一个关系中的一个属性是另外一个关系中的主码则这个属性为外码。
主属性 : 候选码中出现过的属性称为主属性。比如关系 工人(工号,身份证号,姓名,性别,部门). 显然工号和身份证号都能够唯一标示这个关系,所以都是候选码。工号、身份证号这两个属性就是主属性。如果主码是一个属性组,那么属性组中的属性都是主属性。
非主属性: 不包含在任何一个候选码中的属性称为非主属性。比如在关系——学生(学号,姓名,年龄,性别,班级)中,主码是“学号”,那么其他的“姓名”、“年龄”、“性别”、“班级”就都可以称为非主属性。
函数依赖(functional dependency) :若在一张表中,在属性(或属性组)X 的值确定的情况下,必定能确定属性 Y 的值,那么就可以说 Y 函数依赖于 X,写作 X → Y。
部分函数依赖(partial functional dependency) :如果 X→Y,并且存在 X 的一个真子集 X0,使得 X0→Y,则称 Y 对 X 部分函数依赖。比如学生基本信息表 R 中(学号,身份证号,姓名)当然学号属性取值是唯一的,在 R 关系中,(学号,身份证号)->(姓名),(学号)->(姓名),(身份证号)->(姓名);所以姓名部分函数依赖与(学号,身份证号);
完全函数依赖(Full functional dependency) :在一个关系中,若某个非主属性数据项依赖于全部关键字称之为完全函数依赖。比如学生基本信息表 R(学号,班级,姓名)假设不同的班级学号有相同的,班级内学号不能相同,在 R 关系中,(学号,班级)->(姓名),但是(学号)->(姓名)不成立,(班级)->(姓名)不成立,所以姓名完全函数依赖与(学号,班级);
传递函数依赖 : 在关系模式 R(U)中,设 X,Y,Z 是 U 的不同的属性子集,如果 X 确定 Y、Y 确定 Z,且有 X 不包含 Y,Y 不确定 X,(X∪Y)∩Z=空集合,则称 Z 传递函数依赖(transitive functional dependency) 于 X。传递函数依赖会导致数据冗余和异常。传递函数依赖的 Y 和 Z 子集往往同属于某一个事物,因此可将其合并放到一个表中。比如在关系 R(学号 , 姓名, 系名,系主任)中,学号 → 系名,系名 → 系主任,所以存在非主属性系主任对于学号的传递函数依赖。。
1.2. 数据库范式
1NF:表所有字段都是1NF
2NF:消除了非主属性对于码的部分函数依赖。
3NF:消除了非主属性对于码的传递函数依赖
举例:
id 手机号 身份证号 姓名 系号
id是主码 手机号 、身份证号是候选码(主属性),姓名是非主属性,系号是用于传递函数依赖
拆分手机号和身份证号就消除了2NF,拆分系号就消除了3NF
1.2. DDL、DML
DML(Data Manipulation Language):表内容CRUD
DDL(Data Definition Language):库表结构CRUD
drop table XXX;
truncate table XXX;
#
delete from where id = XXX;
2.字符集
2.1.数据库字符集
utf8
utf8mb4:可以存储emoji和复杂的汉字。
由于计算机只能存储二进制数据,所以
字符->二进制称为:字符编码
二进制->字符称为:字符解码
2.2.计算机字符集
2.2.1 ASCLL
2.2.2 GBK
包含汉字。
2.2.3 UTF-8
包含几乎所有字符(中英文)
3.Mysql
3.0 Mysql基础
3.0.1 基本概念
关系型数据库:数据之间关系是1-1,1-多,多-多。
3.1 Innodb
3.1.1 基本内容
Mysql:默认REPEATABLE-READ
(虽然是可重复读,但是却实现了串行化,所以保证了幻读的存在)
3.1.2 Innodb锁算法
- Record Lock:记录锁,单行记录锁。
- Gap Lock:间隙锁,锁定一个范围,不包括记录本身
- Next-key lock:record+ Gap
3.2 事务
3.2.1 基本概念
事务:进行一项操作,要么都执行,要么都不执行。
3.2.2 ACID
Atomicity原子性:要么完成,要么失败 实现方式:undo log回滚日志
Consistency一致性:一个转账一个收账,钱最终总和应该是一样 实现方式:其他3个得以保证后一致性就可以得到保证。
Isolation隔离性:各个事务独立开来。实现方式:锁(行级锁、表锁....)与MVCC
Durability持久性:只要事务提交,应该是持久存于硬件上。 实现方式:redo log重做日志
3.2.3 Mysql事务与事务隔离级别
隔离级别 | *脏读* | *不可重复读* | *幻读* |
---|---|---|---|
*读未提交* | 有 | 有 | 有 |
*读已提交* | 无 | 有 | 有 |
*可重复读* | 无 | 无 | 有 |
*串行化* | 无 | 无 | 无 |
脏读:一个事务读取到另一个事务还未提交的数据。
不可重复读:在一个事务中多次读取同一个数据时,结果出现不一致(有一个事务进行了修改)。
幻读:在一个事务中使用相同的 SQL 两次读取,第二次读取到了其他事务新插入的行。
3.2.4 分布式事务
分布式事务:多个独立事务合到整体事务中,如游戏中每个人准备比赛。所以分布式事务一定是可串行化。
3.3 Mysql千行笔记
参考笔记:https://javaguide.cn/database/mysql/a-thousand-lines-of-mysql-study-notes/#数据库操作
注:可以当参考笔记翻阅,内部表的from、列子查询、行子查询需要熟练掌握。
3.4 Mysql高性能优化
3.4.1 基本设计规范
1. 必须使用Innodb存储引擎
2. 使用utf8mb4格式(字符集格式不一致会导致索引失效)
3. 单表数据量控制在500w以内
4. 合理设计库表
1. 慎用分库分表,防止跨分区查询效率低下
2. 热点数据放到一张表内,减少无用字段列。
3. MySQL 限制每个表最多存储 4096 列,并且每一行数据的大小不能超过 65535 字节
5. 禁止表中建立预留字段
6. 禁止在库中存储图片,文件等二进制数据。
3.4.2 字段设计规范
1.所有字段定义非空
2.对金额小数点要求严格的使用deciaml存储数据。
3.4.3 索引设计规范
1.单表所以不可超过5个
2.禁止单个索引,采用联合索引。
3.索引应在where、order by、group by、distinct、join这些字段中。
4.联合索引中,遵循最左原则,把热点字段、字段小的、区分度最高的放到最左边.
5.避免使用外键约束
3.4.4 SQL优化
1. 禁止使用select *,改用select 字段。
2. 避免使用子查询,改用join
3.5 Mysql索引
3.5.1 Hash表
键值对的集合,基于Hash算法能快速根据key取出value
缺点:不支持顺序、范围查询,存在key键冲突
3.5.2 B树&B+树
区别:
- B树所有节点既放键也放值,而B+树只有叶子结点放key和value,其他非叶子结点放key
- B+树叶子结点之间有引用链,B树叶子结点之间独立,
- B+树检索是从根到叶子结点,而B树是二分,不一定能到达叶子结点。
MyISAM引擎:采用B+树-非聚簇索引(回表):先取key值,再取value值,然后根据value值读取对应数据。(索引文件与数据文件分开)
InnoDB引擎:采用B+树-聚簇索引:先取key值,value就是主键对应的值。其余辅助索引和非聚簇索引一样,先查key的value查出主键值,然后再去key值(主键),找到value就是主键对应的值(索引文件就是数据文件)
3.5.3 索引类型
1. 主键索引:默认是id,没有的话采用唯一索引做主键,再没有会自动创建一个6Byte隐藏的自增主键
2. 二级索引(辅助索引):叶子结点是主键的值。以下都是二级索引
1. 唯一索引:唯一约束
2. 普通索引:加快查找速度
3. 前缀索引:根据前缀加快查找速度
4. 全文索引:加快大文本数据查询
#主键索引查询流程:直接根据id=1的key查询索引-数据文件
select * from A where id = 1;
#二级索引查询流程:先根据name查询主键id,这里索引文件应该是包含了比如100个主键中包含name ="胡宇乔",然后依次回表查询100行记录
select * from B where name = "胡宇乔";
3.5.4 聚集/非聚集索引、覆盖索引
聚集索引(主键索引属于聚集索引):索引文件与数据文件放在一起的。 查询快但是依赖有序数据,更新代价大。
InnoDB中:非叶子结点存储索引,叶子结点存储索引与数据。
非聚集文件(二级索引属于非聚集索引):不放在一起。更新数据代价小,依赖有序数据,要回表根据主键到数据文件中查询。
覆盖索引:索引包含了查询字段的值,如下:,这里就不需要回表操作了。
#注意与上面select * from B where name = "胡宇乔"区别
select name from B where name = "胡宇乔";
3.5.5 创建索引注意点
- 字段不为NULL(所以建表默认给0,1,true,false)、频繁查询(但不频繁更新,比如status状态)
- 频繁排序字段: create_time,modify_time
- 联表查询字段:比如XXX_id。
- 多建联合索引,避免出现单个的冗余索引:能命中(A,B)就一定能命中A:比如idx_phone_unionid
3.6 Mysql 日志--Binlog、Redolog、Undolog(刷盘内容待优化)
3.6.1 Redolog
redolog重做日志:物理日志,记录了数据页的修改(并非库表某行某字段的更新)。属于Innodb数据引擎
原理:Mysql会先把数据页读到Buffer Pool 中,后续查询从Buffer Pool查询,更新表数据时,直接在Buffer Pool更新,然后记录到redo log buffer中,清空redo log buffer刷盘到redo日志中。
3.6.2 Binlog
binlog归档日志:逻辑日志,记录某行某字段的更新(主从复制等等要用到)
原理:事务执行时,先把日志写到binlog cache,执行提交时,把binlog cache写到binlog中。
3.6.3 两阶段提交
1. Redo Log:让InnoDB具有了崩溃恢复能力
2. BinLog:保证Mysql 集群的数据一致性
之前没有两阶段提交的时候,若redo log写入正常,bin log错误,后续恢复的时候binlog由于没有记录不会回滚,redo log不回滚就会覆盖,导致数据错误。
两阶段提交:即将redo log的写入分成prepare(事务前)与commit(事务内快结束的时候)。
场景一:当事务--写bin log出错时,redo log恢复时发现处于prepare,就要回滚该事务,保证数据正确。
场景而:当事务--commit阶段出错,而bin log还在,就会提交该事务。
3.6.4 Undo log
回滚日志,保证事务原子性。
3.7 MVCC
1. 普通Select:以MVCC快照读读取数据
2. select...for update/lock in share mode,insert,update,delete当前读:采用Next-key Lock防止出现幻读
3.8 Mysql基础架构
3.8.1 基本知识点
Mysql基础架构分为:
1. Server层:跨存储引擎功能:连接器、分析器、优化器、执行器,存储过程,视图函数、BinLog日志模块
1. 存储引擎:负责数据的存储与读取:Innodb(自带redolog模块)
注:由于设计到事务回滚,需要区分出查询语句与非查询语句,两者核心区别在于redolog是否进入两阶段提交。
3.9 Mysql 存储时间
3.9.1 Timestamp、DateTime
TimeStamp:与时区有关,跟随服务器时间变化而变化(推荐)
DateTime:与时区无关,会导致时间误读
4.Redis
4.1 Redis基本知识点
4.1.1 Redis基础知识点
Redis:基于内存的非关系型数据库,可做缓存、分布式锁、消息队列。支持多种数据类型、事务、持久化、Lua脚本,多种集群。
写锁(排他锁、X锁):当加入了X锁后,其他事务不能加任何锁
读锁(共享锁、S锁):当加入S锁后,其他事务只能加S锁,不能加X锁。故而保证了阻塞
注:只要有写锁就要阻塞
4.1.2 Redission分布式锁
4.2 Redis--高并发、高性能
4.2.1 基本内容
QPS(Query Per Second):服务器每秒可以执行的查询次数;
TPS:服务器每秒吞吐量
Redis功能:
1. 缓存
2. 分布式锁
3. 限流
4. 消息队列
5. 统计榜单
4.2.2 Redis设计与实现
Redis基于Reactor模式开发了网络事件处理器(文件事件处理器),该处理器使用I/0多路复用来监听多个套接字(异步阻塞I/O)
4.2.3 Redis为什么不使用多线程?
单线程(6.0之后多线程,为了提高网络IO读写性能)
1. 单线程实现容易,易于维护
1. Redis性能瓶颈不在于CPU,在于内存与网络传输
1. 多线程会导致死锁,线程上下文切换等问题,甚至会影响性能
4.2.4 Redis为什么给缓存设置过期时间
1. 缓存数据一直保存容易OOM
1. 键长存会影响业务。
4.2.5 Redis如何判断数据是否过期
Redis通过一个过期字典(Hash表, key 是redis的key,值是过期时间)
4.2.6 过期数据删除策略
Redis采用的是定期+惰性/懒汉式删除结合使用。
- 惰性/懒汉式删除:取出的时候才删除(对CPU友好)
- 定期删除:每隔一段时间抽取一批key进行删除(对内存友好)
但依然可能会有key值过期了没有删除,所以就有了内存淘汰机制
4.2.7 内存淘汰机制
内存淘汰:内存不够用时,需要移除一些键
- volatile-lru:从已设置过期时间的数据集中,选最近最少使用淘汰
- volatile-ttl:从已设置过期时间的数据集中,选要过期的淘汰。
- volatile-ttl:从已设置过期时间的数据集中,随机选择淘汰
- allkeys-lru:从所有键空间中,选最近最少使用淘汰(*最常用)
- allkeys-random:从所有键空间中,随机淘汰
- no-eviction:内存不足时,外部禁止写入。
4.0后增加了
7. volatile-LFU:从已设置过期时间的数据集中,选最不经常使用
7. allkeys-LFU:从所有键空间中,移除最不经常使用
可以看到,redis内存淘汰策略分为2LRU,2随机,1禁止,1ttl,2LFU
4.2.8 Redis持久化机制
持久化:redis挂了之后再重启数据可以恢复
实现方式
1. SnapShotting-RDB:快照
2. Append-Only File AOF:只追加文件
Redis不满足原子性(不支持roll back)
4.2.9 Redis--缓存击穿、缓存穿透、缓存雪崩
-
缓存穿透:缓存和数据库中都没有数据
-
解决方案:1.布隆过滤器 2.存储空对象:数据库没找到后,redis中临时存一个空对象
-
布隆过滤器:说某元素不存在,就一定不存在,说某元素存在,不一定存在
原因:布隆过滤器是采用哈希函数计算存储的元素值,多个元素会出现哈希值一样的情况,但若每次查询位数组的时候,发现哈希的数组不存在,则一定没有该元素
-
-
-
缓存击穿:某个key值过期,来了大量访问
- 解决方案:1.永不过期 2.分布式锁:一个线程获取,其他线程等待
-
缓存雪崩:海量key值过期,来了大量访问
- 解决方案:1.redis高可用:多设置几台redis 2.限流降级:缓存失效后,通过加锁或者队列来控制读数据库写缓存的数量 3.数据预热:大量数据加载到缓存中,根据不同访问量来设置不同过期时间
4.2.10 Redis--缓存与数据库数据一致性
更新数据库,删除redis
删除缓存失败--增加Cache更新重试机制:即重试多次删除,比如3次,还是失败的话key值到加入队列,后续缓存恢复了继续删。
4.3 Redis缓存读写策略
4.3.1 Cache Aside Pattern旁路缓存模式
写:更新DB,删除Cache
读:从cache读,没有就从db读,然后把数据放入Cache
缺点:依然会有数据不一致情况,比如A正在读db-a,B来写db然后删了cache,但是A读完写入cache了。之所以还采用这种策略是因为缓存写入操作比db操作快很多
改进:加入分布式锁保证线程安全。
4.3.2 Read/Write Through Pattern(读写穿透)
4.3.3 Write Behind Pattern(异步缓存写入)
书山有路勤为径,学海无涯苦作舟。程序员不仅要懂代码,更要懂生活,关注我,一起进步。