数据库简单知识整理
2023-02-08 10:46:30 16 举报
AI智能生成
数据库是按照数据结构来组织、存储和管理数据的仓库。每个数据库都有一个或多个不同的API用于创建、访问、管理、搜索和复制所保存的数据。我们也可以将数据存储在文件中,但是在文件中读写数据速度相对较慢。所以,现在我们使用关系型数据库管理系统(RDBMS)来存储和管理大量数据。所谓关系型数据库,是指建立在关系模型基础上的数据库,借助于集合代数等概念和方法来处理数据库中的数据。现实世界中的各种实体以及实体之间的各种联系均用关系模型来表示。
作者其他创作
大纲/内容
Mysql
sql语句执行过程
连接管理
查询缓存
语法解析
查询优化
存储引擎执行
基本数据结构区别
char vs varchar
char是固定大小,当存储字符不足时,后面加空字符,varchar是可变大小,varchar(20)只代表最大存储20字符!字符!
C/S 通信方式
TCP/IP
命名管道/共享内存
UNIX Domain socket
InnoDB
页大小为16kb
索引结构为B+树
B树每个节点都存储数据和指针,B+树只有叶子节点存储数据其他节点存储指针,所以在Innodb读取磁盘数据一次16kb的情况下,B树一个节点存储的范围更窄,而B+树存储更长,B+树就可以通过比B树更少的IO次数来找到目标值
B+树所有叶子节点构成一个有序链表,按主键排序来遍历全部记录,能更好支持范围查找。
由于数据顺序排列并且相连,所以便于区间查找和搜索。而B树则需要进行每一层的递归遍历,相邻的元素可能在内存中不相邻,所以缓存命中性没有B+树好。
由于数据顺序排列并且相连,所以便于区间查找和搜索。而B树则需要进行每一层的递归遍历,相邻的元素可能在内存中不相邻,所以缓存命中性没有B+树好。
单表访问方式
const
通过主键列来定位一条记录:select * from test where id =1
ref
通过二级索引来定位一条记录:select * from test where second = 1
ref_or_null
通过二级索引来定位一条记录以及该索引为null的节点: select * from test where second = 1 or second is null
range
在索引列对值进行范围匹配: select * from test where id > 4
index
查询所需要的值都在二级索引上,所以不必进行回表操作: select second from test where second = 1;
all
在聚簇索引上进行全表扫描: select * from test
语句优化
EXPLAIN
select_type
表示查询中每个select子句的类型
table
显示这一行的数据是关于哪张表的
type
ALL
遍历全表
index
遍历索引
range
遍历一个范围
ref
哪些列或常量被用于查找索引列上的值
eq_ref
类似ref,区别就在使用的索引是唯一索引
const
常量匹配
possible_keys
可能用到的索引
Key
实际使用的索引
key_len
使用索引中使用的字节数
ref
表的连接匹配条件,即哪些列或常量被用于查找索引列上的值
rows
找到所需的记录所需要读取的行数
Extra
额外信息
尽量把字段设置为NOT NULL
使用连接(JOIN)来代替子查询(Sub-Queries)
子查询会创建临时表,降低效率
连接操作时尽量使用索引
外键
减少对外键约束的使用,外键会导致操作更耗时且依赖关系复杂
ACID
原子性
一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节
一致性
在事务开始之前和事务结束以后,数据库的完整性没有被破坏
隔离性
数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。
持久性
事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
触发器
mysql触发器是针对单独一行数据的,如果有多行数据操作会导致触发器多次触发,影响效率,尽量不使用
存储过程
类似于mysql里的脚本
索引
普通索引
主键
创建表时未指定会默认创建一个主键索引
唯一索引
数据类型
char
定长字符串
varchar
变长字符串
blob
二进制文本
text
NULL
不能以 = 或者 != 判断,只能 IS NULL / IS NOT NULL
连接
left join
inner join
right join
防止sql注入
应用层拦截
预编译
主从备份
keepalived
分库分表
分表
单表太多字段,一页塞不下几条数据,导致B+树层级深,查询时IO多,耗时
单表容量过大,查询耗时,通过hash到多个表,减少查询时间
分库
单库容量过大,服务器无法支撑,需要创建多个库,分散数据
事务
实现:MVCC
BEGIN
ROLLBACK
COMMIT
每条语句都是一个事务,批处理需要放在同一个事务里,不然耗时
控制变量:tx_isolation
级别
读未提交
读已提交
可重复读
串行化
事务问题
脏读
读到未提交的数据
不可重复读
不可重复读指的是在同一事务内,不同的时刻读到的同一批数据可能是不一样的,可能会受到其他事务的影响,比如其他事务改了这批数据并提交了。通常针对数据更新
幻读
幻读是针对数据插入(INSERT)操作来说的。假设事务A对某些行的内容作了更改,但是还未提交,此时事务B插入了与事务A更改前的记录相同的记录行,并且在事务A提交之前先提交了,而这时,在事务A中查询,会发现好像刚刚的更改对于某些数据未起作用,但其实是事务B刚插入进来的,让用户感觉很魔幻,感觉出现了幻觉,这就叫幻读。
log
undolog
数据库实现中通常会在正常事务进行中,就不断的连续写入Undo Log,来记录本次修改之前的历史值。当Crash真正发生时,可以在Recovery过程中通过回放Undo Log将未提交事务的修改抹掉。InnoDB采用的就是这种方式。
redolog
为了取得更好的读写性能,InnoDB会将数据缓存在内存中(InnoDB Buffer Pool),对磁盘数据的修改也会落后于内存,这时如果进程或机器崩溃,会导致内存数据丢失,为了保证数据库本身的一致性和持久性,InnoDB维护了REDO LOG。修改Page之前需要先将修改的内容记录到REDO中,并保证REDO LOG早于对应的Page落盘,也就是常说的WAL,Write Ahead Log。当故障发生导致内存数据丢失后,InnoDB会在重启时,通过重放REDO,将Page恢复到崩溃前的状态。
redis
数据结构
String
一个string类型的值最大存储512M
incr test 。将test对应的数字值(仅对数字值)增加1。 incrby test 10 。增加10
decr test 。将test对应的数字值(仅对数字值)减少1。decr key 10 减少 10
append wang feifei 。追加
Hash
键值对集合
hset singer wang fei。创建哈希表键值对
hget singer wang 。 获取哈希表键值
hmset singer wang fei wang2 fei2。设置哈希表多个键值对
hmget singer wang wang2。获取哈希表多个键值对
hkeys singer。获取哈希表里所有键
hexists singer wang。查看哈希表里是否存在对应的键。存在返回1,否则0
List
字符串列表
lpush
从左边push进一个元素
lpop
rpush
从右边push进一个元素
rpop
lrange test 0 10 。获取指定范围内的元素
lrem key count value 。 移除和value相等的count个值
Set
String类型的无序集合,不允许重复
sadd test 1
smembers test 。返回test内所有成员
srem test 1 。删除1这个元素
Zset
String类型的有序集合,不允许重复,元素会关联一个double值分数作排序,从小到大
操作
del
如果一个键被成功删除后则命令行输出1,否则输出0。del wang
dump
序列化给定Key为字节数组并返回。dump wang
exists
检查给定key是否存在, 存在返回1,否则返回0。exists wang
get
获取键对应的值,若未设置则为nil。get wang
过期时间
设置一个键的过期时间。expire wang 10 。设置wang键值对10秒过期。以秒计时
expireat wang 14958120423。以时间戳设置过期时间
pexpire wang 100。以毫秒设置过期时间
pexpireat wang 1152242123。以毫秒时间戳设置key过期时间
persist wang 。移除key的过期时间,key将永远保留。返回1表示成功,如果key不存在或者之前没有设置过期时间则返回0
ttl wang 。以秒单位返回键的过期时间。当key不存在返回-2, 当key存在但没有过期时间返回-1
pttl wang 。以毫秒时间返回键的过期时间
set
设置键值对。set wang fei
keys
获取库中的键。可使用匹配符。keys * 是获取所有的。keys w* 是获取有w前缀的
type
type key 返回键存储的值的类型,若键不存在则返回none
setnx
设置字符串键值对,若此时key已存在则不执行并返回0,否则执行并返回1。set if not exist
setex wang 10 fei 。设置键值对,并指定过期时间为10
发布订阅
订阅
subscribe topic 创建订阅主题
发布
publish topic msg 向指定主题发送数据
持久化
rdb
记录所有数据
aof
记录所有操作命令
哨兵 sentinel
解决问题:在redis主从架构中,当master宕机后,需要从slaves中选出一个来作为新的master,并将其他slave改为新master的从节点,并告知redis客户端连接新的master地址。这一切手动操作起来很麻烦,而redis哨兵就是用来解决此问题的,哨兵是一种特殊的redis节点
redis哨兵是一个单独的服务,通常需要至少有3个哨兵节点组成集群保持高可用以及投票(奇数个节点)。集群内哨兵都对redis master进行监控,并通过master可以知道slave的信息。当master宕机后,哨兵集群会自动从redis slave里选出一个新master出来,并通知client。
为什么需要3个呢?因为要投票。偶数个会发生对半的情况
哨兵集群虽然管理着redis集群,但是当redis集群增大,仅靠一个哨兵集群是负担不住的,需要对哨兵集群和redis集群进行拆分,分别的哨兵集群管理分别的redis集群。
客户端连接:1 首先遍历哨兵集合获取一个可用的哨兵节点,因为哨兵节点之间数据是共享的,所以可以从任何一个哨兵节点获取主节点信息。2 通过调用sentinel get-master-addr-by-name master-name来获取主节点信息。 3 向得到的主节点发送info replication验证是否真的是主节点,这样做是为了防止故障转移期间的主节点变化。4 客户端之后会保持和哨兵集合的联系,随时获取关于主节点信息的变化(利用redis的发布订阅功能,监听每个哨兵的切换master的相关频道),若主节点产生变化,则重新创建连接。
redis使用raft算法进行主节点的选举
集群寻址问题
redis没有使用一致性hash,而是采用了hash slot算法,一共16384个槽。集群内每个节点均分一定哈希槽之间的数据,也就是每个节点存储一段分片。每份分片又在多个互为主从的节点上备份,数据先写入主节点,再同步到从节点,不保持强一致性,而是最终一致性。redis的16379就是用于节点间通信的端口。当客户端请求的数据不在该节点上后,会返回转向指令。redis节点间的复制是异步的
redis实现异步队列
特性
端口:6379
redis所有操作都是原子性的,单个操作是原子性的,多个操作也支持原子性
为什么快?
基于内存
数据结构简单
采用单线程,避免了上下文切换和竞争开销
使用多路IO复用模型,非阻塞IO
redis是单线程来执行命令的,所以一条命令从客户端到达服务端不会马上被执行,因为会先缓存在队列中
执行过程
发送指令
等待指令执行
接收指令结果
单指令vs多指令:以set和mset为例
单指令会执行多次发送指令和接收指令结果,会增加多余的网络耗时
多指令虽然节省了网络耗时,但是因为多指令是原子的,如果数据量大,会导致服务阻塞在此。此时需要进行拆分,分为多个小一点的多指令。
配置文件
redis.conf
问题
如果防止键覆盖
创建唯一的键格式,比如uuid或加上timestamp
使用setnx
分布式锁
直接使用setnx创建一个键值对,在释放锁的时候删除键值对。
因为业务程序可能会在中途退出,这时未能释放锁,造成阻塞,需要设置过期时间
setnx不支持设置过期时间,需要额外调用expire。这样两个操作就不是原子性的了,中间发生问题就很难搞
redis2.6 后set命令可以完成setnx的效果。set key value nx px timeDuration。以毫秒为单位,此时是原子性的
有些业务处理流程可能比锁过期时间长,这时候锁已经被别的服务获取,这时候再去delete的话,就会发生误删锁的情况。可以在创建时value设置为随机数或者时间戳,删除时先判断是否相等,再执行delete, 但是这个过程不是原子性的,可以将操作写在lua脚本里完成,因为redis执行lua脚本是原子性的
应用场景:同样的服务集群接受客户端请求,只能选其中一个进行执行
事务
单个redis命令是原子性的,但是redis事务却不是原子性的,事务的执行过程是原子的,不会提供像mysql一样的回滚操作,中间命令失败不会引起事务停止,只是跳过当前操作而已。可以理解为只是一个批量执行过程而已
multi开启事务,exec结束事务, watch监控事务。watch 可以监控一个键,当事务执行过程中键被其他会话修改,那么exec结束事务提交的时候就会失败。基于cas乐观锁原理
redis事务支持隔离性和一致性
watch:可以通过watch,实现cas机制。在事务开启前先监听某个键,若在事务过程中,键被别的客户端修改,则事务不会被执行。
过期策略
定时过期: 给每个过期key都设置一个定时器,到了过期时间就立即清除。该策略虽然响应及时,但是会耗费大量cpu时间
惰性删除: 当访问key时才去判断key是否过期。过期则清除。虽然最大化节约了cpu资源,但是对内存不友好,要是一直没访问就一直堆积内存。
定期过期:创建时间轮,每过一段时间扫描一下过期key:并清除其中的过期的key。
redis采用的是惰性删除和定期删除
应用场景
计数器:创建简单的键值对,对键进行自增自减操作,实现计数器效果
会话缓存:存放用户信息
消息队列:list是一个双向链表。可以通过lpush和rpop写入和读取消息
内存数据淘汰策略
redis内存占用上升后, 会根据配置来进行数据淘汰。了解下lru
缓存异常
缓存雪崩:缓存大面积失效,请求全部打倒数据库上。
过期时间尽量随机,不要堆积到一起。
并发量不大时,对客户端的访问进行加锁排队,对请求量进行限制
应用层解决,比如定期检查
缓存穿透:缓存和数据库中都没有的数据,导致请求都落在数据库上,并一直请求数据库
应用层增加校验
若数据库中没有,可以在缓存中存储key-nil,并设置过期时间
布隆过滤器
缓存击穿:缓存中没有但是数据库中有,并发量大的时候,同时去访问数据库。
设置热点数据永不过期
应用层加互斥锁对访问进行限制
缓存预热: 系统上线后,需要提前准备一些缓存
应用层定时刷新
启动时自动加载
为了防止高并发同时访问缓存,但是又没有缓存数据,高并发直接攻击数据库,可以在应用层加锁限制访问量
redis实现延时队列
一致性问题
问题:
现在有两个进程,A把redis删了,这时B过来读数据没有,去找数据库,读到A还没来得及写的旧数据,然后B把数据更新到redis,然后A才把旧数据更新到redis,造成了脏数据的短暂停留。怎么办?
延时双删:redis一致性问题:先删除redis,再请求mysql,再删除一次redis缓存 再将mysql的数据放入redis中(此过程中如果失败,就等下一个请求来写入redis),这三个操作中,任何一个出错就不管了。业务场景下并发量还没有达到要进行业务分流的地步。如果硬是要解决的话就只能是业务分流串行操作了。先更新mysql再更新redis。
0 条评论
下一页