目录
一. MySQL
1. 数据库三大范式是什么?
2. MyISAM和InnoDB存储引擎的区别?
3. 什么是MVCC?有什么作用?怎么实现的?
4. 什么字段适合作为索引?
5. 索引的结构有哪些?
6. 为什么数据库主要使用B+树?B树和B+树有什么区别?
7. hash存储结构和B+树存储结构有什么优劣?
8. B+树的具体实现是什么样的?
9. 联合索引在B+树中怎么存储?
10. 最左匹配原则
11. 百万级的数据怎么删除
12. 什么是数据库事务?事务的四大特性ACID介绍一下
13. 什么是脏读、不可重复读、幻读?
14. 事务的隔离级别?怎么实现的(加了什么锁)?MySQL默认的隔离级别是什么?
15. 数据库的行锁和表锁是怎么实现的?
16. 如何定位sql语句的性能问题?如何解决?
17. 超大分页怎么处理?
18. 怎么排查慢sql?对于慢sql怎么优化?
19. MySQL数据库CPU飙升到500%怎么处理?
20. 千万数据的表,CRUD特别慢,怎么优化?
21. 数据库是怎么进行主从复制的?
22. count(*)、count(1)、count(列名)有什么区别?
23. 频繁的增删数据量某个表,数据库最终数据只有几万或者更少,为什么查询会变慢
24. mysql执行sql流程?
25. 什么情况下会索引失效?
26. 分库分表的方案?
二. Redis
1. redis有哪些数据类型?
2. redis的适用场景?项目中哪里用过redis?
3. redis为什么快?
4. redis为什么单线程还快?
5. redis的持久化机制是什么?优缺点是什么?
6. redis过期键的删除策略?
7. 有2000万mysql数据,redis只能存储20w数据,怎么保证redis存储的都是热点数据?
8. redis支持事务吗?
9. redis事务的命令有哪些?
10. redis哨兵机制是什么?
11. redis集群模式(redis cluster)的工作原理知道吗?
12. redis是如何进行主从复制的?
13. 缓存异常
14. 如何用redis实现分布式锁?
15. 假设redis中有一亿数据,有10w个前缀都是一样的,如何把他们找出来?
16. 有10万条数据,要插入到redis中,如何操作?
17. 如何使用redis做异步队列?
18. 如何使用redis做延迟队列?
19. redis的回收线程是怎么工作的?
20. redis回收使用的是什么算法?
三.MongoDB
1. 你们项目中MongoDB中存储是什么数据?
2. MySQL和MongoDB和redis的特点、区别、使用场景?
什么是存储引擎?
存储引擎定义了如何存储数据、如何为存储的数据建立索引、如何查询更新数据等技术的查询方法。
如何选择存储引擎?
- 当插入和查询比较多时,且对事务,并发性要求不高时使用MyISAM,因为MyISAM查询比较快。比如博客系统,新闻门户网站。
- 当需要用到事务,行级锁时,都是用InnoDB存储引擎。
为什么MyISAM查询比InnoDB快?
从功能上来说,InnoDB支持事务,会有一个MVVC(多版本并发控制)比较,会消耗性能,以及行级锁也会有性能开销。
从索引上来说,InnoDB的非聚簇索引查询数据会有一个回表的过程。先从非聚簇索引查到聚簇索引,然后再定位到行数据。而MyISAM直接能用索引定位到行数据。
详情讲解链接:https://www.jianshu.com/p/8845ddca3b23
MVCC(Multi-Version Concurrency Control)是多版本并发控制。MVCC能够代替行级锁,并发控制时系统性能消耗更少。
MVCC是用两个隐藏的列和undoLog实现的。两个隐藏的列分别是 当前事务的版本号 和 回滚指针(指向undolog中的版本记录)。
MVCC多版本控制的过程:
事务执行更新操作时,会先给行数据加排它锁,然后把当前数据快照存在undolog中,拷贝完毕后,再修改行数据,并把回滚指针指向undolog中的版本记录。事务执行期间只需要读版本记录即可。
undolog:保存了事务发生前的数据的一个版本,可以用于回滚,同时可以提供多版本并发控制下的读。
redolog:数据库操作数据的时候会先写入缓存,然后再写入数据库。但是如果写入缓存成功,但是数据库宕机,数据就丢失了。因此有了redolog,每次写入缓存后必须在redolog中记录该操作(对 XXX表空间中的XXX数据页XXX偏移量的地方做了XXX更新),这样即使数据库宕机,等数据库恢复后,仍然能把数据从redolog刷盘到数据库。
频繁使用的字段、区分度高的字段、外键
Hash、BTree(Blance Tree)、B+Tree。在MySQL的InnoDB存储引擎中,主要使用的是B+树。
B树讲解:什么是B树_石楠烟斗的雾的博客-CSDN博客
B+树讲解:什么是B+树_石楠烟斗的雾的博客-CSDN博客
磁盘页:假设一次IO能读8K的数据,那么8K就是一个磁盘页
B树和B+树的区别?
- B+树的叶子节点形成了有序链表,每个节点都有指向下一个节点的指针,范围查询更方便
- B树的每个节点都存储卫星数据,而B+树中间节点只存储索引,所以一个磁盘也可以存更多的节点。叶子节点包含了全量的元素,并存储了卫星数据(索引指向的数据记录,比如数据库的某一行)
需要指出的是:聚簇索引的叶子节点存储了卫星数据,非聚簇索引存储的是指向卫星数据的指针。
B树:
B+树:
因此一般情况下,用B+树就能获得稳定快速的查询
每个中间节点都只存储索引,叶子节点包含全量的元素,并存储了卫星数据。
聚簇索引的B+树结构: 叶子节点存储的是卫星数据; 非聚簇索引的B+树结构: 叶子节点存储的是指向卫星数据的指针。
联合索引主要是用索引第一列构建的树,因此第一列是有序的,其他列是无序的。但是当第一列确定时,其他列相对有序。最左匹配原则也由此而来。
以最左边为起点,连续的索引都能匹配上,遇到范围查询(> < between like)就会终止。
例如:建立联合索引(a,b,c,d),where a=1 and b=b and c>1 and d=1 则d用不到索引,因为c是范围查询。
最左匹配原则原理:
索引的底层是B+树,联合索引也是。但是联合索引是用最左边的字段来构建B+树,因此最左边的字段是有序的,后边的字段都是无序的。但是当最左边字段确定时,后边字段相对有序。
删除数据需要的时间和索引数量成正比,因为维护索引需要成本。
因此删除百万级数据的时候,应该先删除索引,然后删除不用的数据,最后重新建立索引。这样绝对比直接删除快的多。
事务是一组操作,要么都执行,要么都不执行。
MySQL默认的隔离级别是 可重复读;Oracle是 读已提交。
行锁是对索引进行加锁,没有索引不能用行锁,会升级到表锁。并且如果两条数据的索引相同,另一条数据也会被锁住。
例如:select * from table where id = 1 for update;如果id是索引,则加的是行锁;如果不是,则加的是表锁。
行锁和表锁如何使用?
行锁:
数据库会默认给UPDATE、DELETE、INSERT加排它锁,SELECT语句不会加任何锁,但是用户可以通过如下语句自行加锁
- 加共享锁:select * from table where xxx LOCK IN SHARE MODE;
- 加排它锁:select * from table where xxx FOR UPDATE;
表锁:
表锁必须手动释放,但是在事务提交前不要释放锁,因为UNLOCK TABLE隐含提交事务。如果要对t1写,对t2读,则按如下操作:
LOCK TABLES t1 WRITE, t2 READ;
dosomething......
COMMIT;
UNLOCK TABLES;
定位性能问题用explain命令查看执行计划。有几个关键的字段:
优化:
比如:select * from table where age>20 limit 1000000,10; 这样相当于把前一百万条数据抛弃了,取了后续10条,这样非常慢。
【优化方案一】数据库角度:
【优化方案二】需求角度:尽量不做这样的需求,问业务方这是强需求吗?是什么样的场景导致一定要这样做?是否可以限制用户的查询轨迹,只允许逐页查看之类的。
可以登录mysql配置开启慢查询日志,当有慢查询时,慢查询日志就会记录该sql。但是一般这个功能都是公司DBA做的,业务方可以在平台上查看慢sql耗时。
配置开启慢查询日志:set slow_query_log = on
配置临界时间:set long_query_time = 0.5
慢sql优化:
看一下有没有慢sql,用explain命令分析并优化慢sql
【分库分表】
垂直分表:把表按照某个维度(比如使用频率)垂直拆分开
- 优点:一个数据页能存储更多的数据,能减少IO次数
- 缺点:需要管理冗余列,有时候需要联表查询
水平分表:当数据超过200万行时,CRUD就会很慢,这时就需要用某种策略将数据水平分片
- 优点:能够支撑大量的数据
- 缺点:分布式事务难以处理、跨库的join、跨库的count orderby groupby等函数不好使用。后两个问题可以通过在应用程序中进行拼装来解决
执行结果上:count(*)、count(1)没有区别,count(列名)不统计为null的记录。
执行效果上:mysql会自动优化count(*),因此当有主键的时候count(*)比count(1)快;没有主键的时候,count(1)比count(*)快。如果列名是主键,则count(列名)最快。
原因是该表的空间大了,查询起来很慢。解决的方法是把该表所占用的表空间缩小,或者说释放表空间。
alter table XXXX move; 这样处理后就释放了表空间了。
但是释放表空间后,表的行号rowid会发生变化,而基于rowid的索引则会变成无效。因此该操作后必须重建索引。
否则会 提示“ORA-01502: 索引'SMP.ITEMLOG_MID_IDX'或这类索引的分区处于不可用状态”
而重建索引的方法当然可以先drop掉再create ,但是这样太麻烦了,
用alter index XXX rebuild 这样最快了,不会改变原来的索引结构
1、客户端先和sql服务器建立连接,客户端发生查询sql脚本给服务器
2、服务器先检查查询缓存,如果命中了缓存,则立刻返回存储在缓存中的结果。否则进入下一阶段。
3、服务器端进行SQL解析、预处理,再由优化器生成对应的执行计划。
4、MySQL根据优化器生成的执行计划,再调用存储引擎的API来执行查询。
5、将结果返回给客户端。
- 不满足最左匹配原则
- 索引列使用了函数
- like左边包含%
- 使用了not in 或 not exist
- 使用了or 关键字(如果使用了
or
关键字,那么它前面和后面的字段都要加索引,不然所有的索引都会失效)
概念:以某个字段为依据,按照一定策略,将一个表中的数据拆分到多个表中。
场景:单表的数据量太多,影响了SQL效率工具:Sharding-JDBC
- 选择分片键
- 选择分片策略:取模、范围(Between AND)
redis中String底层的数据结构?
Redis六种底层数据结构_星空是梦想的博客-CSDN博客_redis 底层数据结构、图解redis五种数据结构底层实现(动图哦)
是简单动态字符串(SDS)。用char[]数组的形式保存String中每个字符,还有len属性保存字符串的长度,以及free属性保存未使用的字节数。
为什么要这样设计?
杜绝缓存区溢出。c字符串如果没有重新分配空间,直接修改字符串,可能会造成空间溢出。SDS会先判断空间是否够用,不够的话会扩展至所需大小。
减少内存重新分配。redis会进行空间预分配,减少了因为字符串修改导致的扩容;并且是惰性空间释放,如果字符串变短,不会直接回收,而是存到free中。
redis中list的底层数据结构?
redis中list的底层数据结构是一个双向链表。
redis中hash的底层数据结构?
hash的底层数据结构可以是ziplist(压缩列表)或者哈希表(数组加链表的结构)。
当hash对象可以同时满足一下两个条件时,哈希对象使用ziplist编码。
- 哈希对象保存的所有键值对的键和值的字符串长度都小于64字节
- 哈希对象保存的键值对数量小于512个
zipList的数据结构?
zipList:
zipEntry:
- ziplist包括zip header、zip entry、zip end三个模块。
- zip entry由prevlen、encoding&length、value三部分组成。
- prevlen主要是指前面zipEntry的长度,coding&length是指编码字段长度和实际- 存储value的长度,value是指真正的内容。
- 每个key/value存储结果中key用一个zipEntry存储,value用一个zipEntry存储。
redis中set的数据结构?
redis中set是一个intset或者hashtable的结构。当元素全为整数且元素数量不超过512个时用intset存储,否则用hashtable。
intset底层实际上是一个int数组。
zset底层的数据结构是什么?
漫画:什么是 “跳表”?、跳跃表及底层实现原理解析_Slayer_Zhao的博客-CSDN博客_跳跃表原理和实现
zset底层的存储结构包括压缩列表(ziplist)或者跳表(skiplist),在同时满足以下两个条件的时候使用ziplist,其他时候使用skiplist,两个条件如下:
- 有序集合保存的元素数量小于128个
- 有序集合保存的所有元素的长度小于64字节
压缩列表:
当ziplist作为zset的底层存储结构时候,每个集合元素使用两个紧挨在一起的压缩列表节点来保存,第一个节点保存元素的成员,第二个元素保存元素的分值。
跳表:
当skiplist作为zset的底层存储结构的时候,使用skiplist按序保存元素及分值,使用dict来保存元素和分值的映射关系。
跳表的结构如下图所示,跳表是在原始链表的基础上加了多层索引链表,索引链表的节点指向原始列表的节点,使得查询数据的效率更高。
插入:
因为redis使用了IO多路复用的模型,用一个选择器监听多个通道,当通道有事件发生时,线程就处理事件。相当于可以处理大量并发IO。
IO多路复用和多线程有什么区别?
- IO多路复用适合处理IO密集型,IO等待的时间比较长,不需要太多CPU计算,单线程就处理计算逻辑了
- 多线程适合处理CPU密集型,需要多线程使用多核CPU处理计算逻辑。
redis完全是单线程的吗?
不是。redis的读写命令是单线程的,但是处理网络IO是多线程的,因为redis的瓶颈主要是网络IO,所以 处理网络IO 用多线程可以解决这个问题,并且不会造成数据并发读写。
RDB:按照一定的时间间隔把内存快照保存到磁盘中
AOF:把所有的写操作记录到磁盘日志中
redis使用:惰性删除 + 定期删除
当数据集上升到一定程度,就会使用淘汰策略。
淘汰策略用于处理 内存不足时需要申请额外空间的数据,有三种策略:
- 随机移除
- 移除使用最少的key
- 移除更早过期的key
当内存用完的时候,写操作会返回错误,或者你可以配置内存淘汰策略
支持,redis的事务能保证一系列指令按顺序执行,中间不会插入其他语句。但是redis事务只支持隔离性,不支持原子性,不支持回滚。
当中间有语句执行失败时,会继续执行其他语句。
sentinel,哨兵,用于保证redis集群的高可用。它有四个作用:
哨兵机制不保证数据不丢失,只能保证redis集群高可用
redis集群使用了哈希槽的方式来进行数据存储和读取。
假设redis集群有3个分片,数据将分别存储在这三个分片上,三个分片上被均分了16384个哈希槽。数据具体存储在哪个分片上,是由哈希槽算法决定的。CRC16(key) % 16384就可以得知数据要被存储在哪个槽上了。
如果是服务端路由的方式,客户端把请求发送到任意分片节点,接收到请求的节点都会把请求转发到正确的节点上执行。
redis为什么不使用一致性哈希,而使用哈希槽?
因为哈希槽好扩容,如果要增加节点,只需要把其他分片上的哈希槽迁移一部分到新节点即可。
一致性哈希(哈希环)
如果我们要确定一张图片要放在ABC哪台服务器上,普通做法是 图片的hash值%3(3台服务器),但是如果要再加一台服务器,所有图片的位置就都需要改变。
所以我们用一致性hash,用 hash值 对 2^32 取模,并把2^32想象成一个环,ABC服务器则是环上三个点,图片的hash值必然落在弧形区间内,归为下一个最近的服务器。
缺点:可能会数据分布不均匀,导致哈希倾斜。解法:把ABC服务器,多取几个hash值(A1A2A3)作为虚拟节点,虚拟节点越多,哈希倾斜带来的影响就越小。
什么是redis集群模式下的脑裂?如何解决脑裂?
脑裂是因为网络原因,导致master、slave、sentinel不在一个网络分区。此时sentinel感应不到master,就会从slave中重新选举一个master,两个master看起来就像master分裂成了两个。
集群脑裂问题中,如果客户端还继续往旧的master写数据,当网络恢复后,sentinel就会把之前master降为slave,此时再从新的master同步数据就会丢失大量数据。
解决方案:
redis有两个配置如下,如果slave节点少于3个,或者slave超过10秒都没连接到master,master就拒绝写请求
//配置slave节点最少有3个 min-slaves-to-write 3 //配置slave连接到master的最大延时为10秒 min-slaves-max-lag 10
这样的主从复制模式有一个缺点,就是所有的复制同步都由master处理,导致master压力太大,可以使用主从从模式,slave同步到slacve
【缓存雪崩】:同一时间内大面积缓存失效,导致请求全都打到服务器上。
解决方案:缓存预热,在服务器启动前把热点数据提前刷到缓存中,并且过期时间尽量设置的随机一些,避免同一时间失效
【缓存击穿】:缓存中没有数据库中有的数据(一般是缓存时间到期)
解决方案:一般有两种解决方案。第一是让热点数据用不过期,但是轻易不使用这种方式。第二种是用双重判空的方式,先去缓存中拿,缓存中没有再加锁,再判断缓存中有没有,没有的话再到数据库拿。
【缓存穿透】:缓存和数据库中都没有的数据
解决方案:可以在缓存中设置一个key,value是null,这样也就直接能从缓存拿到了
如何获取分布式锁?怎么保证性能?
- 可以用setnx()获取锁成功返回1,获取失败返回0;如果是获取成功则可以继续处理下边的业务,如果获取失败自旋5次,尝试去获取锁。
如果要设置过期时间,用jedisClient.set(key, value, "NX"
,
"EX", expireSecond);//NX是不存在时才set,XX是存在时才set,EX是秒,PX是毫秒
自旋比较消耗性能,如果不采用自旋的方式呢?
使用blpop。
redis和zk获取分布式锁有什么区别?
zk获取分布式锁是通过创建临时节点的方式,如果创建临时节点成功就获取锁,如果创建失败就用监听器监听这个临时节点,释放锁就是删除临时节点。
但是一般都不使用zk做分布式锁,很客观的原因是要用zk做分布式锁就得有zk集群,但是一般公司都没有zk集群,但是都有redis的集群,所以都用redis做分布式锁。
使用redis的list数据结构,blpush生产消息,brpop消费消息,brpop是阻塞的,没有消息就一直等待,直到消息到来。
用redis的zset做延迟队列,消息作为value,时间戳作为score,用zrangebyscore来获取n秒前的消息。
当redis已使用内存达到阈值之上,如果有新请求进来,就会按照配置的淘汰策略进行回收。
LRU算法,淘汰最少使用的数据。
LRU算法具体实现?
LRU算法的核心思想是:
如果数据最近被访问过,那么将来被访问的几率也更高。
实现:
- 新加入数据添加到链表头部
- 被访问的数据也添加移动到链表头部
- 当链表满时从底部淘汰数据
代价:命中需要遍历链表,找到命中的数据,并移动到链表头部
费率、保单的扩展字段。
由于费率计算复杂,每个保司计算费率的条件也都不一样,这时候如果用数据库存储,就没办法设计表,因此采用MongoDB存储,可以灵活的存储非结构化数据。
MySQL是关系型数据库,常用于存储结构化数据,并且支持事务。
MongoDB和redis都是非关系型数据库,MongoDB当做数据库使用,如果不确定数据的结构,就可以使用MongoDB,便于扩展字段。
redis当做缓存使用,查询非常快速,比如可以存储热点数据,缩小给前端的返回时间。
3.