11,Mysql 与Redis

11.1 存储引擎

11.1.1 存储MD5 应该用什么类型

MD5 是由数字和字母组成的一个 16 位或者 32 位长度的字符串,一般在应用开发中都是使用 32 位。
要回答这个问题,必须了解这两个类型的功能特性和区别。
 第一个,char 是一个固定长度的字符串,Varchar 是一个可变长度的字符串假设声明一个 char(10)的长度,如果存储字符串“abc”,虽然实际字符长度只有3,但是 char 还是会占 10 个字节长度。
同样,如果用 varchar 存储,那它只会使用 3 个字符的实际长度来存储。
 第二个,存储的效率不同,char 类型每次修改以后存储空间的长度不变,所以效率更高varchar 每次修改数据都需要更新存储空间长度,效率较低
 第三个,存储空间不同,char 不管实际数据大小,存储空间是固定的,而varchar 存储空间等于实际数据长度,所以 varchar 实际存储空间的使用要比 char 更小
基于他们特性的分析,可以得出一个基本的结论:
 char 适合存储比较短的且是固定长度的字符串
 varchar 适合存储可变长度的字符串
回答
我认为应该使用 Char 类型,原因是:
char 类型是固定长度的字符串,varchar 是可变长度字符串。
而 MD5 是一个固定长度的字符,不管数据怎么修改,长度不变,这个点很符合 char 类型。
另外,由于是固定长度,所以在数据变更的时候,不需要去调整存储空间大小,在效率上会比 varchar

11.1.3 导致索引失效的原因有哪些

InnoDB 引擎里面有两种索引类型,一种是主键索引、一种是普通索引。
InnoDB 用了 B+树的结构来存储索引数据。
当使用索引列进行数据查询的时候,最终会到主键索引树中查询对应的数据并进行返回。
理论上来说,使用索引列查询,就能很好地提升查询效率,但是不规范地使用会导致索引失效,从而无法发挥索引本身的价值。
导致索引失效的情况有很多:

  1. 在索引列上做运算,比如使用函数,Mysql 在生成执行计划的时候,它是根据统计信息来判断是否要使用索引的。
    而在索引列上加函数运算,导致 Mysql 无法识别索引列,也就不会再走索引了。
    不过从 Mysql8 开始,增加了函数索引可以解决这个问题。
  2. 在一个由多列构成的组合索引中,需要按照最左匹配法则,也就是从索引的最左列开始顺序检索,否则不会走索引。
    在组合索引中,索引的存储结构是按照索引列的顺序来存储的,因此在 sql 中也需要按照这个顺序才能进行逐一匹配。否则 InnoDB 无法识别索引导致索引失效。
  3. 当索引列存在隐式转化的时候, 比如索引列是字符串类型,但是在 sql 查询中没有使用引号。那么 Mysql 会自动进行类型转化,从而导致索引失效
  4. 在索引列使用不等于号、not 查询的时候,由于索引数据的检索效率非常低,因此 Mysql 引擎会判断不是索引。
  5. 使用 like 通配符匹配后缀%xxx 的时候,由于这种方式不符合索引的最左匹配原则,所以也不会走索引。
    但是反过来,如果通配符匹配的是前缀 xxx%,符合最左匹配,也会走索引。
  6. 使用 or 连接查询的时候,or 语句前后没有同时使用索引,那么索引会失效。只有 or 左右查询字段都是索引列的时候,才会生效。
    除了这些场景以外,对于多表连接查询的场景中,连接顺序也会影响索引的使用。
    不过最终是否走索引,我们可以使用 explain 命令来查看 sql 的执行计划,然后针对性地进行调优即可。

11.1.4 什么是聚集索引和非聚集索引

. 简单来说,聚集索引就是基于主键创建的索引,除了主键索引以外的其他索引,称为非聚集索引,也叫作二级索引。

  1. 由于在 InnoDB 引擎里面,一张表的数据对应的物理文件本身就是按照 B+树来组织的一种索引结构,而聚集索引就是按照每张表的主键来构建一颗 B+树,然后叶子节点里面存储了这个表的每一行数据记录。
  2. 所以基于 InnoDB 这样的特性,聚集索引并不仅仅是一种索引类型,还代表着一种数据的存储方式。
  3. 同时也意味着每个表里面必须有一个主键,如果没有主键,InnoDB 会默认选择或者添加一个隐藏列作为主键索引来存储这个表的数据行。一般情况是建议使用自增 id 作为主键,这样的话 id 本身具有连续性使得对应的数据也会按照顺序存储在磁盘上,写入性能和检索性能都很高。否则,如果使用 uuid 这种随机 id,那么在频繁插入数据的时候,就会导致随机磁盘 IO,从而导致性能降低。
  4. 需要注意的是,InnoDB 里面只能存在一个聚集索引,原因很简单,如果存在多个聚集索引,那么意味着这个表里面的数据存在多个副本,造成磁盘空间的浪费,以及数据维护的困难。
  5. 由于在 InnoDB 里面,主键索引表示的是一种数据存储结构,所以如果是基于非聚集索引来查询一条完整的记录,最终还是需要访问主键索引来检索。

