八股文
八股文
优先级:MySQL->redis->jvm->juc
应届生面试优先看中的是:八股文+算法+项目
数据库
MySQL
常见问题总结
1、什么是MySQL?
MySQL 是一种关系型数据库,在Java企业级开发中非常常用,因为 MySQL 是开源免费的,并 且方便扩展。阿里巴巴数据库系统也⼤量⽤到了 MySQL,因此它的稳定性是有保障的。MySQL 是开放源代码的,因此任何⼈都可以在 GPL(General Public License) 的许可下下载并根据个性 化的需要对其进⾏修改。MySQL的默认端⼝号是3306。
2、存储引擎
⼀些常用命令
查看MySQL提供的所有存储引擎
show engines;
MyISAM和InnoDB区别
1、InnoDB支持事务,MyISAM不支持,对于InnoDB每一条SQL语言都默认封装成事务,自动提交,这样会影响速度,所以最好把多条SQL语言放在begin和commit之间,组成一个事务;
2、InnoDB支持外键,而MyISAM不支持。对一个包含外键的InnoDB表转为MYISAM会失败;
3、InnoDB支持MVCC,应对高并发事务,MVCC比单纯的加锁更高效;MVCC只在READ COMMITTED和REPEATABLE READ两个隔离级别下工作;MVCC可以使用乐观锁和悲观锁来实现;各数据库中的MVCC实现并不统一。
4、InnoDB不保存表的具体行数,执行select count(*) from table时需要全表扫描。而MyISAM用一个变量保存了整个表的行数,执行上述语句时只需要读出该变量即可,速度很快;
5、Innodb不支持全文索引,而MyISAM支持全文索引,查询效率上MyISAM要高;
什么是MVCC?
MVCC(多版本并发控制)是一种在数据库系统中用于处理并发事务的技术。
它的作用:
- 提供并发性:允许多个事务同时执行,提高系统的并发处理能力。
- 保证数据的一致性:确保在并发环境下,数据的正确性和一致性得到维护。
实现方式:
- 版本管理:为每个数据记录维护多个版本。
- 读取一致性:事务在读取数据时,看到的是一个符合其事务隔离级别的数据版本。
它解决并发事务问题的方式:
- 避免读锁定:通过多版本管理,读取操作不需要阻塞其他事务的写入。
- 提高并发性能:减少了因锁定带来的性能开销。
- 保证隔离性:实现了不同隔离级别的需求,如可串行化、重复读等。
3、数据库的三范式是哪些?
第一范式:列不可再分
第二范式:行可以唯一区分,主键约束
第三范式:表的非主属性不能依赖与其他表的非主属性
外键约束 且三大范式是一级一级依赖的,第二范式建立在第一范式上,第三范式 建立第一第二范式上。
4、数据库的事务
什么是事务?:多条sql语句,要么全部成功,要么全部失败。
事务的特性:
数据库事务特性:原子性(Atomic)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability)。简称ACID。
- 原子性:事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用。
- 一致性:执行事务前后,数据保持一致性,多个事务对同一个数据读取的结果是相同的。
- 隔离性:在并发数据操作时,不同的事务拥有各自的数据空间,它们的操作不会对彼此产生干扰 。
- 持久性:一旦事务提交成功,事务中的所有操作都必须持久化到数据库中,即使数据库发生故障也不应该对其有任何影响。
5、索引是什么
- 帮助MySQL高效获取数据的数据结构,能加快数据库的查询速度。
- 索引往往是存储在磁盘上的文件中的(可能存储在单独的索引文件中,也可能和数据一起存储在数据文件中)。
- 常见的索引包括:聚集索引、覆盖索引、组合索引、前缀索引、唯一索引等,没有特别说明,默认都是使用B+树结构组织(多路搜索树,并一定是二叉的)的索引。
6、SQL优化手段有哪些?
- 查询语句中不要使用select *
- 尽量减少子查询,使用关联查询(left join,right join,inner join)替代
- 减少使用IN或者NOT IN,使用exists,not exists或者关联查询语句替代
- or的查询尽量使用union或者union all代替(在确认没有重复数据或者不用剔除重复数据时,union all会更好)
- 尽量避免在where子句中使用!=或<>操作符,否则引擎放弃使用索引而进行全表扫描。
7、简单说一说drop、delete与truncate的区别
SQL中的drop、delete、truncate都表示删除,但是三者有一些差别
delete和truncate只删除表的数据不删除表的结构。
从速度上来说:drop>truncate>delete delete语句是DML,这个操作会放到rollback segment(回滚段)中,事务提交之后才生效;如果有相应的trigger,执行的时候将被触发。truncate,drop是DDL,操作立即生效,原数据不放到rollback segment中,不能回滚,操作不触发trigger。
8、什么是视图?
视图是一种虚拟的表,具有和物理表相同的功能。可以对视图进行增、改、查操作,视图通常是有一个表或者多个表的行或列的子集。对视图的修改不影响基本表。它使得我们获取数据更容易,相比于多表查询。
24-4-19
9、 什么是内联接、左外联接、右外联接?
- 内联接(Inner Join):匹配2张表中相关联的记录。
- 左外联接(Left Outer Join):除了匹配2张表中相关联的记录外,还会匹配左表中剩余的记录,右表中未匹配到的字段用NULL表示。
- 右外联接(Right Outer Join):除了匹配2张表中相关联的记录外,还会匹配右表中剩余的记录,左表中未匹配到的字段用NULL表示。在判定左表和右表时,要根据表名出现在Outer Join 的左右位置关系。
10、并发事务带来哪些问题?
- 脏读(Dirty read):当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。
- 丢失修改(Lost to modify):指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。 例如:事务1读取某表中的数据A=20,事务2也 读取A=20,事务1修改A=A-1,事务2也修改A=A-1,最终结果A=19,事务1的修改被丢失。
- 不可重复读(Unrepeatable read):指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
- 幻读(Phantom read):幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。
不可重复读和幻读的区别:
不可重复读的重点是修改,比如多次读取一条记录发现其中某些列的值被修改,幻读的重点在于新增或者删除比如多次读取一条记录发现记录增多或减少了。
11、事务隔离级别有哪些?MySQL的默认隔离级别是?
SQL 标准定义了四个隔离级别:
- READ-UNCOMMITTED(读取未提交):最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
- READ-COMMITTED(读取已提交):允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
- REPEATABLE-READ(可重复读):对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
- SERIALIZABLE(可串行化):最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以阻止脏读、不可重复读以及幻读。
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
READ-UNCOMMITTED | √ | √ | √ |
READ-COMMITTED | × | √ | √ |
REPEATABLE-READ | × | × | √ |
SERIALIZABLE | × | × | × |
MySQL的InnoDB存储引擎的默认支持的隔离级别是REPEATABLE-READ(可重复读)。
注意:与SQL标准不同的地方在于InnoDB存储引擎在REPEATABLE-READ(可重复读)事务隔离级别下使用的是Next-Key Lock锁算法,因此可以避免幻读的产生,这与其他数据库(如SQLserver)是不同的。所以说InnoDB 存储引擎的默认支持的隔离级别是 REPEATABLE-READ(可重读) 已经可以完全保证事务的隔离性要求,即达到了 SQL标准的 SERIALIZABLE(可串行化) 隔离级别。因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是READ-COMMITTED(读取提交内容),但是你要知道的是InnoDB存储引擎默认使用 REPEATABLE-READ(可重读)并不会有任何性能损失。InnoDB 存储引擎在分布式事务的情况下一般会用到 SERIALIZABLE(可串行化) 隔离级别。
24-4-22
12、一条SQL语句在MySQL中如何执行的?
MySQL基本架构分析
连接器:身份认证和权限相关(登录MySQL的时候)。
查询缓存:执行查询语句的时候,会先查询缓存(MySQL 8.0 版本后移除,因为这个功能不太实用)。
分析器:没有命中缓存的话,SQL 语句就会经过分析器,分析器说白了就是要先看你的 SQL 语句要干嘛,再检查你的 SQL 语句语法是否正确。
优化器:按照MySQL认为最优的方案去执行。
执行器:执行语句,然后从存储引擎返回数据。
简单来说 MySQL 主要分为 Server 层和存储引擎层:
Server层:主要包括连接器、查询缓存、分析器、优化器、执行器等,所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图,函数等,还有一个通用的日志模块 binglog 日志模块。
存储引擎: 主要负责数据的存储和读取,采用可以替换的插件式架构,支持 InnoDB、MyISAM、Memory 等多个存储引擎,其中 InnoDB 引擎有自有的日志模块 redolog 模块。现在最常用的存储引擎是 InnoDB,它从 MySQL 5.5.5 版本开始就被当做默认存储引擎了。
Server层基本组件介绍
1)连接器
连接器主要和身份认证和权限相关的功能相关,就好比一个级别很高的门卫一样。
主要负责用户登录数据库,进行用户的身份认证,包括校验账户密码,权限等操作,如果用户账户密码已通过,连接器会到权限表中查询该用户的所有权限,之后在这个连接里的权限逻辑判断都是会依赖此时读取到的权限数据,也就是说,后续只要这个连接不断开,即时管理员修改了该用户的权限,该用户也是不受影响的。
- 查询缓存(MySQL 8.0 版本后移除)
查询缓存主要用来缓存我们所执行的 SELECT 语句以及该语句的结果集。
连接建立后,执行查询语句的时候,会先查询缓存,MySQL 会先校验这个 sql 是否执行过,以 Key-Value 的形式缓存在内存中,Key 是查询预计,Value 是结果集。如果缓存 key 被命中,就会直接返回给客户端,如果没有命中,就会执行后续的操作,完成后也会把结果缓存起来,方便下一次调用。当然在真正执行缓存查询的时候还是会校验用户的权限,是否有该表的查询条件。
MySQL 查询不建议使用缓存,因为查询缓存失效在实际业务场景中可能会非常频繁,假如你对一个表更新的话,这个表上的所有的查询缓存都会被清空。对于不经常更新的数据来说,使用缓存还是可以的。
所以,一般在大多数情况下我们都是不推荐去使用查询缓存的。
MySQL 8.0 版本后删除了缓存的功能,官方也是认为该功能在实际的应用场景比较少,所以干脆直接删掉了。
3)分析器
MySQL 没有命中缓存,那么就会进入分析器,分析器主要是用来分析 SQL 语句是来干嘛的,分析器也会分为几步:
第一步,词法分析,一条 SQL 语句有多个字符串组成,首先要提取关键字,比如 select,提出查询的表,提出字段名,提出查询条件等等。做完这些操作后,就会进入第二步。
第二步,语法分析,主要就是判断你输入的 sql 是否正确,是否符合 MySQL 的语法。
完成这 2 步之后,MySQL 就准备开始执行了,但是如何执行,怎么执行是最好的结果呢?这个时候就需要优化器上场了。
4)优化器
优化器的作用就是它认为的最优的执行方案去执行(有时候可能也不是最优),比如多个索引的时候该如何选择索引,多表查询的时候如何选择关联顺序等。
可以说,经过了优化器之后可以说这个语句具体该如何执行就已经定下来。
5)执行器
当选择了执行方案后,MySQL 就准备开始执行了,首先执行前会校验该用户有没有权限,如果没有权限,就会返回错误信息,如果有权限,就会去调用引擎的接口,返回接口执行的结果。
语句分析
1、查询语句
说了以上这么多,那么究竟一条 sql 语句是如何执行的呢?其实我们的 sql 可以分为两种,一种是查询,一种是更新(增加,更新,删除)。我们先分析下查询语句,语句如下:
select * from tb_student A where A.age='18' and A.name=' 张三 ';
结合上面的说明,我们分析下这个语句的执行流程:
-
先检查该语句是否有权限,如果没有权限,直接返回错误信息,如果有权限,在MySQL8.0版本以前,会先查询缓存,以这条sql语句为key在内存中查询是否有结果,如果有直接缓存,如果没有,执行下一步。
-
通过分析器进行词法分析,提取 sql 语句的关键元素,比如提取上面这个语句是查询 select,提取需要查询的表名为 tb_student,需要查询所有的列,查询条件是这个表的 id='1'。然后判断这个 sql 语句是否有语法错误,比如关键词是否正确等等,如果检查没问题就执行下一步。
-
接下来就是优化器进行确定执行方案,上面的sql语句,可以有两种执行方案:
a.先查询学生表中姓名为“张三”的学生,然后判断是否年龄是 18。 b.先找出学生中年龄 18 岁的学生,然后再查询姓名为“张三”的学生。
那么优化器根据自己的优化算法进行选择执行效率最好的一个方案(优化器认为,有时候不一定最好)。那么确认了执行计划后就准备开始执行了。
-
进行权限校验,如果没有权限就会返回错误信息,如果有权限就会调用数据库引擎接口,返回引擎的执行结果。
2、更新语句
以上就是一条查询 sql 的执行流程,那么接下来我们看看一条更新语句如何执行的呢?sql 语句如下:
update tb_student A set A.age='19' where A.name=' 张三 ';
我们来给张三修改下年龄,在实际数据库肯定不会设置年龄这个字段的,不然要被技术负责人打的。其实这条语句也基本上会沿着上一个查询的流程走,只不过执行更新的时候肯定要记录日志了,这就会引入日志模块了,MySQL自带的日志模块式binlog(归档日志),所有的存储引擎都可以使用,我们常用的 InnoDB 引擎还自带了一个日志模块 redolog(重做日志),我们就以 InnoDB 模式下来探讨这个语句的执行流程。流程如下:
- 先查询到张三这一条数据,如果有缓存,也是会用到缓存。
- 然后拿到查询的语句,把 age 改为 19,然后调用引擎 API 接口,写入这一行数据,InnoDB 引擎把数据保存在内存中,同时记录 redo log,此时 redo log 进入 prepare 状态,然后告诉执行器,执行完成了,随时可以提交。
- 执行器收到通知后记录 binlog,然后调用引擎接口,提交 redo log 为提交状态。
- 更新完成。
总结
- MySQL 主要分为 Server 层和引擎层,Server 层主要包括连接器、查询缓存、分析器、优化器、执行器,同时还有一个日志模块(binlog),这个日志模块所有执行引擎都可以共用,redolog 只有 InnoDB 有。
- 引擎层是插件式的,目前主要包括,MyISAM,InnoDB,Memory 等。
- SQL 等执行过程分为两类,一类对于查询等过程如下:权限校验---》查询缓存---》分析器---》优化器---》权限校验---》执行器---》引擎
- 对于更新等语句执行流程如下:分析器----》权限校验----》执行器---》引擎---redo log prepare---》binlog---》redo log commit
24-4-24 周三
13、MySQL的数据存放在哪个文件?
mysql> SHOW VARIABLES LIKE 'datadir';
[root@xiaolin ~]#ls /var/lib/mysql/my_test
db.opt
t_order.frm
t_order.ibd
- db.opt,用来存储当前数据库的默认字符集和字符校验规则。
- t_order.frm ,表结构会保存在这个文件。在 MySQL 中建立一张表都会生成一个.frm 文件,该文件是用来保存每个表的元数据信息的,主要包含表结构定义。
- t_order.ibd,表数据会保存在这个文件。
14、表空间文件的结构是怎么样的?
表空间由段(segment)、区(extent)、页(page)、行(row)组成,InnoDB存储引擎的逻辑存储结果大致如下图所示:
1、行(row)
数据库表中的记录都是按行(row)进行存放的,每行记录根据不同的行格式,有不同的存储结构。后面我们详细介绍 InnoDB 存储引擎的行格式,也是本文重点介绍的内容。
2、页(page)
记录是按照行来存储的,但是数据库的读取并不以「行」为单位,否则一次读取(也就是一次 I/O 操作)只能处理一行数据,效率会非常低。
因此,InnoDB 的数据是按「页」为单位来读写的,也就是说,当需要读一条记录的时候,并不是将这个行记录从磁盘读出来,而是以页为单位,将其整体读入内存。
默认每个页的大小为 16KB,也就是最多能保证 16KB 的连续存储空间。
页是 InnoDB 存储引擎磁盘管理的最小单元,意味着数据库每次读写都是以 16KB 为单位的,一次最少从磁盘中读取 16K 的内容到内存中,一次最少把内存中的 16K 内容刷新到磁盘中。
页的类型有很多,常见的有数据页、undo日志页、溢出页等等。数据表中的行记录是用「数据页」来管理的,总之知道表中的记录存储在「数据页」里面就行。
3、区(extent)
我们知道 InnoDB 存储引擎是用 B+ 树来组织数据的。
B+ 树中每一层都是通过双向链表连接起来的,如果是以页为单位来分配存储空间,那么链表中相邻的两个页之间的物理位置并不是连续的,可能离得非常远,那么磁盘查询时就会有大量的随机I/O,随机 I/O 是非常慢的。
解决这个问题也很简单,就是让链表中相邻的页的物理位置也相邻,这样就可以使用顺序 I/O 了,那么在范围查询(扫描叶子节点)的时候性能就会很高。
那具体怎么解决呢?
在表中数据量大的时候,为某个索引分配空间的时候就不再按照页为单位分配了,而是按照区(extent)为单位分配。每个区的大小为 1MB,对于 16KB 的页来说,连续的 64 个页会被划为一个区,这样就使得链表中相邻的页的物理位置也相邻,就能使用顺序 I/O 了。
4、段(segment)
表空间是由各个段(segment)组成的,段是由多个区(extent)组成的。段一般分为数据段、索引段和回滚段等。
- 索引段:存放 B + 树的非叶子节点的区的集合;
- 数据段:存放 B + 树的叶子节点的区的集合;
- 回滚段:存放的是回滚数据的区的集合,之前讲事务隔离的时候就介绍到了 MVCC 利用了回滚段实现了多版本查询数据。
好了,终于说完表空间的结构了。接下来,就具体讲一下 InnoDB 的行格式了。
之所以要绕一大圈才讲行记录的格式,主要是想让大家知道行记录是存储在哪个文件,以及行记录在这个表空间文件中的哪个区域,有一个从上往下切入的视角,这样理解起来不会觉得很抽象。
InnoDB行格式有哪些?
行格式(row_format),就是一条记录的存储结构。
InnoDB 提供了 4 种行格式,分别是 Redundant、Compact、Dynamic和 Compressed 行格式。
- Redundant 是很古老的行格式了, MySQL 5.0 版本之前用的行格式,现在基本没人用了。
- 由于 Redundant 不是一种紧凑的行格式,所以 MySQL 5.0 之后引入了 Compact 行记录存储方式,Compact 是一种紧凑的行格式,设计的初衷就是为了让一个数据页中可以存放更多的行记录,从 MySQL 5.1 版本之后,行格式默认设置成 Compact。
- Dynamic 和 Compressed 两个都是紧凑的行格式,它们的行格式都和 Compact 差不多,因为都是基于 Compact 改进一点东西。从 MySQL5.7 版本之后,默认使用 Dynamic 行格式。
Redundant 行格式我这里就不讲了,因为现在基本没人用了,这次重点介绍 Compact 行格式,因为 Dynamic 和 Compressed 这两个行格式跟 Compact 非常像。
所以,弄懂了 Compact 行格式,之后你们在去了解其他行格式,很快也能看懂。
24-5-8 周三
Compact行格式具体长什么样?
记录的额外信息
记录的额外信息包含3个部分:变长字段长度列表、NULL值列表、记录头信息。
记录的真实数据
记录真实数据部分除了我们定义的字段,还有三个隐藏字段,分别为:row_id、trx_id、roll_pointer
- row_id
如果我们建表的时候指定了主键或者唯一约束列,那么就没有 row_id 隐藏字段了。如果既没有指定主键,又没有唯一约束,那么 InnoDB 就会为记录添加 row_id 隐藏字段。row_id不是必需的,占用 6 个字节。
- trx_id
事务id,表示这个数据是由哪个事务生成的。 trx_id是必需的,占用 6 个字节。
- roll_pointer
这条记录上一个版本的指针。roll_pointer 是必需的,占用 7 个字节。
15、锁机制与InnoDB锁算法
MyISAM和InnoDB存储引擎使用的锁:
- MyISAM采用表级锁
- InnoDB支持行级锁和表级锁,默认为行级锁
表级锁和行级锁的对比:
- 表级锁:MySQL中锁定粒度最大的⼀种锁,对当前操作的整张表加锁,实现简单,资源消耗也比较少,加锁快,不会出现死锁。其锁定粒度最大,触发锁冲突的概率最高,并发度最低,MyISAM和 InnoDB引擎都支持表级锁。
- 行级锁:MySQL中锁定粒度最小的一种锁,只针对当前操作的行进行加锁。 行级锁能大大减少数据库操作的冲突。其加锁粒度最小,并发度高,但加锁的开销也最大,加锁慢,会出现死锁。
InnoDB存储引擎的锁的算法有三种:
- Record lock:单个行记录上的锁
- Gap lock:间隙锁,锁定一个范围,不包括记录本身
- Next-key lock:record+gap 锁定⼀个范围,包含记录本身
相关知识点:
- innodb对于行的查询使用next-key lock
- Next-locking keying为了解决Phantom Problem幻读问题
- 当查询的索引含有唯⼀属性时,将next-key lock降级为record key
- Gap锁设计的目的是为了阻止多个事务将记录插入到同⼀范围内,而这会导致幻读问题的产生
- 有两种方式显式关闭gap锁:(除了外键约束和唯⼀性检查外,其余情况仅使用record lock) A. 将事务隔离级别设置为RC B. 将参数innodb_locks_unsafe_for_binlog设置为1
24-5-25 周六
16、大表优化
当MySQL单表记录数过大时,数据库的CRUD性能会明显下降,一些常见的优化措施如下:
限定数据的范围
务必禁止不带任何限制数据范围条件的查询语句。比如:我们当用户在查询订单历史的时候,我们可以控制在一个月的范围内;
读/写分离
经典的数据库拆分方案,主库负责写,从库负责读;
垂直分区(分列)
根据数据库里面数据表的相关性进行拆分。例如,⽤户表中既有⽤户的登录信息⼜有⽤户的基 本信息,可以将⽤户表拆分成两个单独的表,甚⾄放到单独的库做分库。
简单来说垂直拆分是指数据表列的拆分,把一张列比较多的表拆分为多张表。
- 垂直拆分的优点:可以使得列数据变小,在查询时减少读取的Block数,减少I/O次数。此外,垂直分区可以简化表的结构,易于维护。
- 垂直拆分的缺点:主键会出现冗余,需要管理冗余列,并会引起Join操作,可以通过在应用层进行Join来解决。此外,垂直分区会让事务变得更加复杂;
水平分区(分行)
保持数据表结构不变,通过某种策略存储数据分片。这样每一片数据分散到不同的表或者库中,达到了分布式的目的。水平拆分可以支撑非常大的数据量。
水平拆分是指数据表行的拆分,表的行数超过200万行时,就会变慢,这时可以把一张的表的数据拆成多张表来存放。举个例子:我们可以将用户信息表拆分成多个用户信息表,这样就可以避 免单一表数据量过大对性能造成影响。
水平拆分可以支持非常大的数据量。需要注意的⼀点是:分表仅仅是解决了单一表数据过大的问题,但由于表的数据还是在同一台机器上,其实对于提升MySQL并发能力没有什么意义,所以水平拆分最好分库 。水平拆分能够支持非常大的数据量存储,应用端改造也少,但分片事务难以解决 ,跨节点Join性能较差,逻辑复杂。《Java工程师修炼之道》的作者推荐尽量不要对数据进行分片,因为拆分会带来逻辑、部署、运维的各种复杂度 ,⼀般的数据表在优化得当的情况下支撑千万以下的数据量是没有太大问题的。如果实在要分片,尽量选择客户端分片架构,这样可以减少一次和中间件的网络I/O。
补充:
常见的数据库分片的两种常见方案:
- 客户端代理:分片逻辑在应用端,封装在jar包中,通过修改或者封装JDBC层来实现。当当网的Sharding-JDBC、阿里的TDDL是两种比较常见的实现。
- 中间件代理:在应用和数据中间加了一个代理层。分片逻辑统一维护在中间件服务中。我们现在谈的MyCat、360的Atlas、网易的DDB等等都是这种架构的实现。
17、索引有什么优缺点?
优点:
①提高数据的检索速度,降低数据库IO成本;使用索引的意义就是通过缩小表中需要查询的记录的数目从而加快搜索的速度
②降低数据排序的成本,降低CPU的消耗;索引之所以查的快,是因为先将数据排好序,若该字段正好需要排序,则正好降低了排序的成本
缺点:
①占用存储空间:索引实际上也是一张表,记录了主键与索引字段,一般以索引文件的形式存储在磁盘上
②降低更新表的速度:表的数据发生了变化,对应的索引也需要一起变更,从而降低了更新速度。否则索引指向的物理数据可能不对,这也是索引失效的原因之一
18、MySQL中的varchar与char的区别?varchar(30)中的30代表的含义?
- 区别:varchar是一种可变长度的类型,char是一种固定长度的类型
- varchar(30)中30的含义最多存放30个字符。
- 对效率要求高用char,对空间要求高用varchar
19、解释一下什么是池化设计思想。什么是数据库连接池?为什么需要数据库连接池?
池化设计应该不是⼀个新名词。我们常见的如java线程池、jdbc连接池、redis连接池等就是这类设计的代表实现。这种设计会初始预设资源,解决的问题就是抵消每次获取资源的消耗,如创建线程的开销,获取远程连接的开销等。就好比你去食堂打饭,打饭的大妈会先把饭盛好几份放那里,你来了就直接拿着饭盒加菜即可,不用再临时又盛饭又打菜,效率就高了。除了初始化资源,池化设计还包括如下这些特征:池子的初始值、池子的活跃值、池子的最大值等,这些特征可以直接映射到java线程池和数据库连接池的成员属性中。
数据库连接本质就是一个socket的连接。数据库服务端还要维护一些缓存和用户权限信息之类的,所以占用了一些内容。我们可以把数据库连接池看作是维护的数据库连接的缓存,以便将来需要对数据库的请求时可以重用这些连接。为每个用户打开和维护数据库连接,尤其是对动态数据库驱动的网站应用程序的请求,既昂贵又浪费资源。在连接池中,创建连接之后,将其放置在池中,并再次使用它,因此不必建立新的连接。如果使用了所有连接,则会建立一个新连接并将其添加到池中。连接池还减少了用户必须等待建立与数据库的连接的时间。
20、分表分库之后,id主键如何处理?
因为要是分成多个表之后,每个表都是从1开始累加,这样是不对的,我们需要一个全局唯一的id来支持。
生成全局id有下面这几种方式:
- UUID:不适合作为主键,因为太长了,并且无序不可读,查询效率低。比较适合用于生成唯一的名字的标识比如文件的名字。
- 数据库自增id:两台数据库分别设置不同步长,生成不重复ID的策略来实现高可用。这种方式生成的 id 有序,但是需要独立部署数据库实例,成本高,还会有性能瓶颈。
- 利用redis生成id:性能比较好,灵活方便,不依赖于数据库。但是,引入了新的组件造成系统更加复杂,可用性降低,编码更加复杂,增加了系统成本。
- Twitter的snowflake算法
- 美团的Leaf分布式ID生成系统:Leaf 是美团开源的分布式ID⽣成器,能保证全局唯⼀性、 趋势递增、单调递增、信息安全,里面也提到了几种分布式方案的对比,但也需要依赖关系数据库、Zookeeper等中间件。
24-5-27 周一
21、说说InnoDB与MyISAM有哪些区别?
-
在MySQL5.1及其之前的版本中,MyISAM是默认的存储引擎,而在MySQL5.5版本之后,默认使用InnoDB存储引擎。
-
MyISAM不支持行级锁,换句话说,MyISAM 会对整张表加锁,而不是针对行。同时, MyISAM不支持事务和外键。MyISAM可被压缩,存储空间较小,而且MyISAM在筛选大量数据时非常快。
-
InnoDB是事务型引擎,当事务异常提交时,会被回滚。同时,InnoDB支持行锁。此外,InnoDB需要更多存储空间,会在内存中建立其专用的缓冲池用于高速缓冲数据和索引。InnoDB支持自动崩溃恢复特性。
建议:一般情况下,个人建议优先选择 InnoDB 存储引擎,并且尽量不要将 InnoDB 与 MyISAM 混合使用。
22、MySQL索引类型有哪些?
- 主键索引 索引列中的值必须是唯一的,不允许有空值。
- 普通索引 MySQL中基本索引类型,没有什么限制,允许在定义索引的列中插入重复值和空值。
- 唯一索引 索引列中的值必须是唯一的,但是允许为空值。
- 全文索引 只能在文本类型CHAR,VARCHAR,TEXT类型字段上创建全文索引。字段长度比较大时,如果创建普通索引,在进行like模糊查询时效率比较低,这时可以创建全文索引。MyISAM和InnoDB中都可以 使用全文索引。 空间索引MySQL在5.7之后的版本支持了空间索引,而且支持OpenGIS几何数据模型。MySQL在空间索引这方面遵循OpenGIS几何数据模型规则。
- 前缀索引 在文本类型如CHAR,VARCHAR,TEXT类列上创建索引时,可以指定索引列的长度,但是数值类型不能指定。
什么时候不要使用索引?
- 经常增删改的列不要建立索引;
- 有大量重复的列不建立索引;
- 表记录太少不要建立索引。
索引的数据结构
MySQL索引使⽤的数据结构主要有BTree索引 和 哈希索引 。对于哈希索引来说,底层的数据结构就是哈希表,因此在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余大部分场景,建议选择BTree索引。
MySQL的BTree索引使⽤的是B树中的B+Tree,但对于主要的两种存储引擎的实现方式是不同的。
- MyISAM: B+Tree叶节点的data域存放的是数据记录的地址。在索引检索的时候,首先按照 B+Tree搜索算法搜索索引,如果指定的Key存在,则取出其 data 域的值,然后以data域的值为地址读取相应的数据记录。这被称为“非聚簇索引”。
- InnDB:其数据文件本身就是索引⽂件。相比MyISAM,索引文件和数据文件是分离的,其表数据文件本身就是按B+Tree组织的⼀个索引结构,树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据文件本身就是主索引。这被称为“聚簇索引(或聚集索引)”。而其余的索引都作为辅助索引,辅助索引的data域存储相应记录主键的值而不是地址,这也是和MyISAM不同的地⽅。在根据主索引搜索时,直接找到key所在的节点即可取出数据;在根据辅助索引查找时,则需要先取出主键的值,再走⼀遍主索引。 因此,在设计表的时候,不建议使⽤过长的字段作为主键,也不建议使用非单调的字段作为主键,这样会造成主索引频繁分裂。
23、MVCC?
多版本并发控制(MVCC=Mutil-Version Concurrency Control),是一种用来解决读-写冲突的无锁并发控制。也就是为事务分配单向增长的时间戳,为每个修改保存一个版本。版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照(复制了一份数据)。这样在读操作不用阻塞写操作,写操作不用阻塞读操作的同时,避免了脏读和不可重复读。
24、MVCC可以为数据库解决什么问题?
在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数 据库并发读写的性能。同时还可以解决脏读、幻读、不可重复读等事务隔离问题,但不能解决更新丢失问题。
25、说说MVCC的实现原理
MVCC 的目的就是多版本并发控制,在数据库中的实现,就是为了解决读写冲突,它的实现原理主 要是依赖记录中的 3 个隐式字段、undo 日志、Read View 来实现的。
24-5-31 周五
26、说一说悲观锁和乐观锁?
在数据库管理领域,悲观锁和乐观锁是两种处理并发控制的重要策略,常用于保证数据的一致性和完整性,特别是在高并发环境下。
悲观锁
概念:
悲观锁假设最坏的情况,即在事务处理过程中,其他事务会对同一数据进行修改,因此在事务开始时就立即锁定所需的数据资源,直到事务结束才释放锁。这种机制确保了事务处理过程中的数据不会被其他事务干扰。
适用场景:
①当并发写操作相对频繁,且写操作远多于读操作的场景。
②对数据一致性和实时性要求极高的系统,如金融交易系统。
优点:
强一致性:可以有效防止并发冲突,确保数据的完成性和一致性。
缺点:
性能影响:由于锁住资源的时间较长,可能会导致其他需要访问该资源的事务等待,增加系统的响应时间和降低并发处理能力。
死锁风险:在复杂的事务处理中,如果多个事务以不恰当的顺序申请锁,可能会发生死锁。
乐观锁
概念:
乐观锁采取较为宽松的锁策略,它假定在事务处理过程中并发冲突的概率较低。在读取数据时不加锁,而是在更新数据时检查在此期间数据是否被其他事务修改过。通常通过版本号、时间戳等机制实现,只有当数据未被修改时,更新才会成功。
使用场景:
①读多写少的场景,尤其是那些并发读操作远多于写操作的系统。
②对性能要求比较高,允许一定程度并发冲突的系统。
优点:
提高性能:因为不锁定数据,减少了事务间的阻塞,提高了系统的并发处理能力和响应速度。
缺点:
无法绝对避免并发冲突:在高并发写操作下,可能需要多次重试才能成功更新数据。
数据一致性较弱:相比悲观锁,在某些情况下可能牺牲了一定程度的数据即时一致性。
27、主键与索引有什么区别?
-
主键一定会创建一个唯一索引,但是有唯一索引的列不一定是主键;
-
主键不允许有空值,唯一索引列允许有空值;
-
一个表只能有一个主键,但是可以有多个唯一索引;
-
主键可以被其他表引用为外键,唯一索引列不可以;
-
主键是一种约束,而唯一索引是一种索引,是表的冗余数据结构。
28、说说MySQL数据库的锁?
关于MySQL的锁机制,可能会被问很多问题。
MySQL中有共享锁和排它锁,也就是读锁和写锁。
- 共享锁:不阻塞,多个用户可以同一时刻读取同一资源,相互之间没有影响。
- 排它锁:一个写操作阻塞其他的读锁和写锁,这样可以只允许一个用户进行写入,防止其他用户读取正在写入的资源。
- 表锁:系统开销最小,会锁定整张表,MyISAM使用表锁。
- 行锁:容易出现死锁,发生冲突概率低,并发高,InnoDB支持行锁(必须有索引才能实现,否则会自动锁全表,那么就不是行锁了)。
29、怎样尽量避免死锁的出现?
1、设置获取锁的超时时间,至少能保证最差情况下,可以退出程序,不至于一直等待导致死锁;
2、设置按照同一顺序访问资源,类似于串行执行;
3、避免事务中的用户交叉;
4、保持事务简短并在一个批处理中;
5、使用低隔离级别;
6、使用绑定链接。
30、数据库的三范式是什么?
第一范式:列不可再分
第二范式:行可以唯一区分,主键约束
第三范式:表的非主属性不能依赖与其他表的非主属性外键约束
三大范式是一级一级依赖的,第二范式建立在第一范式上,第三范式建立在第一第二范式上
24-6-2 周天
Redis
1、简单介绍一下redis呗!
简单来说 Redis 就是一个使⽤C语言开发的数据库,不过与传统数据库不同的是Redis的数据是存在内存中的 ,也就是它是内存数据库,所以读写速度非常快,因此Redis 被广泛应⽤于缓存方向。 另外,Redis除了做缓存之外,Redis也经常用来做分布式锁,甚⾄是消息队列。 Redis 提供了多种数据类型来支持不同的业务场景。Redis 还支持事务 、持久化、Lua 脚本、多种集群方案。
2、分布式缓存常见的技术选型方案有哪些?
分布式缓存的话,使用的比较多的主要是 Memcached 和 Redis。分布式缓存最开始兴起的时候用的是Memcached,后续主要使用的Redis。
分布式缓存主要解决的是单机缓存的容量受服务器限制并且无法保存通用的信息。因为,本地缓存只在当前服务里有效,比如如果你部署了两个相同的服务,他们两者之间的缓存数据是无法共同的。
3、说说Redis和Memcached的区别和共同点
共同点:
1、都是基于内存的数据库,一般都用来当做缓存使用。
2、都有过期策略。
3、两者的性能都非常高。
区别:
1、Redis支持更丰富的数据类型(支持更复杂的应用场景)。Redis 不仅仅⽀持简单的 k/v 类 型的数据,同时还提供 list,set,zset,hash 等数据结构的存储。Memcached 只支持最简单的 k/v 数据类型。
2、Redis 支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,⽽Memecache 把数据全部存在内存之中。
3、Redis有灾难恢复机制。因为可以把缓存中的数据持久化到磁盘中。
4、Redis支持发布订阅模型、Lua脚本、事务等功能,而Memcached不支持。并且,Redis支持更多的编程语言。
4、缓存数据的处理流程是怎样的?
简单来说就是:
- 如果⽤户请求的数据在缓存中就直接返回。
- 缓存中不存在的话就看数据库中是否存在。
- 数据库中存在的话就更新缓存中的数据。
- 数据库中不存在的话就返回空数据。
5、为什么要用Redis/缓存?
简单来说,来说使用缓存主要是为了提升用户体验以及应对更多的用户。
场景:假如用户第一次访问数据库中的某些数据的话,这个过程是比较慢,毕竟是从硬盘中读取的。但是,如果说,用户访问的数据属于高频数据并且不会经常改变的话,那么我们就可以很放心地将该用户访问的数据存在缓存中。
这样有什么好处呢?
那就是保证用户下⼀次再访问这些数据的时候就可以直接从缓存中获取了。 操作缓存就是直接操作内存,所以速度相当快。 不过,要保持数据库和缓存中的数据的⼀致性。 如果数据库中的对应数据改变的之后,同步改变缓存中相应的数据即可!
高并发:
一般像MySQL这类的数据库的QPS大概都在1w左右(4核8g),但是使用Redis缓存之后很容易达到10w+,甚至最高达30w+。(就单机Redis的情况,Redis集群的话会更高)。
QPS(Query Per Second):服务器每秒可以执⾏的查询次数
;
所以,直接操作缓存能够承受的数据库请求数量是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样⽤户的⼀部分请求会直接到缓存这而不用经过数据库。进而,我们也就提高的系统整体的并发。
24-6-3 周一
6、Redis 常见数据结构以及使用场景分析
-
string
介绍:string 数据结构是简单的 key-value 类型。虽然 Redis 是⽤ C 语⾔写的,但是 Redis 并没有使⽤ C 的字符串表示,而是自己构建了一种简单动态字符串(simple dynamic string,SDS)。相⽐于 C 的原⽣字符串,Redis 的 SDS 不光可以保存文本数据还可以保存二进制数据,并且获取字符串长度复杂度为 O(1)(C 字符串为 O(N)),除此之外,Redis 的 SDS API 是安全的,不会造成缓冲区溢出。
常⽤命令: set,get,strlen,exists,dect,incr,setex 等等。
应用场景:一般常用在需要计数的场景,比如用户的访问次数、热点文章的点赞转发数量等。
-
list
介绍:list即是链表。链表是⼀种非常常见的数据结构,特点是易于数据元素的插入和删除并且可以灵活调整链表长度,但是链表的随机访问困难。许多高级编程语言都内置了链表的实现比如 Java 中的 LinkedList,但是 C语言并没有实现链表,所以 Redis 实现了自己的链表数据结构。Redis 的 list 的实现为⼀个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销。
常用命令:rpush、lpop、rpop、lrange、llen等。
应用场景:发布与订阅或者说消息队列、慢查询。
-
hash
介绍 :hash 类似于 JDK1.8 前的 HashMap,内部实现也差不多(数组 + 链表)。不过, Redis 的 hash 做了更多优化。另外,hash 是⼀个 string 类型的 field 和 value 的映射表, 特别适合⽤于存储对象,后续操作的时候,你可以直接仅仅修改这个对象中的某个字段的值。 比如我们可以用hash 数据结构来存储用户信息,商品信息等等。
常⽤命令: hset,hmset,hexists,hget,hgetall,hkeys,hvals 等。
应⽤场景: 系统中对象数据的存储。
-
set
介绍 : set 类似于 Java 中的 HashSet 。Redis 中的 set 类型是⼀种无序集合,集合中的元 素没有先后顺序。当你需要存储⼀个列表数据,⼜不希望出现重复数据时,set 是⼀个很好 的选择,并且 set 提供了判断某个成员是否在⼀个 set 集合内的重要接⼝,这个也是 list 所 不能提供的。可以基于 set 轻易实现交集、并集、差集的操作。⽐如:你可以将⼀个⽤户所 有的关注⼈存在⼀个集合中,将其所有粉丝存在⼀个集合。Redis 可以⾮常⽅便的实现如共同关注、共同粉丝、共同喜好等功能。这个过程也就是求交集的过程。
常⽤命令: sadd,spop,smembers,sismember,scard,sinterstore,sunion 等。
应用场景: 需要存放的数据不能重复以及需要获取多个数据源交集和并集等场景
-
sorted set
介绍: 和 set 相⽐,sorted set 增加了⼀个权重参数 score,使得集合中的元素能够按 score 进⾏有序排列,还可以通过 score 的范围来获取元素的列表。有点像是 Java 中 HashMap 和 TreeSet 的结合体。
常⽤命令: zadd,zcard,zscore,zrange,zrevrange,zrem 等。
应⽤场景: 需要对数据根据某个权重进行排序的场景。比如在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息
7、Redis给缓存数据设置过期时间有啥用?
一般情况下,我们设置保存的缓存数据的时候都会设置一个过期时间。为什么呢?
因为内存是有限的,如果缓存中的所有数据都一直保存的话,就会出现 out of memory。redis自带了给缓存数据设置过期时间的功能,比如:
exp key 60 # 数据在60s后过期
setex key 60 value # 数据在60s后过期
ttl key # 查看数据还有多久过期
注意:Redis中除了字符串类型有自己独有设置过期时间的命令 setex 外,其他方法都需要依靠 expire 命令来设置过期时间 。另外, persist命令可以移除一个键的过期时间;
过期时间除了有助于缓解内存的消耗,还有什么其他的用处吗?
很多时候,我们的业务场景就是需要某个数据只在某⼀时间段内存在,比如我们的短信验证码可能只在1分钟内有效,用户登录的 token 可能只在 1 天内有效。
如果使用传统的数据库来处理的话,⼀般都是自己判断过期,这样更麻烦并且性能要差很多。
8、Redis是如何判断数据是否过期的?-高频
Redis通过⼀个叫做过期字典(可以看作是hash表)来保存数据过期的时间。过期字典的键指向Redis数据库中的某个key(键),过期字典的值是⼀个long long类型的整数,这个整数保存了key所指向的数据库键的过期时间(毫秒精度的UNIX时间戳)。
9、过期的数据的删除策略了解吗?-高频
如果假设你设置了⼀批 key 只能存活 1 分钟,那么 1 分钟后,Redis 是怎么对这批 key 进行删除的呢?
常用的过期数据的删除策略有2个:
1、惰性删除:只会在取出key的时候才对数据进行过期检查。这样对CPU最友好,但是可能会造成太多过期 key 没有被删除。
2、定期删除:每隔⼀段时间抽取一批 key 执行删除过期key操作。并且,Redis 底层会通过限制删除操作执行的时长和频率来减少删除操作对CPU时间的影响。
定期删除对内存更加友好,惰性删除对CPU更加友好。所以Redis采用的是定期删除+惰性删除。
但是,仅仅通过给 key 设置过期时间还是有问题的。因为还是可能存在定期删除和惰性删除漏掉了很多过期 key 的情况。这样就导致大量过期 key 堆积在内存里,然后就Out of memory了。
解决这个问题的方式就是:Redis内存淘汰机制。
10、Redis内存淘汰机制是什么?(重点)
相关问题:MySQL ⾥有 2000w 数据,Redis 中只存 20w 的数据,如何保证 Redis中的数据都是热点数据?
Redis 提供 6 种数据淘汰策略:
- volatile-lru(least recently used):从已设置过期时间的数据集(server.db[i].expires) 中挑选最近最少使用的数据淘汰
- volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
- volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
- allkeys-lru(least recently used):当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)
- allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
- no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。这个应该没人使用吧!
4.0 版本后增加以下两种:
volatile-lfu(least frequently used):从已设置过期时间的数据集(server.db[i].expires)中 挑选最不经常使用的数据淘汰
allkeys-lfu(least frequently used):当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的 key
24-6-4 周二
11、Redis的持久化机制(怎样保证Redis挂掉之后再重启数据可以进行恢复)
很多时候我们需要持久化数据也就是将内存中的数据写入到硬盘里面,大部分原因是为了之后重用数据(比如重启机器、机器故障之后恢复数据),或者是为了防止系统故障而将数据备份到一个远程位置。
Redis 不同于 Memcached 的很重要⼀点就是,Redis支持持久化,而且支持两种不同的持久化操作。Redis的一种持久化方式叫快照(snapshotting,RDB),另⼀种方式是只追加文件 (append-only file, AOF)。
快照(snapshotting)持久化(RDB->Redis DataBase)
Redis可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。Redis创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本 (Redis 主从结构,主要用来提高 Redis 性能),还可以将快照留在原地以便重启服务器的时候使用。
快照持久化是 Redis 默认采用的持久化方式,在 Redis.conf 配置文件中默认有此下配置:
save 900 1 # 在900秒(15分钟)之后,如果至少有1个key发生变化,Redis就会自动触发
BGSAVE命令创建快照。
save 300 10 #在300秒(5分钟)之后,如果⾄少有10个key发⽣变化,Redis就会⾃动触发
BGSAVE命令创建快照。
save 60 10000 #在60秒(1分钟)之后,如果⾄少有10000个key发⽣变化,Redis就会⾃动
触发BGSAVE命令创建快照。
AOF(append-only file)持久化
与快照持久化相比,AOF 持久化的实时性更好,因此已成为主流的持久化放方案。默认情况下 Redis 没有开启 AOF(append only file)⽅式的持久化,可以通过 appendonly 参数开启:
appendonly yes
开启 AOF 持久化后每执⾏一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入硬盘中的AOF⽂件。AOF ⽂件的保存位置和 RDB 文件的位置相同,都是通过 dir 参数设置的,默认的 文件名是 appendonly.aof。
在Redis的配置文件中存在三种不同的AOF持久化方式,它们分别是:
appendfsync always # 每次有数据修改发⽣时都会写⼊AOF⽂件,这样会严重降低Redis的速度
appendfsync everysec # 每秒钟同步⼀次,显示地将多个写命令同步到硬盘
appendfsync no # 让操作系统决定何时进行同步
为了兼顾数据和写入性能,⽤户可以考虑 appendfsync everysec 选项 ,让 Redis 每秒同步⼀次 AOF ⽂件,Redis 性能⼏乎没受到任何影响。⽽且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候,Redis 还会优雅的放慢自己的速度以便适 应硬盘的最大写入速度。
12、Redis的事务
Redis可以MULTI、EXEC,DISCARD和WATCH等命令来实现事务的功能。使⽤ MULTI命令后可以输入多个命令。Redis不会立即执行这些命令,而是将它们放到队列,当调⽤了EXEC命令将执行所有命令。
Redis的事务和我们平时理解的关系型数据库的事务不同。事务具有四大特性: 1. 原子性,2. 隔离性,3. 持久性,4. 一致性。
- 原子性(Atomicity): 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
- ⼀致性(Consistency): 执行事务前后,数据保持⼀致,多个事务对同⼀个数据读取的结果是相同的;
- 隔离性(Isolation): 并发访问数据库时,⼀个⽤户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
- 持久性(Durability): ⼀个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
Redis 是不支持roll back 的,因而不满足原子性的(而且不满足持久性)。
Redis中的事务可以理解为:Redis事务提供了一种将多个命令请求打包的功能。然后,再按顺序执行打包的所有命令,并且不会被中途打断。
24-6-15 周三
13、缓存穿透-高频
13.1.什么是缓存穿透?
缓存穿透是指客户端请求的数据在缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库。
13.2.缓存穿透情况的处理流程是怎样的?
如下图所示,用户的请求最终都要跑到数据库中查询⼀遍。
13.3.有哪些解决办法?
①缓存空对象
优点:实现简单,维护方便。
缺点:额外的内存消耗,可能会造成短期的不一致。
②布隆过滤
优点:内存占用较少,没有多余的key
缺点:实现复杂、存在误判可能
其他:
增强id的复杂度,避免被猜测id规律
做好数据的基础格式校验
加强用户权限校验
做好特点参数的限流
14、缓存雪崩-高频
缓存雪崩是指在同一时段大量的缓存key同时失效或者Redis服务宕机,导致大量请求到达数据库,带来巨大压力。
还有一种缓存雪崩的场景是:有一些被大量访问数据(热点缓存)在某一时刻大面积失效,导致对应的请求直接落到了数据库上。
针对热点缓存失效的解决方案:
- 给不同的key的TTL添加随机值
- 给业务添加多级缓存
针对Redis服务不可用的情况:
- 利用Redis集群提高服务的可用性
- 给缓存业务添加降级限流策略,避免同时处理大量的请求
15、缓存击穿-高频
缓存击穿问题也叫热点Key问题,就是一个被高并发访问并且缓存重建业务较复杂的key突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。
解决方案:
①互斥锁
优点:
- 没有额外的内存消耗
- 保证一致性
- 实现简单
缺点:
- 线程需要等待,性能受影响
- 可能有死锁的风险
②逻辑过期
优点:线程无需等待,性能较好
缺点:
- 不保证一致性
- 有额外的内存消耗
- 实现复杂
16、如何保证缓存和数据库数据的一致性?-高频
Cache Aside Pattern(旁路缓存模式)
Cache Aside Pattern中遇到写请求是这样的:更新DB,然后直接删除cache。
如果更新数据库成功,而删除缓存这一步失败的情况的话,简单说两个解决方案:
1、缓存失效时间变短(不推荐,治标不治本):我们让缓存数据的过期时间变短,这样的话缓存就会从数据库中加载数据。另外,这种解决办法对于先操作缓存后操作数据库的场景不使用。
2、增加cache更新重试机制(常用):如果cache服务当前不可用导致缓存删除失败的话, 我们就隔⼀段时间进行重试,重试次数可以自己定。如果多次重试还是失败的话,我们可以把当前更新失败的 key 存入队列中,等缓存服务可用之后,再将缓存中对应的key删除即可。
方案一:采用先更新数据库,再删除缓存的策略。
优点:确保数据库中的数据是最新的,然后通过删除缓存来触发后续对新数据的加载。在这个过程中,要注意处理可能出现的删除缓存失败的情况,可以通过重试机制来保障。
方案二:设置合理的缓存过期时间。让缓存数据在一定时间后自动失效,从而去重新从数据库中获取最新数据,这能在一定程度上避免长时间的数据不一致。
方案三:利用消息队列来处理数据更新操作。当数据库数据发生变化时,将更新消息发送到队列中,由专门的处理程序来根据消息更新缓存,确保一致性。
17、redis为什么快(从单线程模型以及数据结构和内存型数据库三方面考虑)
redis采用了单线程模型,避免了线程切换的开销、避免了锁竞争(单线程模型下所有操作都是串行执行的,完全避免了锁竞争问题);Redis 提供了丰富且高效的内置数据结构,每种数据结构都进行了专门的优化,确保了 Redis 在处理不同类型的数据时都能保持高效;redis是内存型数据库,所有数据都存储在内存中,内存访问速度快,避免了磁盘I/O操作,redis提供了RDB快照和AOF日志两种持久化机制,既保证了数据的高速访问,又能在需要时将数据持久化到磁盘,兼顾了性能和数据可靠性。
18、redis大key问题
什么是大key问题:大key问题是指在redis中某个键的值非常大,可能是一个很大的字符串、大的列表、集合、哈希或有序集合。大Key会导致内存消耗、操作耗时、网络开销的问题。
处理大key问题:
①分片存储:将大key拆分成多个小key存储。
②分段操作:对大key进行分段操作,避免一次性操作大量数据。
③定期清理和压缩:定期检查并清理不再需要的大key,或者对大key进行压缩存储。
④使用合适的数据结构:选择合适的数据结构来存储数据,避免使用不适合的结构导致大key。