11.1.5 谈谈你对B树和B+树的理解

 B 树和 B+树的应用场景

  1. B 树是一种多路平衡查找树 。
    二叉树,每个节点支持两个分支的树结构,相比于单向链表,多了一个分支。
    二叉查找树,在二叉树的基础上增加了一个规则,左子树的所有节点的值都小于它的根节点,右子树的所有子节点都大于它的根节点。
    二叉查找树会出现斜树问题,导致空间复杂度增加,因此又引入了一种平衡二叉树,它具有二叉查找树的所有特点,同时增加了一个规则:”它的左右两个子树的高度差的绝对值不超过 1。平衡二叉树会采用左旋、右旋的方式来实现平衡。
    而 B 树是一种多路平衡查找树,它满足平衡二叉树的规则,但是它可以有多个子树,子树的数量取决于关键字的数量,比如这个图中根节点有两个关键字 3 和 5,那么它能够拥有的子路数量= 关键字数+1。
    因此从这个特征来看,在存储同样数据量的情况下,平衡二叉树的高度要大于 B 树。
    B+树,其实是在 B 树的基础上做的增强,最大的区别有两个:
    a. B 树的数据存储在每个节点上,而 B+树中的数据是存储在叶子节点,并且通过链表的方式把叶子节点中的数据进行连接。
    b. B+树的子路数量等于关键字数
  2. B 树和 B+树,一般都是应用在文件系统和数据库系统中,用来减少磁盘 IO 带来的性能损耗。
    以 Mysql 中的 InnoDB 为例,当我们通过 select 语句去查询一条数据时 InnoDB 需要从磁盘上去读取数据,这个过程会涉及磁盘 IO 以及磁盘的随机 IO
    我们知道磁盘 IO 的性能是特别低的,特别是随机磁盘 IO。
    因为,磁盘 IO 的工作原理是,首先系统会把数据逻辑地址传给磁盘,磁盘控制电路按照寻址逻辑把逻辑地址翻译成物理地址,也就是确定要读取的数据在哪个磁道,哪个扇区。
    为了读取这个扇区的数据,需要把磁头放在这个扇区的上面,为了实现这一个点,磁盘会不断旋转,把目标扇区旋转到磁头下面,使得磁头找到对应的磁道,这里涉及寻道事件以及旋转时间。
    磁盘 IO 这个过程的性能开销是非常大的,特别是查询的数据量比较多的情况下。
    所以在 InnoDB 中,干脆对存储在磁盘块上的数据建立一个索引,然后把索引数据以及索引列对应的磁盘地址,以 B+树的方式来存储。
    当我们需要查询目标数据的时候,根据索引从 B+树中查找目标数据即可,由于 B+树分路较多,所以只需要较少次数的磁盘 IO 就能查找到。
  3. 为什么用 B 树或者 B+树来做索引结构?原因是 AVL 树的高度要比 B 树的高度要高,而高度就意味着磁盘 IO 的数量。所以为了减少磁盘 IO 的次数,文件系统或者数据库才会采用 B 树或者 B+树。
    数据结构在实际开发中非常常见,比如数组、链表、双向链表、红黑树、跳跃表、B 树、B+树、队列等。
    在我看来,数据结构是编程中最重要的基本功之一。
    学了顺序表和链表,我们就能知道查询操作比较多的场景中应该用顺序表,修改操作比较多的场景应该使用链表。

11.1.6 为什么Mysql的索引结构要采用B+树

首先,常规的数据库存储引擎,一般都是采用 B 树或者 B+树来实现索引的存储。
因为 B 树是一种多路平衡树,用这种存储结构来存储大量数据,它的整个高度会相比二叉树来说,会矮很多。
而对于数据库来说,所有的数据必然都是存储在磁盘上的,而磁盘 IO 的效率实际上是很低的,特别是在随机磁盘 IO 的情况下效率更低。
所以树的高度能够决定磁盘 IO 的次数,磁盘 IO 次数越少,对于性能的提升就越大,这也是为什么采用 B 树作为索引存储结构的原因。
但是在 Mysql 的 InnoDB 存储引擎里面,它用了一种增强的 B 树结构,也就是 B+树来作为索引和数据的存储结构。
相比较于 B 树结构,B+树做了几个方面的优化。

  1. B+树的所有数据都存储在叶子节点,非叶子节点只存储索引。
  2. 叶子节点中的数据使用双向链表的方式进行关联。
    使用 B+树来实现索引的原因,我认为有几个方面。
  3. B+树非叶子节点不存储数据,所以每一层能够存储的索引数量会增加,意味着 B+树在层高相同的情况下存储的数据量要比 B 树要多,使得磁盘 IO 次数更少。
  4. 在 Mysql 里面,范围查询是一个比较常用的操作,而 B+树的所有存储在叶子节点的数据使用了双向链表来关联,所以在查询的时候只需将两个节点进行遍历就行,而 B 树需要获取所有节点,所以B+树在范围查询上效率更高。
  5. 在数据检索方面,由于所有的数据都存储在叶子节点,所以 B+树的 IO 次数会更加稳定一些。
  6. 因为叶子节点存储所有数据,所以 B+树的全局扫描能力更强一些,因为它只需要扫描叶子节点。但是 B 树需要遍历整个树。
    另外,基于 B+树这样一种结构,如果采用自增的整型数据作为主键,还能更好地避免增加数据的时候,带来叶子节点分裂导致的大量运算的问题。
    总的来说,我认为技术方案的选型,更多的是去解决当前场景下的特定问题,并不一定是说 B+树就是最好的选择,就像 MongoDB 里面采用 B 树结构,本质上来说,其实是关系型数据库和非关系型数据库的差异。

11.1.7 Mysql索引的优点和缺点是什么

在我看来,Mysql 里面的索引的优点有很多

  1. 通过 B+树的结构来存储数据,可以大大减少数据检索时的磁盘 IO 次数,从而提升数据查询的性能
  2. B+树索引在进行范围查找的时候,只需要找到起始节点,然后基于叶子节点的链表结构往下读取即可,查询效率较高。
  3. 通过唯一索引约束,可以保证数据表中每一行数据的唯一性当然,索引的不合理使用,也会带来很多的缺点。
  4. 数据的增加、修改、删除,需要涉及索引的维护,当数据量较大的情况下,索引的维护会带来较大的性能开销。
  5. 一个表中允许存在一个聚簇索引和多个非聚簇索引,但是索引数不能创建太多,否则造成的索引维护成本过高。
  6. 创建索引的时候,需要考虑到索引字段值的分散性,如果字段的重复数据过多,创建索引反而会带来性能降低。

11.1.8 为什么MYSQL语句命中索引比不命中索引要快

数据库索引最主要的作用就是帮助我们快速检索到想要的数据,从而不至于每次查询都做全局扫描。
MySQL 的 InnoDB 引擎采用的是 B+树数据结构,当我们去执行 SELECT 语句查询数据的时候,InnoDB 需要从磁盘上去读取数据,而这个过程会涉及磁盘 以及磁盘的随机 IO
系统会把数据的逻辑地址传给磁盘,磁盘控制线路按照寻址逻辑把逻辑地址翻译成物理地址。也就是确定要读取的数据在哪个磁道、哪个扇区。为了读取这个扇区的数据,需要把磁头放在这个扇区上面,为了实现这样一个点,磁盘会不断地去旋转。把目标扇区旋转到磁头下面,使得磁头能够找到对应的磁道。
这里还会涉及寻道的时间以及旋转时间的一个损耗。很明显磁盘 IO 这个过程的性能开销是非常大的,尤其是查询的数据量比较多的情况下。
所以 InnotDB 里面,干脆对存储在磁盘上的数据建立一个索引,然后把索引数据以及索引列对应的磁盘地址以 B+树的方式进行存储。
当我们需要查找目标数据的时候,根据索引从 B+树中去查找目标数据就行了。由于 B+树的子树比较多,所以,只需要较少次数的磁盘 IO 就能够查找到目标数据。
在定义索引时,必须牢记以下几点:
1、索引表中的每个字段将降低写入性能。
2、建议使用表中的唯一值为字段编制索引。
3、在关系数据库中充当外键的字段必须建立索引,因为它们有助于跨多个表进行复杂查询。
4、索引还使用磁盘空间,因此在选择要索引的字段时要小心。

11.1.9 mysql的MySAM索引和InnoDB索引有什么区别

  1. InnoDB 支持事务,MyISAM 不支持,对于 InnoDB 每一条 SQL 语言都默认封装成事务,自动提 交,这样会影响速度,所以最好把多条 SQL 语言放在 begin 和 commit 之间,组成一个事务;
  2. InnoDB 支持外键,而 MyISAM 不支持。对一个包含外键的 InnoDB 表转为 MYISAM 会失败;
  3. InnoDB 是聚集索引,数据文件是和索引绑在一起的,必须有主键,通过主键索引效率很高。
    但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。因此,主键不应该 过大,因为主键太大,其他索引也都会很大。而 MyISAM 是非聚集索引,数据文件是分离的, 索引保存的是数据文件的指针。主键索引和辅助索引是独立的。
  4. InnoDB 不保存表的具体行数,执行 select count(*) from table 时需要全表扫描。而 MyISAM
    用 一个变量保存了整个表的行数,执行上述语句时只需要读出该变量即可,速度很快;
  5. Innodb 不支持全文索引,而 MyISAM 支持全文索引,查询效率上 MyISAM 要高;

11.1.10 Mysql表设计时间列用dateTime和timstamp

这两种格式都有各自的优点:

1、DATETIME:
DATETIME 数据类型以 ‘YYYY-MM-DD HH:MM:SS’ 的格式来表示日期和时间。它的范围从 ‘1000-01-01 00:00:00’ 到 ‘9999-12-31 23:59:59’。DATETIME 存储的时间是绝对值,与时区无关。
优点:
可以在任何 MySQL 版本中使用,因为它是标准的 SQL 数据类型。
可以存储广泛的时间范围,包括远古和遥远的未来。
不受时区影响,适用于多地区应用或需要特定时间点的应用场景。
2、TIMESTAMP:
TIMESTAMP 数据类型以 ‘YYYY-MM-DD HH:MM:SS’ 的格式来表示日期和时间。它的范围从 ‘1970-01-01 00:00:01’ UTC 到 ‘2038-01-19 03:14:07’ UTC。TIMESTAMP 存储的时间是相对于 UTC 的,但可以通过设置时区进行转换。
优点:
占用的存储空间更小,只需要 4 字节,相对于 DATETIME 的 8 字节。
自动更新功能:可以设置为默认值为 CURRENT_TIMESTAMP,并且在插入或更新数据时自动更新为当前时间,非常方便。
选择使用哪种格式取决于具体的需求。如果需要存储绝对时间值,不受时区影响,并且需要支持更广泛的时间范围,可以选择 DATETIME。如果存储空间很重要,或者需要自动更新时间戳的功能,可以选择 TIMESTAMP。

11.2 事务

11.2.1 如何理解mysql的事务隔离级别

事务隔离级别,是为了解决多个并行事务竞争导致的数据安全问题的一种规范。
具体来说,多个事务竞争可能会产生三种不同的现象。

  1. 假设有两个事务 T1/T2 同时在执行,T1 事务有可能会读取到 T2 事务未提交的数据,但是未提交的事务 T2 可能会回滚,也就导致了 T1 事务读取到最终不一定存在的数据产生脏读的现象。
  2. 假设有两个事务 T1/T2 同时执行,事务 T1 在不同的时刻读取同一行数据的时候结果可能不一样,从而导致不可重复读的问题。
  3. 假设有两个事务 T1/T2 同时执行,事务 T1 执行范围查询或者范围修改的过程中,事务T2 插入了一条属于事务 T1 范围内的数据并且提交了,这时候在事务 T1 查询发现多出来了一条数据,或者在 T1 事务发现这条数据没有被修改,看起来像是产生了幻觉,这种现象称为幻读。
    而这三种现象在实际应用中,可能有些场景不能接受某些现象的存在,所以在 SQL 标准中定义了四种
    隔离级别,分别是:
  4. 读未提交,在这种隔离级别下,可能会产生脏读、不可重复读、幻读。
  5. 读已提交(RC),在这种隔离级别下,可能会产生不可重复读和幻读。
  6. 可重复读(RR),在这种隔离级别下,可能会产生幻读
  7. 串行化,在这种隔离级别下,多个并行事务串行化执行,不会产生安全性问题。
    这四种隔离级别里面,只有串行化解决了全部的问题,但也意味着这种隔离级别的性能是最低的。
    在 Mysql 里面,InnoDB 引擎默认的隔离级别是 RR(可重复读),因为它需要保证事务 ACID 特性中的隔离性特征。

11.2.2 mysql的事务实现原理

Mysql 里面的事务,满足 ACID 特性,所以在我看来,Mysql 的事务实现原理,就是 InnoDB 是如何保证 ACID 特性的。
首先,A 表示 Atomic 原子性,也就是需要保证多个 DML 操作是原子的,要么都成功,要么都失败。
那么,失败就意味着要对原本执行成功的数据进行回滚,所以 InnoDB 设计了一个 UNDO_LOG 表,在事务执行的过程中,把修改之前的数据快照保存到UNDO_LOG 里面,一旦出现错误,就直接从 UNDO_LOG 里面读取数据执行反向操作就行了。
其次,C 表示一致性,表示数据的完整性约束没有被破坏,这个更多是依赖于业务层面的保证,数据库本身也提供了一些,比如主键的唯一余数,字段长度和类型的保证等等。
接着,I 表示事务的隔离性,也就是多个并行事务对同一个数据进行操作的时候,如何避免多个事务的干扰导致数据混乱的问题。
而 InnoDB 实现了 SQL92 的标准,提供了四种隔离级别的实现。分别是:
RU(未提交读)
RC(已提交读)
RR(可重复读)
Serializable(串行化)
InnoDB 默认的隔离级别是 RR(可重复读),然后使用了 MVCC 机制解决了脏读和不可重复读的问题,然后使用了行锁/表锁的方式解决了幻读的问题。
最后一个是 D,表示持久性,也就是只要事务提交成功,那对于这个数据的结果的影响一定是永久性的。
不能因为宕机或者其他原因导致数据变更失效。
理论上来说,事务提交之后直接把数据持久化到磁盘就行了,但是因为随机磁盘 IO 的效率确实很低,所以 InnoDB 设计了Buffer Pool 缓冲区来优化,也就是数据发生变更的时候先更新内存缓冲区,然后在合适的时机再持久化到磁盘。
那在持久化这个过程中,如果数据库宕机,就会导致数据丢失,也就无法满足持久性了。
所以 InnoDB 引入了 Redo_LOG 文件,这个文件存储了数据被修改之后的值,当我们通过事务对数据进行变更操作的时候,除了修改内存缓冲区里面的数据以外,还会把本次修改的值追加到 REDO_LOG里面。
当提交事务的时候,直接把 REDO_LOG 日志刷到磁盘上持久化,一旦数据库出现宕机,在 Mysql 重启以后可以直接用 REDO_LOG 里面保存的重写日志读取出来,再执行一遍从而保证持久性。
因此,在我看来,事务的实现原理的核心本质就是如何满足 ACID 的,在 InnDB 里面用到了 MVCC、行锁表锁、UNDO_LOG、REDO_LOG 等机制来保证。

11.2.3 谈谈你对MVCC的理解

多版本并发控制(MVCC=Multi-Version Concurrency Control),是一种用来解决读 - 写冲突的无 锁并发控制。也就是为事务分配单向增长的时间戳,为每个修改保存一个版本。版本与事务时间戳 关联,读操作只读该事务开始前的数据库的快照(复制了一份数据)。这样在读操作不用阻塞写操 作,写操作不用阻塞读操作的同时,避免了脏读和不可重复读。
MVCC 可以为数据库解决什么问题?
在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能。同时还可以解决脏读、幻读、不可重复读等事务隔离问题,但不能解决更新丢失问题。
说说 的实现原理
MVCC 的目的就是多版本并发控制,在数据库中的实现,就是为了解决读写冲突,它的实现原理主 要是依赖记录中的 3 个隐式字段、undo 日志、 Read View 来实现的。
MVCC 过程中会加锁吗
在 MVCC 中,通常不需要加锁来控制并发访问。
相反,每个事务都可以读取已提交的快照,而不需要获得共享锁或排他锁。
在写操作的时候,MVCC 会使用一种叫为“写时复制”(Copy-On-Write)的技术,也就是在修改数据之前先将数据复制一份,从而创建一个新的快照。
当一个事务需要修改数据时,MVCC 会首先检查修改数据的快照版本号是否与该事务的快照版本一致,如果一致则表示可以修改这条数据,否则该事务需要等待其他事务完成对该数据的修改。
另外,这个事物在新快照之上修改的结果,不会影响原始数据,
其他事务可以继续读取原始数据的快照,从而解决了脏读、不可重复度问题。
所以,正是有了 MVCC 机制,让多个事务对同一条数据进行读写时,不需要加锁也不会出现读写冲突。

11.2.4mysql的InnoDB如何解决幻读

我会从三个方面来回答:
1、 Mysql 的事务隔离级别
Mysql 有四种事务隔离级别,这四种隔离级别代表当存在多个事务并发冲突时,可能出现的脏读、不可重复读、幻读的问题。
2、 什么是幻读?
那么, 什么是幻读呢?
幻读是指在同一个事务中,前后两次查询相同的范围时,得到的结果不一致
 第一个事务里面我们执行了一个范围查询,这个时候满足条件的数据只有一条
 第二个事务里面,它插入了一行数据,并且提交了
 接着第一个事务再去查询的时候,得到的结果比第一次查询的结果多出来了一条数据。
3、 InnoDB 如何解决幻读的问题
所以,在 InnoDB 中设计了一种间隙锁,它的主要功能是锁定一段范围内的索引记录 ,当对查询范围 id>4 and id <7 加锁的时候,会针对 B+树中(4,7)这个区间范围的索引加间隙锁。
意味着在这种情况下,其他事务对这个区间的数据进行插入、更新、删除都会被锁住。
这条查询语句是针对 id>4 这个条件加锁,那么它需要锁定多个索引区间,所以在这种情况下 InnoDB引入了 next-key Lock 机制。
next-key Lock 相当于间隙锁和记录锁的合集,记录锁锁定存在的记录行,间隙锁锁住记录行之间的间隙,而 next-key Lock 锁住的是两者之和。
每个数据行上的非唯一索引列上都会存在一把 next-key lock,当某个事务持有该数据行的 next-key lock 时,会锁住一段左开右闭区间的数据。
因此,当通过 id>4 这样一种范围查询加锁时,会加 next-key Lock,锁定的区间范围是:(4, 7 , 7,10,10,+∞
间隙锁和 next-key Lock 的区别在于加锁的范围,间隙锁只锁定两个索引之间的引用间隙,而 next-key
Lock 会锁定多个索引区间,它包含记录锁和间隙锁。
当我们使用了范围查询,不仅仅命中了 Record 记录,还包含了 Gap 间隙,在这种情况下我们使用的就是临键锁,它是 MySQL 里面默认的行锁算法。
4 、总结
虽然 InnoDB 中通过间隙锁的方式解决了幻读问题,但是加锁之后一定会影响到并发性能,因此,如果对性能要求较高的业务场景中,可以把隔离级别设置成 RC,这个级别中不存在间隙锁。

11.3 性能优化

11.3.1 执行SQL响应比较慢,你有哪些排查思路

1、排查思路
如果执行 SQL 响应比较慢,我觉得可能有以下 4 个原因:
第 1 个原因:没有索引或者 导致索引失效。
第 2 个原因:单表数据量数据过多,导致查询瓶颈
第 3 个原因:网络原因或者机器负载过高。
第 4 个原因:热点数据导致单点负载不均衡
2、解决方案
第 1 种情况:索引失效或者没有索引的情况
第一,慢 SQL 的定位和排查
我们可以通过慢查询日志和慢查询日志分析工具得到有问题的 SQL 列表。
l 第二,执行计划分析
针对慢 SQL,我们可以使用关键字 explain 来查看当前 sql 的执行计划.可以重点关注 type key rows filterd 等字段 ,从而定位该 SQL 执行慢的根本原因。
如果发现慢查询的 SQL 没有命中索引,可以尝试去优化这些 SQL 语句,保证 SQL 走索引执行。如果 SQL 结构没有办法优化的话,可以考虑在表上再添加对应的索引。我们在优化 SQL 或者是添加索引的时候,都需要符合最左匹配原则。
l 第三,使用 show profile 工具
Show Profile 是 MySQL 提供的可以用来分析当前会话中,SQL 语句资源消耗情况的工具,可用于 SQL调优的测量。在当前会话中.默认情况下处于 show profile 是关闭状态,打开之后保存最近 15 次的运行结果针对运行慢的 SQL,通过 profile 工具进行详细分析.可以得到 SQL 执行过程中所有的资源开销情况. 如 IO 开销,CPU 开销,内存开销等.
第 2 种情况:单表数据量数据过多,导致查询瓶颈的情况。即使 SQL 语句走了索引,表现性能也不会
这个时候我们需要考虑对表进行切分。表切分规则一般分为两种,一种是水平切分,一种是垂直切分。
水平切分的意思是把一张数据行数达到千万级别的大表,按照业务主键切分为多张小表,这些小表可能达到 100 张甚至 1000 张。
那垂直切分的意思是,将一张单表中的多个列,按照业务逻辑把关联性比较大的列放到同一张表中去。
除了这种分表之外,我们还可以分库,比如我们已经拆分完 1000 表,然后,把后缀为 0-100 的表放到同一个数据库实例中,然后,100-200的表放到另一个数据库实例中,以此类推把 1000 表存放到 10 个数据库实例中。这样的话,我们就可以根据业务主键把请求路由到不同数据库实例,从而让每一个数据库实例承担的流量比较小,达到提高数据库性能的目的。
第 3 种情况:网络原因或者机器负载过高的情况,我们可以进行读写分离.
比如 MySQL 支持一主多从的分布式部署,我们可以将主库只用来处理写数据的操作,而多个从库只用来处理读操作。在流量比较大的场景中,可以增加从库来提高数据库的负载能力,从而提升数据库的总体性能。
第 4 种情况:热点数据导致单点负载不均衡的情况。
这种情况下,除了对数据库本身的调整以外,还可以增加缓存。将查询比较频繁的热点数据预存到缓存当中,比如 Redis、MongoDB、ES 等,以此来缓解数据的压力,从而提高数据库的响应速度。

11.3.2 数据库连接池有什么用,他有哪些关键参数

首先,数据库连接池是一种池化技术,池化技术的核心思想是实现资源的复用,避免资源重复创建销毁的开销。
而在数据库的应用场景里面,应用程序每次向数据库发起 CRUD 操作的时候,都需要创建连接
在数据库访问量较大的情况下,频繁地创建连接会带来较大的性能开销。
而连接池的核心思想,就是应用程序在启动的时候提前初始化一部分连接保存到连接池里面,当应用需要使用连接的时候,直接从连接池获取一个已经建立好的连接。
连接池的设计,避免了每次连接的建立和释放带来的开销。
在这里插入图片描述
连接池的参数有很多,不过关键参数就几个:
首先是,连接池初始化的时候会有几个关键参数:

  1. 初始化连接数,表示启动的时候初始多少个连接保存到连接池里面。
  2. 最大连接数,表示同时最多能支持多少连接,如果连接数不够,后续要获取连接的线程会阻塞。
  3. 最大空闲连接数,表示没有请求的时候,连接池中要保留的最大空闲连接。
  4. 最小空闲连接,当连接数小于这个值的时候,连接池需要再创建连接来补充到这个值。
    然后,就是在使用连接的时候的关键参数:
  5. 最大等待时间,就是连接池里面的连接用完了以后,新的请求要等待的时间,超过这个时间就会提示超时异常。
  6. 无效连接清除, 清理连接池里面的无效连接,避免使用这个连接操作的时候出现错误。
    不同的连接池框架,除了核心的参数以外,还有很多业务型的参数,比如是否要检测连接 sql 的有效性、
    连接初始化 SQL 等等,这些配置参数可以在使用的时候去查询 api 文档就可以知道。

11.3.3 为什么分布式系统中不推荐使用多表关联查询

12 Redis 缓存

12.1 谈谈你对Redis的理解

  1. Redis 是一个高性能的基于 Key-Value 结构存储的 Nosql 开源数据库。
  2. 目前市面上绝大部分公司都采用 Redis 来实现分布式缓存,从而提高数据的检索效率。
  3. Redis 之所以这么流行,主要有几个特点:
    a. 它是基于内存存储,在进行数据 IO 操作时,能够 10W QPS
    b. 提供了非常丰富的数据存储结构,如 String、List、Hash、Set、ZSet 等。
    c. Redis 底层采用单线程实现数据的 IO,所以在数据算法层面并不需要考虑并发安全性,所以底层算法上的时间复杂度基本上都是常量。
  4. Redis 虽然是内存存储,但是它也可以支持持久化,避免因为服务器故障导致数据丢失的问题
    基于这些特点,Redis 一般用来实现分布式缓存,从而降低应用程序对关系型数据库检索带来的性能影响。除此之外,Redis 还可以实现分布式锁、分布式队列、排行榜、查找附近的人等功能,为复杂应用提供非常方便和成熟的解决方案

12.2 如何解决缓存雪崩,缓存穿透和缓存击穿问题

缓存雪崩,就是存储在缓存里面的大量数据,在同一个时刻全部过期,原本缓存组件抗住的大部分流量全部请求到了数据库。导致数据库压力增加造成数据库服务器崩溃的现象。
在这里插入图片描述
导致缓存雪崩的主要原因,我认为有两个:

  1. 缓存中间件宕机,当然可以对缓存中间件做高可用集群来避免。
  2. 缓存中大部分 key 都设置了相同的过期时间,导致同一时刻这些 key 都过期了。对于这样的情况,可以在失效时间上增加一个 1 到 5 分钟的随机值。
    缓存穿透 ,表示短时间内有大量的不存在的 key 请求到应用里面,而这些不存在的 key 在缓存里面又找不到,从而全部穿透到了数据库,造成数据库压力。
    我认为这个场景的核心问题是针对缓存的一种攻击行为,因为在正常的业务里面,即便是出现了这样的情况,由于缓存的不断预热,影响不会很大。
    而攻击行为就需要具备时间的持续性,而只有 key 确实在数据库里面也不存在的情况下,才能达到这个目的,所以,我认为有两个方法可以解决:
  3. 把无效的 key 也保存到 Redis 里面,并且设置一个特殊的值,比如“null”,这样的话下次再来访问,就不会去查数据库了。
  4. (如图)但是如果攻击者不断用随机的不存在的 key 来访问,也还是会存在问题,所以可以用布隆过滤器来实现,在系统启动的时候把目标数据全部缓存到布隆过滤器里面,当攻击者用不存在的key 来请求的时候,先到布隆过滤器里面查询,如果不存在,那意味着这个 key 在数据库里面也不存在。
    布隆过滤器还有一个好处,就是它采用了 bitmap 来进行数据存储,占用的内存空间很少。
    在这里插入图片描述
    不过,在我看来,您提出来的这个问题,有点过于放大了它带来的影响。
    首先,在一个成熟的系统里面,对于比较重要的热点数据,必然会有一个专门缓存系统来维护,同时它的过期时间的维护必然和其他业务的 key 会有一定的差别。而且非常重要的场景,我们还会设计多级缓存系统。
    其次,即便是触发了缓存雪崩,数据库本身的容灾能力也并没有那么脆弱,数据库的主从、双主、读写分离这些策略都能够很好地缓解并发流量。
    最后,数据库本身也有最大连接数的限制,超过限制的请求会被拒绝,再结合熔断机制,也能够很好地保护数据库系统,最多就是造成部分用户体验不好。
    另外,在程序设计上,为了避免缓存未命中导致大量请求穿透到数据库的问题,还可以在访问数据库这个环节加锁。虽然影响了性能,但是对系统是安全的。
    在这里插入图片描述

12.3 简述Redis持久化机制RDB和AOF实现原理

关于 RDB 和 AOF 的实现原理以及优缺点,我觉得可以分成三个部分去回答。
第一部分,先说明这两种持久化机制的特性。
RDB 和 AOF 都是 Redis 里面提供的持久化机制,RDB 是通过快照方式实现持久化、AOF 是通过命令追加的方式实现持久化。
第二部分,说明这两种机制的工作原理
【如图】RDB 持久化机制会根据快照触发条件,把内存里面的数据快照写入到磁盘,以二进制的压缩文件进行存储。
在这里插入图片描述

RDB 快照的触发方式有很多,比如
 执行 bgsave 命令触发异步快照,执行 save 命令触发同步快照,同步快照会阻塞客户端的执行指令。
 根据 redis.conf 文件里面的配置,自动触发 bgsave
 主从复制的时候触发
AOF 持久化机制是近乎实时的方式来完成持久化的,就是客户端执行一个数据变更的操作,Redis Server 就会把这个命令追加到 aof 缓冲区的末尾,然后再把缓冲区的数据写入到磁盘的 AOF 文件里面,至于最终什么时候真正持久化到磁盘,是根据刷盘的策略来决定的。
为了避免追加的方式导致 AOF 文件过大的问题,Redis 提供了 AOF 重写机制(如图),也就是说当 AOF 文件的大小达到某个阈值的时候,就会把这个文件里面相同的指令进行压缩。
在这里插入图片描述
第三部分,AOF 和 RDB 的优缺点分析
我认为 RDB 和 AOF 的优缺点有两个。
 RDB 是每隔一段时间触发持久化,因此数据安全性低,AOF 可以做到实时持久化,数据安全性较高
 RDB 文件默认采用压缩的方式持久化,AOF 存储的是执行指令,所以 RDB 在数据恢复的时候性能比 AOF 要好

12.4 简述Redis中AOF从写的过程

AOF 是 Redis 里面的一种数据持久化方式,它采用了指令追加的方式(如图)。
近乎实时地去实现数据指令的持久化,因为 AOF,会把每个数据更改的操作指令,追加存储到 aof 文件里面。
所以很容易导致 AOF 文件出现过大,造成 IO 性能问题。
Redis 为了解决这个问题,设计了 AOF 重写机制,也就是说把 AOF 文件里面相同的指令进行压缩,只保留最新的数据指令。
简单来说,如果 aof 文件里面存储了某个 key 的多次变更记录,但是实际上,
最终在做数据恢复的时候,只需要执行最新的指令操作就行了,历史的数据就没必要存在这个文件里面占空间。
AOF 文件重写的具体过程分为几步:
首先,根据当前 Redis 内存里面的数据,重新构建一个新的 AOF 文件
然后,读取当前 Redis 里面的数据,写入到新的 AOF 文件里面
最后,重写完成以后,用新的 AOF 文件覆盖现有的 AOF 文件
另外,因为 AOF 在重写的过程中需要读取当前内存里面所有的键值数据,再生成对应的一条指令进行保存。
而这个过程是比较耗时的,对业务会产生影响。
所以 Redis 把重写的过程放在一个后台子进程里面来完成,
这样一来,子进程在做重写的时候,主进程依然可以继续处理客户端请求。
最后,为了避免子进程在重写过程中,主进程的数据发生变化,
导致 AOF 文件和 Redis 内存中的数据不一致的问题,Redis 还做了一层优化。
就是子进程在重写的过程中,主进程的数据变更需要追加到 AOF 重写缓冲区里面。
等到 AOF 文件重写完成以后,再把 AOF 重写缓冲区里面的内容追加到新的 AOF 文件里面。

12.5 redis的内存淘汰算法和原理是什么

Redis 里面的内存淘汰策略,是指内存的使用率达到 上限的时候的一种内存释放的行为。
Redis 里面提供了很多中内存淘汰算法,归纳起来主要就四种

  1. Random 算法,随机移除某个 key
  2. TTL 算法 ,在设置了过期时间的键中,把更早过期时间的 key 有限移除
  3. LRU 算法,移除最近很少使用的 key
  4. LFU 算法,移除最近很少使用的 key
    LRU 是比较常见的一种内存淘汰算法,在 Redis 里面会维护一个大小为 16 的候选池,这个候选池里面的数据会根据时间进行排序,然后每一次随机取出 5 个 key 放入到这个候选池里面,当候选池满了以后,访问的时间间隔最大的 key 就会从候选池里面取出来淘汰掉。
    通过这样的一个设计,就可以把真实的最少访问的 key 从内存中淘汰掉。
    但是这样的一种 LRU 算法还是存在一个问题,假如一个 key 的访问频率很低,但是最近一次偶尔被访问到,那么 LRU 就会认为这是一个热点 Key,不会被淘汰。
    所以在 Redis4 里面,增加了一个 LFU 的算法,相比于 LRU,LFU 增加了访问频率这个维度来统计数据的热点情况。
    LFU 的主要设计是,使用了两个双向链表形成一个二维双向链表,一个用来保存访问频率,另一个用来保存访问频率相同的所有元素。
    当添加元素的时候,访问次数默认为 1,于是找到相同访问频次的节点,然后添加到相同频率节点对应的双向链表头部。
    当元素被访问的时候,就会增加对应 key 的访问频次,并且把当前访问的节点移动到下一个频次节点。
    有可能出现某个数据前期访问次数很多,然后后续就一直不用了,如果单纯按照访问频率,这个 key就很难被淘汰,所以在 LFU 中通过使用频率和上次访问时间来标记数据的热度,如果有读写,就增加访问频率,如果一段时间内没有读写,就减少访问频率。
    所以,通过 LFU 算法改进之后,就可以真正达到非热点数据的淘汰了。当然,LFU 也有缺点,相比 LRU算法,LFU 增加了访问频次的维护,以及实现的复杂度要比 LRU 更高。

12.6 谈谈你对时间轮的理解

时间轮,简单理解就是一种用来存储一系列定时任务的环状数组,它的整个工作原理和我们的钟表的表类似。
使用时间轮的方式来管理多个定时任务的好处有很多,我认为有两个核心原因:
 减少定时任务添加和删除的时间复杂度,提升性能。
 可保证每次执行定时器任务都是 O(1)复杂度,在定时器任务密集的情况下,性能优势非常明显。
当然,它也有缺点,对于执行时间非常严格的任务,时间轮不是很适合,因为时间轮算法的精度取决于最小时间单元的粒度。假设以 1s 为一个时间刻度,那小于 1s 的任务就无法被时间轮调度。
时间轮算法在很多地方都有用到,比如 Dubbo、Netty、Kafka 等。

12.7 Redis到底是多线程还是单线程

Redis 底层采用单线程实现数据的 IO,
从 Redis6.0 开始,在多路复用及层面增加了多线程的处理,来优化 IO 处理的能力。
不过,具体的数据操作仍然是由主线程来处理的,所以我们可以认为 Redis 对于数据 IO 的处理依然是单线程。从 CPU 层面来说,Redis 只需要采用单线程即可,原因有两个。
1, 如果采用多线程,对于 Redis 中的数据操作,都需要通过同步的方式来保证线程安全性,这反而会影响到 redis 的性能,
2,在 Linux 系统上 Redis 通过 pipelining 可以处理 100w 个请求每秒,而应用程序的计算复杂度主要是 O(N) 或 O(log(N)) ,不会消耗太多 CPU
从内存层面来说,Redis 本身就是一个内存数据库,内存的 IO 速度本身就很快,所以内存的瓶颈只是受限于内存大小。
最后,Redis 本身的数据结构也做了很多的优化,比如压缩表、跳跃表等方式降低了时间复杂度,同时还提供了不同时间复杂度的数据类型。

12.8 Rdeds存在线程安全问题吗

Redis Server 本身是一个线程安全的 K-V 数据库,也就是说在 Redis Server 上执行的指令,不需要任何同步机制,不会存在线程安全问题。
(如图)虽然 Redis 6.0 里面,增加了多线程的模型,但是增加的多线程只是用来处理网络 IO 事件,对于指令的执行过程,仍然是由主线程来处理,所以不会存在多个线程同时执行操作指令的情况。

12.9 Redis和mysql如何保证数据一致性

一般情况下,Redis 用来实现应用和数据库之间读操作的缓存层,主要目的是减少数据库 IO,还可以提升数据的 IO 性能, 这是它的整体架构。
当应用程序需要去读取某个数据的时候,首先会先尝试去 Redis 里面加载,如果命中就直接返回。如果没有命中,就从数据库查询,查询到数据后再把这个数据缓存到 Redis 里面。
在这里插入图片描述

在这样一个架构中,会出现一个问题,就是一份数据,同时保存在数据库和 Redis 里面,当数据发生变化的时候,需要同时更新 Redis 和 Mysql,由于更新是有先后顺序的,并且它不像 Mysql中的多表事务操作,可以满足 ACID 特性。所以就会出现数据一致性问题。
在这种情况下,能够选择的方法只有几种。

  1. 先更新数据库,再更新缓存
  2. 先删除缓存,再更新数据库
    如果先更新数据库,再更新缓存,如果缓存更新失败,就会导致数据库和 Redis 中的数据不一致。
    如果是先删除缓存,再更新数据库,理想情况是应用下次访问 Redis 的时候,发现 Redis 里面的数据是空的,就从数据库加载保存到 Redis 里面,那么数据是一致的。但是在极端情况下,由于删除 Redis和更新数据库这两个操作并不是原子的,所以这个过程如果有其他线程来访问,还是会存在数据不一致问题。
    在这里插入图片描述
    所以,如果需要在极端情况下仍然保证 Redis 和 Mysql 的数据一致性,就只能采用最终一致性方案。
    (如图)比如基于 RocketMQ 的可靠性消息通信,来实现最终一致性。
    在这里插入图片描述

(如图)还可以直接通过 Canal 组件,监控 Mysql 中 binlog 的日志,把更新后的数据同步到 Redis里面。
在这里插入图片描述
因为这里是基于最终一致性来实现的,如果业务场景不能接受数据的短期不一致性,那就不能使用这个方案来做。

谈谈你对Elasicsearch的理解

它是建立在全文搜索引擎库 Apache Lucene 基础之上的一个开源的搜索
引擎,也可以作为 NoSQL 数据库,存储任意格式的文档和数据。也可以做大数据的分析,是一个跨界开源产品。
它最主要的应用场景是 ELK 的日志分析系统。
另外它还有以下特点:

  1. 第一,采用 Master-slave 架构,实现数据的分片和备份
  2. 第二,使用 Java 编写,并对 Lucene 进行封装,隐藏了 Lucene 的复杂性
  3. 第三,能胜任上百个服务节点的扩展,并支持 PB 级别的结构化或者非结构化数据
  4. 第四,ES 提供的 Restful API,不仅简化了 ES 的操作,还支持任何语言的客户端提供 API 接口,
    另外 Restful API 的风格还实现了 CURD 操作、创建索引,删除索引等功能。
    在 ES 的使用上我也有一些经验心得,比如:
  5. es 里面复杂的关联查询尽量别用,一旦用了性能都不太好。最好是先在 Java 系统里就完成关联,将关联好的数据直接写入 ES 中
  6. 避免一些太复杂的操作,比如 join/nested/parent-child 搜索,不然容易出现性能问题。
  7. 避免深分页查询,ES 集群的分页查询支持 from 和 size 参数,查询的时候,每个分片必须构造一个长度为 from+size 的优先队列,然后回传到网关节点,网关节点再对这些优先队列进行排序找到正确的 size 文档。当 from 足够大的时候,就算不发生 OOM,也会影响到 CPU 和带宽等,从而影响到整个集群的性能。

详细描述一下 Elasticsearch 索引文档的过程

第一步:客户端集群某节点写入数据,发送请求。
第二步: 节点 1 接收到请求后 ,使用文档_id 来确定文档属于分片 0。请求会被转到另外的节点 ,假定节点 3。 因此分片 0 的主分片分配到节点 3 上。
第三步: 节点 3 在主分片上执行写操作 , 如果成功 , 则将请求并行转发到节点 1 和节 点 2 的副本分片上 ,等待结果返回。所有的副本分片都报告成功 , 节点 3 将向协调节点 ( 节点 1 )报告成功 , 节点 1 向请求客户端报告写入成功。
在这里插入图片描述

详细描述一下 Elasticsearch 搜索的过程?

1、搜索被执行成一个两阶段过程 ,我们称之为 Query Then Fetch ;
2、在初始查询阶段时,查询会广播到索引中每一个分片拷贝( 主分片或者副本分片) 。 每 个分片在本地执行搜索并构建一个匹配文档的大小为 from + size 的优先队列。
PS :在 搜 索 的 时 候 是 会 查 询 Filesystem Cache 的 ,但 是 有 部 分 数 据 还 在Memory Buffer ,所以搜索是近实时的。
3、每个分片返回各自优先队列中 所有文档的 ID 和排序值 给协调节点,它合并这些值到自己的优先队列中来产生一个全局排序后的结果列表。
4、接下来就是 取回阶段 , 协调节点辨别出哪些文档需要被取回并向相关的分片提交多 个 GET 请求。每个分片加载并丰富文档 , 如果有需要的话 , 接着返回文档给协调节点。一旦所有的文档都被取回了 ,协调节点返回结果给客户端。

  1. 补充 : Query Then Fetch 的搜索类型在文档相关性打分的时候参考的是本分片 的数据,这样在文档数量较少的时候可能不够准确,DFS Query Then Fetch 增加 了一 个预查询的处理,询问 Term 和 Document frequency ,这个评分更准确,但是性能会变差 。

Elasticsearch 对于大数据量(上亿量级)的聚合如何实现?

Elasticsearch 提供的首个近似聚合是 cardinality 度量。
它提供一个字段的基数 , 即该字 段的 distinct 或者 unique 值的数目。 它是基于 HLL 算法的。
HLL 会先对我们的输入做哈 希运算,然后根据哈希运算的结果中的 bits 做概率估算从而得到基数。
其特点是: 可配置 的精度, 用来控制内存的使用( 更精确=更多内存); 小的数据集精度是非常高的;我 们可以通过配置参数 ,来设置去重需要的固定内存使用量。无论数千还是数十亿的唯一值,内存使用量只与你配置的精确度相关。 在并发情况下 ,

Elasticsearch 如果保证读写一致?

1、可以通过版本号使用乐观并发控制 , 以确保新版本不会被旧版本覆盖,由应用层来处理具体的冲突;
2、另外对于写操作 , 一致性级别支持 quorum/one/all , 默认为 quorum , 即只有当 大多数分片可用时才允许写操作。但即使大多数可用 , 也可能存在因为网络等原因导致写入副本失败 ,这样该副本被认为故障 ,分片将会在一个不同的节点上重建。
3、对于读操作 , 可以设置 replication 为 sync(默认) , 这使得操作在主分片和副本分片 都完成后才会返回;如果设置 replication 为 async 时,也可以通过设置搜索请求参数 _preference 为primary 来查询主分片 ,确保文档是最新版本。

如何监控 Elasticsearch 集群状态?

Marvel 让你可以很简单地通过 Kibana 监控 Elasticsearch。你可以实时查看你的集群健 康状态和性能 ,也可以分析过去的集群、索引和节点指标。

  • 13
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值