Bootstrap

MySql总结

MySql总结

SQL优化技巧

  1. 创建索引
  2. 避免索引失效
  3. 锁粒度
  4. 分页查询优化
  5. 分析sql查询执行计划
  6. show profile 分析SQL的执行能力

1.数据库存储引擎

1.1MyIsam:

MyISAM提供了大量的特性,包括全文索引、压缩、空间函数(GIS)等,但MyISAM 不支持事务、行级锁、外键 ,有一个毫无疑问的缺陷就是 崩溃后无法安全恢复 。 5.5之前默认的存储引擎

  • 优势是访问的 速度快 ,对事务完整性没有要求或者以**SELECT、INSERT为主的应用针对数据统计有额外的常数存储**。故而 count(*) 的查询效率很高

  • 数据文件结构:(在《第02章_MySQL数据目录》章节已讲)

    • 表名.frm 存储表结构

    • 表名.MYD 存储数据 (MYData)

    • 表名.MYI 存储索引 (MYIndex)

  • 应用场景:只读应用或者以读为主的业务

1.2InnoDB:
  • MySQL从3.23.34a开始就包含InnoDB存储引擎。 大于等于5.5之后,默认采用InnoDB引擎 。

  • InnoDB是MySQL的 默认事务型引擎 ,它被设计用来处理大量的短期(short-lived)事务。可以确保事务的完整提交(Commit)和回滚(Rollback)。

  • 除了增加和查询外,还需要更新、删除操作,那么,应优先选择InnoDB存储引擎

  • 除非有非常特别的原因需要使用其他的存储引擎,否则应该优先考虑InnoDB引擎。

  • 数据文件结构:(在《第02章_MySQL数据目录》章节已讲)

    • 表名.frm 存储表结构(MySQL8.0时,合并在表名.ibd中)

    • 表名.ibd 存储数据和索引

  • InnoDB是 为处理巨大数据量的最大性能设计 。

  • 在以前的版本中,字典数据以元数据文件、非事务表等来存储。现在这些元数据文件被删除了。比如: .frm , .par , .trn , .isl , .db.opt 等都在MySQL8.0中不存在了。

  • 对比MyISAM的存储引擎, InnoDB写的处理效率差一些 ,并且会占用更多的磁盘空间以保存数据和索引

  • MyISAM只缓存索引,不缓存真实数据;InnoDB不仅缓存索引还要缓存真实数据, 对内存要求较高 ,而且内存大小对性能有决定性的影响。

1.3Memory 引擎置于内存的表
  • Memory采用的逻辑介质是 内存响应速度很快 ,但是当mysqld守护进程崩溃的时候 数据会丢失 。另外,要求存储的数据是数据长度不变的格式,比如,Blob和Text类型的数据不可用(长度不固定的)。

  • 主要特征:

  • Memory同时 支持哈希(HASH)索引B+树索引

  • Memory表至少比MyISAM表要 快一个数量级 。

  • 缺点:其数据易丢失,生命周期短。基于这个缺陷,选择MEMORY存储引擎时需要特别小心。

  • 使用Memory存储引擎的场景:

    1. 目标数据比较小 ,而且非常 频繁的进行访问 ,在内存中存放数据,如果太大的数据会造成 内存溢出 。可以通过参数 max_heap_table_size 控制Memory表的大小,限制Memory表的最大的大小。
    2. 如果 数据是临时的 ,而且 必须立即可用 得到,那么就可以放在内存中。
    3. 存储在Memory表中的数据如果突然间 丢失的话也没有太大的关系 。
1.4其他引擎

CSV 引擎:

Archive引擎:用于数据存档

Federated:访问远程表

  • Federated引擎是访问其他MySQL服务器的一个 代理 ,尽管该引擎看起来提供了一种很好的 跨服务器的灵活性 ,但也经常带来问题,因此 默认是禁用的 。
1.5MyISAM和InnoDB对比

MysQL5.5之前的默认存储引擎是MylSAM,5.5之后改为了InnoDB。

  • 首先对于InnoDB存储引擎,提供了良好的事务管理崩溃修复能力和并发控制。因为InnoDB存储引擎支持事务,所以对于要求事务完整性的场合需要选择InnoDB,比如数据操作除了插入和查询以外还包含有很多更新、删除操作,像财务系统等对数据准确性要求较高的系统。缺点是其读写效率稍差,占用的数据空间相对比较大

  • 其次对于MyISAM存储引擎,如果是小型应用,系统以读操作和插入操作为主只有很少的更新、删除操作,并且对事务的要求没有那么高,则可以选择这个存储引擎。MyISAM存储引擎的优势在于占用空间小,处理速度快;缺点是不支持事务的完整性和并发性。这两种引擎各有特点,当然你也可以在MySQL中,针对不同的数据表,可以选择不同的存储引擎。

1.6索引对比:

① 在InnoDB存储引擎中,我们只需要根据主键值对 聚簇索引 进行一次查找就能找到对应的记录,而在MyISAM 中却需要**进行一次 回表** 操作,意味着MyISAM中建立的索引相当于全部都是 二级索引 。

② InnoDB的数据文件本身就是索引文件,而MyISAM索引文件和数据文件是 分离的 ,索引文件仅保存数据记录的地址

③ InnoDB的非聚簇索引data域存储相应记录 主键的值 ,而MyISAM索引记录的是 地址 。换句话说,InnoDB的所有非聚簇索引都引用主键作为data域。

④ MyISAM的回表操作是十分 快速 的,因为是拿着地址偏移量直接到文件中取数据的,反观InnoDB是通过获取主键之后再去聚簇索引里找记录,虽然说也不慢,但还是比不上直接用地址去访问

⑤ InnoDB要求表 必须有主键 ( MyISAM可以没有 )。如果没有显式指定,则MySQL系统会自动选择一个可以非空且唯一标识数据记录的列作为主键。如果不存在这种列,则MySQL自动为InnoDB表生成一个隐含字段作为主键,这个字段长度为6个字节,类型为长整型。

InnDB:

  • 插入速度严重依赖于插入顺序 ,按照主键的顺序插入是最快的方式,否则将会出现页分裂,严重影响性能。因此,对于InnoDB表,我们一般都会定义一个自增的ID列为主键更新主键的代价很高 ,因为将会导致被更新的行移动。因此,对于InnoDB表,我们一般定义主键为不可更新
  • 不建议使用过长的字段作为主键,因为所有二级索引都引用主键索引,过长的主键索引会令二级索引变得过大。
1.7索引失效案例:
  1. 计算函数类型转换(自动或手动)导致索引失效

  2. 范围条件右边的列索引失效

  3. 不等于**(!=** 或者==<>==)索引失效

  4. is null可以使用索引,==is not null==无法使用索引

  5. like以通配符==%开头==索引失效

  6. ==OR 前后==存在非索引的列,索引失效

  7. 数据库和表的字符集统一使用utf8mb4

    统一使用utf8mb4( 5.5.3版本以上支持)兼容性更好,统一字符集可以避免由于字符集转换产生的乱码。不同的 字符集 进行比较前需要进行 转换 会造成索引失效

1.8关联查询优化:
  • LEFT JOIN 时,选择小表作为驱动表, 大表作为被驱动表 。减少外层循环的次数。(小表驱动大表尽量外表有索引

  • 保证被驱动表的JOIN字段已经创建了索引

  • 需要JOIN 的字段,数据类型保持绝对一致,避免索引失效。

  • INNER JOIN 时,MySQL会自动将 小结果集的表选为驱动表 。选择相信MySQL优化策略。

  • 能够直接多表关联的尽量直接关联,不用子查询。(减少查询的趟数)

  • 不建议使用子查询,建议将子查询SQL拆开结合程序多次查询,或使用 JOIN 来代替子查询。衍生表建不了索引

1.9子查询优化

子查询是 MySQL 的一项重要的功能,可以帮助我们通过一个 SQL 语句实现比较复杂的查询。但是,子查询的执行效率不高。原因:

  • ① 执行子查询时,MySQL需要为内层查询语句的查询结果 建立一个临时表 ,然后外层查询语句从临时表中查询记录。查询完毕后,再 撤销这些临时表 。这样会消耗==过多的CPU和IO资源==,产生大量的慢查询。

  • ② 子查询的结果集存储的临时表,不论是内存临时表还是磁盘临时表都 不会存在索引 ,所以查询性能会受到一定的影响。

  • ③ 对于返回结果集比较大的子查询,其对查询性能的影响也就越大。

结论:

在MySQL中,可以使用==连接(JOIN)查询来替代子查询。连接查询 不需要建立临时表 ,其 速度比子查询要快 ,如果查询中使用索引的话,性能就会更好。
结论:尽量不要使用
NOT IN 或者 NOT EXISTS==,用LEFT JOIN xxx ON xx WHERE xx IS NULL替代

1.10排序优化

优化建议:

  1. SQL 中,可以在 WHERE 子句和 ORDER BY 子句中使用索引,目的是在 WHERE 子句中 避免全表扫描 ,在 ORDER BY 子句 避免使用 FileSort 排序 。当然,某些情况下全表扫描,或者 FileSort 排序不一定比索引慢。但总的来说,我们还是要避免,以提高查询效率。
  2. 尽量使用 Index 完成 ORDER BY 排序。如果 WHERE 和 ORDER BY 后面是相同的列就使用单索引列;如果不同就使用联合索引。
  3. 无法使用 Index 时,需要对 FileSort 方式进行调优。

双路排序 (慢)

MySQL 4.1之前是使用双路排序 ,字面意思就是两次扫描磁盘,最终得到数据, 读取行指针和order by列 ,对他们进行排序,然后扫描已经排序好的列表,按照列表中的值重新从列表中读取对应的数据输出从磁盘取排序字段,在buffer进行排序,再从 磁盘取其他字段 。取一批数据,要对磁盘进行两次扫描,众所周知,IO是很耗时的,所以在mysql4.1之后,出现了第二种改进的算法,就是单路排序。

单路排序 (快)

从磁盘读取查询需要的 所有列 ,按照order by列在buffer对它们进行排序,然后扫描排序后的列表进行输出, 它的效率更快一些,避免了第二次读取数据。并且把随机IO变成了顺序IO,但是它会使用更多的空间, 因为它把每一行都保存在内存中了。

优化策略

  1. 尝试提高 sort_buffer_size
  2. 尝试提高 max_length_for_sort_data
  3. Order by 时select * 是一个大忌。最好只Query需要的字段。
1.11group by优化
  1. group by 使用索引的原则几乎跟order by一致 ,group by 即使没有过滤条件用到索引,也可以直接使用索引。
  2. group by 先排序再分组,遵照索引建的最佳左前缀法则
  3. 当无法使用索引列,增大 max_length_for_sort_datasort_buffer_size 参数的设置
  4. where效率高于having,能写在where限定的条件就不要写在having中了减少使用order by,和业务沟通能不排序就不排序,或将排序放到程序端去做**Order by、groupby、distinct**这些语句较为耗费CPU,数据库的CPU资源是极其宝贵的。包含了order by、group by、distinct这些查询的语句,where条件过滤出来的结果集请保持在1000行以内,否则SQL会很慢。
1.12优先考虑覆盖索引

**简单说就是, **索引列+主键 包含 SELECT 到 FROM之间查询的列 。

理解方式一

  • 索引是高效找到行的一个方法,但是一般数据库也能使用索引找到一个列的数据,因此它不必读取整个行。毕竟索引叶子节点存储了它们索引的数据;当能通过读取索引就可以得到想要的数据,那就不需要读取行了。一个索引包含了满足查询结果的数据就叫做覆盖索引。

理解方式二

  • 非聚簇复合索引的一种形式,它包括在查询里的SELECT、JOIN和WHERE子句用到的所有列(即建索引的字段正好是覆盖查询条件中所涉及的字段)。

好处:

  1. 避免Innodb表进行索引的**二次查询**(回表)
  2. 可以把随机IO变成顺序IO加快查询效率
1.13前缀索引
mysql> alter table teacher add index index1(email);
#或
mysql> alter table teacher add index index2(email(6));

也就是说**使用前缀索引,定义好长度,就可以做到既节省空间,又不用额外增加太多的查询成本。**前面已经讲过区分度,区分度越高越好。因为区分度越高,意味着重复的键值越少。

1.14索引下推

ICP(Index Condition Pushdown)是在MySQL 5.6版本上推出的查询优化策略,把本来由Server层做的索引条件检查下推给存储引擎层来做,以降低回表和访问存储引擎的次数,提高查询效率。和之前 讲过的like“%xxxx”以%开头的模糊查询失效不完全正确。当使用索引下推的时候会用到。

索引下推:在多级索引中,对使用了索引的列==会先进行==where判断操作,而不需用回表后在去server进行where操作判断。减少了回表的次数,提高查询效率。

① 只能用于二级索引(secondary index)
②explain显示的执行计划中type值(join 类型)为 range 、 ref 、 eq_ref 或者 ref_or_null 。
③ 并非全部where条件都可以用ICP筛选,如果where条件的字段不在索引列中,还是要读取整表的记录到server端做where过滤。
④ ICP可以用于MyISAM和InnnoDB存储引擎
⑤ MySQL 5.6版本的不支持分区表的ICP功能,5.7版本的开始支持
⑥ 当SQL使用覆盖索引时,不支持ICP优化方法

1.15普通索引和唯一索引 的使用场景
  1. 查询上的差别:
    严格来说,唯一索引的搜索过程比较快,因为"回表"的数据记录少,但是在实际操作中,这两种索引的带来的消耗几乎相等,差别微乎其微。
  2. 更新上的区别:
    • 普通索引的更新,会使用到change buffer
    • 唯一索引的更新,不会使用到change buffer;
change buffer

当我们需要更新一条数据记录的时候,如果这个记录所在的数据页A在内存中,那么直接更新内存中的数据页A即可,如果数**据页B不在内存中**,这个时候,Innodb会将这些更新操作缓存在change buffer中,当下次需要访问磁盘上的数据页B时,将数据页B从磁盘上加载到内存里面,然后应用change buffer中与这个数据页有关的操作.

优点:

  • 第一、将原本2次的磁盘访问,整合成1次磁盘访问,并且能够保证数据的一致性。
  • 第二、数据页B读入内存是需要占用内存空间的,这种方式能够避免内存的使用,提高内存的利用率

change buffer使用场景

在一些**写多读少的业务中**,change buffer能够发挥很好的作用。它可以将==多次对磁盘的操作,合并成一次merge操作==,从而提高MySQL的性能,一次性merge的操作越多,收益就越大。

但是需要注意,如果你的数据写入之后。立马会读取(也就意味着需要从磁盘读取到内存),那么建议不要使用change buffer,因为在这种情况下,使用change buffer不会减低IO次数,反而==多了change buffer的维护开销==。

哪些情况适合创建索引:
  1. 字段的数值有唯一性的限制
  2. 频繁作为 WHERE 查询条件的字段
  3. 经常 GROUP BYORDER BY 的列
  4. UPDATE、DELETE 的 WHERE 条件列
  5. .DISTINCT 字段需要创建索引
  6. 使用字符串前缀创建索引
  7. 使用最频繁的列放到联合索引的左侧
  8. 在多个字段都要创建索引的情况下,联合索引优于单值索引
  9. 限制索引的数目
哪些情况不适合创建索引:
  1. 大量重复数据的列上不要建立索引
  2. 避免对经常更新的表创建过多的索引
  3. 删除不再使用或者很少使用的索引

3.SQL执行流程

图解

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t2oTz9BH-1676344835170)(MySql总结.assets/image-20221019163226990.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8HRjPWA6-1676344835171)(MySql总结.assets/image-20221020102124868.png)]
第1层:连接层

​ 系统(客户端)访问 MySQL 服务器前,做的第一件事就是建立 TCP 连接。

第2层:服务层
  • SQL Interface: SQL接口:

    • 接收用户的SQL命令,并且返回用户需要查询的结果。比如SELECT … FROM就是调用SQLInterface
  • Parser: 解析器

    • 在解析器中对 SQL 语句进行**语法分析、语义分析**。将SQL语句分解成数据结构,并将这个结构传递到后续步骤,以后SQL语句的传递和处理就是基于这个结构的。如果在分解构成中遇到错误,那么就说明这个SQL语句是不合理的。
    • 在SQL命令传递到解析器的时候会被解析器验证和解析,并为其创建 语法树 ,并根据数据字典丰富查询语法树,会验证该客户端是否具有执行该查询的权限 。创建好语法树后,MySQL还会对SQl查询进行语法上的优化,进行查询重写。
  • Optimizer: 查询优化器

  • SQL语句在语法解析之后、查询之前会使用查询优化器确定 SQL 语句的执行路径,生成一个执行计划 。

  • 这个执行计划表明应该 使用哪些索引 进行查询(全表检索还是使用索引检索),表之间的连接顺序如何,最后会按照执行计划中的步骤调用存储引擎提供的方法来真正的执行查询,并将查询结果返回给用户。

  • Caches & Buffers: 查询缓存组件:

    • MySQL内部维持着一些Cache和Buffer,比如Query Cache用来缓存一条SELECT语句的执行结果,如果能够在其中找到对应的查询结果,那么就不必再进行查询解析、优化和执行的整个过程了,直接将结果反馈给客户端。

    • 这个缓存机制是由一系列小缓存组成的。比如表缓存,记录缓存,key缓存,权限缓存等 。

    • 这个查询缓存可以在 不同客户端之间共享 。

    • 从MySQL 5.7.20开始,不推荐使用查询缓存,并在 MySQL 8.0中删除 。

第3层:引擎层:

​ 服务器通过API与存储引擎进行通信。不同的存储引擎具有的功能不同,这样我们可以根据自己的实际需要进行选取。

存储层:

​ 所有的数据,数据库、表的定义,表的每一行的内容,索引,都是存在 文件系统 上,以 文件 的方式存在的,并完成与存储引擎的交互

性能分析工具

1.1统计SQL查询成本:last_query_cost
  • [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qjiKjTMN-1676344835172)(MySql总结.assets/image-20221025154125582.png)]

    Value指页的数量

    **使用场景:**它对于比较开销是非常有用的,特别是我们有好几种查询方式可选的时候。

1.2定位执行慢的 SQL:慢查询日志
1. 慢查询基本配置
  • slow_query_log 启动停止技术慢查询日志
  • slow_query_log_file 指定慢查询日志得存储路径及文件(默认和数据文件放一起)
  • long_query_time 指定记录慢查询日志SQL执行时间得伐值(单位:秒,默认10秒)
  • log_queries_not_using_indexes 是否记录未使用索引的SQL
  • log_output 日志存放的地方【TABLE】【FILE】【FILE,TABLE】
  1. 开启slow_query_log
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RhcmNO5E-1676344835172)(MySql总结.assets/image-20221025154530455.png)]

  2. 查看慢查询数目
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yt4JuoME-1676344835172)(MySql总结.assets/image-20221025154627299.png)]

  3. [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ctOuzHi0-1676344835173)(MySql总结.assets/image-20221025154757149.png)]

  4. 慢查询日志分析工具:mysqldumpslow

  5. mysqldumpslow 命令的具体参数如下:

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gnoeuj60-1676344835173)(MySql总结.assets/image-20221028094156623.png)]#得到返回记录集最多的10个SQL

    mysqldumpslow -s r -t 10 /var/lib/mysql/atguigu-slow.log

    #得到访问次数最多的10个SQL

    mysqldumpslow -s c -t 10 /var/lib/mysql/atguigu-slow.log

    #得到按照时间排序的前10条里面含有左连接的查询语句

    mysqldumpslow -s t -t 10 -g “left join” /var/lib/mysql/atguigu-slow.log

    #另外建议在使用这些命令时结合 | 和more 使用 ,否则有可能出现爆屏情况

    mysqldumpslow -s r -t 10 /var/lib/mysql/atguigu-slow.log | more

1.3查看 SQL 执行成本:SHOW PROFILE

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WOXlNnxS-1676344835174)(MySql总结.assets/image-20221028094705512.png)]

 show profile cpu,block io for query 2;
1.4分析查询语句:EXPLAIN

如果我们想看看某个查询的执行计划的话,可以在具体的查询语句前边加一个 EXPLAIN ,就像这样:

EXPLAIN SELECT 1;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PHdHk8wn-1676344835174)(MySql总结.assets/image-20221031110140317.png)]

type:

  • all:
    全表扫描
  • index:
    这种连接类型只是另外一种形式的全表扫描,只不过它的扫描顺序是按照索引的顺序.
  • range:
    range指的是有范围的索引扫描,相对于index的全索引扫描,它有范围限制,因此要优于index。
  • ref:
    出现该连接类型的条件是: 查找条件列使用了索引而且不为主键和unique。其实,意思就是虽然使用了索引,但该索引列的值并不唯一,有重复。这样即使使用索引快速查找到了第一条数据,仍然不能停止,要进行目标值附近的小范围扫描。
  • ref_eq:
    ref_eq 与 ref相比牛的地方是,它知道这种类型的查找结果集只有一个?什么情况下结果集只有一个呢!那便是使用了主键或者唯一性索引进行查找的情况,比如根据学号查找某一学校的一名同学,在没有查找前我们就知道结果一定只有一个,所以当我们首次查找到这个学号,便立即停止了查询。
  • const:
    通常情况下,如果将一个主键放置到where后面作为条件查询,mysql优化器就能把这次查询优化转化为一个常量。至于如何转化以及何时转化,这个取决于优化器。

事物

1.1事物的4大特性
  • 原子性atomicity):
    原子性是指事务是一个不可分割的工作单位,要么全部提交,要么全部失败回滚

  • 一致性(consistency)
    根据定义,一致性是指事务执行前后,数据从一个 合法性状态 变换到另外一个 合法性状态 。这种状态是 语义上 的而不是语法上的,跟具体的业务有关。那什么是合法的数据状态呢?满足 预定的约束 的状态就叫做合法的状态。通俗一点,这状态是由你自己来定义的(比如满足现实世界中的约束)。满足这个状态,数据就是一致的,不满足这个状态,数据就是不一致的!如果事务中的某个操作失败了,系统就会自动撤销当前正在执行的事务,返回到事务操作之前的状态。

  • 隔离型(isolation):
    事务的隔离性是指一个事务的执行 不能被其他事务干扰 ,即一个事务内部的操作及使用的数据对 并发 的其他事务是隔离的,并发执行的==各个事务之间不能互相干扰。==

  • 持久性(durability):
    持久性是指一个==事务一旦被提交,它对数据库中数据的改变就是 永久性的== ,接下来的其他操作和数据库故障不应该对其有任何影响。持久性是通过 事务日志 来保证的。日志包括了 重做日志 和 回滚日志 。当我们通过事务对数据进行修改的时候,首先会将数据库的变化信息记录到重做日志中,然后再对数据库中对应的行进行修改。这样做的好处是,即使数据库系统崩溃,数据库重启后也能找到没有更新到数据库系统中的重做日志,重新执行,从而使事务具有持久性。

2.1数据并发问题
  1. 脏写( Dirty Write )

    对于两个事务 Session A、Session B,如果事务Session A 修改了 另一个 未提交 事务Session B 修改过 的数据,那就意味着发生了 脏写

  2. 脏读( Dirty Read )

    对于两个事务 Session A、Session B,Session A 读取 了已经被 Session B 更新 但还 没有被提交 的字段。之后若 Session B 回滚 ,Session A 读取 的内容就是 临时且无效 的

  3. . 不可重复读( Non-Repeatable Read )

    对于两个事务Session A、Session B,Session A 读取 了一个字段,然后 Session B 更新 了该字段。 之后Session A 再次读取 同一个字段, 值就不同 了。那就意味着发生了不可重复读。

  4. . 幻读( Phantom )

    对于两个事务Session A、Session B, Session A 从一个表中 读取 了一个字段, 然后 Session B 在该表中 插入 了一些新的行。 之后, 如果 Session A 再次读取 同一个表, 就会多出几行。那就意味着发生了幻读

2.2SQL中的四种隔离级别

MySQL的默认隔离级别为REPEATABLE READ,我们可以手动修改一下事务的隔离级别

脏写 > 脏读 > 不可重复读 > 幻读

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ey2PVD75-1676344835175)(MySql总结.assets/image-20221103094600187.png)]

3.0事物日志
  • 事务的隔离性由 锁机制 实现。
    • 而事务的原子性、一致性和持久性由事务的 redo 日志和undo 日志来保证。
    • REDO LOG 称为 重做日志 ,提供再写入操作,恢复提交事务修改的页操作,用来保证事务的持久性
    • UNDO LOG 称为 回滚日志 ,回滚行记录到某个特定版本,用来保证事务的原子性、一致性
  • 有的DBA或许会认为 UNDO 是 REDO 的逆过程,其实不然。
3.1redo日志

一方面,缓冲池可以帮助我们消除CPU和磁盘之间的鸿沟,checkpoint机制可以保证数据的最终落盘,然 而由于checkpoint 并不是每次变更的时候就触发 的,而是master线程隔一段时间去处理的。所以最坏的情 况就是事务提交后,刚写完缓冲池,数据库宕机了,那么这段数据就是丢失的,无法恢复。 另一方面,事务包含 持久性 的特性,就是说对于一个已经提交的事务,在事务提交后即使系统发生了崩 溃,这个事务对数据库中所做的更改也不能丢失。 那么如何保证这个持久性呢? 一个简单的做法 :在事务提交完成之前把该事务所修改的所有页面都刷新 到磁盘,但是这个简单粗暴的做法有些问题

另一个解决的思路 :我们只是想让已经提交了的事务对数据库中数据所做的修改永久生效,即使后来系 统崩溃,在重启后也能把这种修改恢复出来。所以我们其实没有必要在每次事务提交时就把该事务在内 存中修改过的全部页面刷新到磁盘,只需要把 修改 了哪些东西 记录一下 就好。比如,某个事务将系统 表空间中 第10号 页面中偏移量为 100 处的那个字节的值 1 改成 2 。我们只需要记录一下:将第0号表 空间的10号页面的偏移量为100处的值更新为 2 。

好处 :

  • redo日志降低了刷盘频率
  • redo日志占用的空间非常小

特点

  • redo日志是顺序写入磁盘的
  • 事务执行过程中,redo log不断记录

Redo log可以简单分为以下两个部分:

  • 重做日志的缓冲 (redo log buffer) ,保存在内存中,是易失的。

数设置:innodb_log_buffer_size: redo log buffer 大小,默认 16M ,最大值是4096M,最小值为1M。

  • 重做日志文件 (redo log file) ,保存在硬盘中,是持久的。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VBWt9QJG-1676344835175)(MySql总结.assets/image-20230213101319637.png)]

第1步:先将原始数据从磁盘中读入内存中来,修改数据的内存拷贝

第2步:生成一条重做日志并写入redo log buffer,记录的是数据被修改后的值

第3步:当事务commit时,对 redo log file采用追加写的方式,将redo log buffer中的内容刷新到 redo log file,

第4步:定期将内存中修改的数据刷新到磁盘中

体会:
Write-Ahead Log(预先日志持久化):在持久化一个数据页之前,先将内存中相应的日志页持久化。
redo log buffer缓存策略

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PInXTecM-1676344835175)(MySql总结.assets/image-20221103104545800.png)]

针对这种情况,InnoDB给出 innodb_flush_log_at_trx_commit 参数,该参数控制 commit提交事务时,如何将 redo log buffer 中的日志刷新到 redo log file 中。它支持三种策略:

设置为0 :表示每次事务提交时不进行刷盘操作。(系统默认master thread每隔1s进行一次重做日志的同步

设置为1 :表示每次事务提交时都将进行同步,刷盘操作( 默认值

设置为2 :表示每次事务提交时都只把 redo log buffer 内容写入 page cache,不进行同步。由os自己决定什么时候同步到磁盘文件。

3.2. Undo日志

redo log是事务持久性的保证,undo log是事务原子性的保证。在事务中 更新数据 的 前置操作 其实是要先写入一个 undo log 。

事务需要保证 原子性 ,也就是事务中的操作要么全部完成,要么什么也不做。但有时候事务执行到一半会出现一些情况,比如:

  • 情况一:事务执行过程中可能遇到各种错误,比如 服务器本身的错误 , 操作系统错误 ,甚至是突然 断电 导致的错误。
  • 情况二:程序员可以在事务执行过程中手动输入 ROLLBACK 语句结束当前事务的执行。以上情况出现,我们需要把数据改回原先的样子,这个过程称之为 回滚 ,这样就可以造成一个假象:这个事务看起来什么都没做,所以符合 原子性 要求。

Undo日志的作用

作用1:回滚数据

作用2:MVCC

undo log的删除
  • 针对于insert undo log

    因为insert操作的记录,只对事务本身可见,对其他事务不可见。故该undo log可以在事务提交后直接删除,不需要进行purge操作。

  • 针对于update undo log

    该undo log可能需要提供MVCC机制,因此不能在事务提交时就进行删除。提交时放入undo log链表,等待purge线程进行最后的删除。

在InnoDB存储引擎中,undo log分为:

  • insert undo log
  • update undo log

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lsEomQ1n-1676344835176)(MySql总结.assets/image-20221103110120875.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aXgeeUc6-1676344835176)(MySql总结.assets/image-20230213104440103.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U3NkOzxm-1676344835176)(MySql总结.assets/image-20221103110403576.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YAwsOadR-1676344835177)(MySql总结.assets/image-20230213104431163.png)]

1.1并发问题的解决方案

怎么解决 脏读 、 不可重复读 、 幻读 这些问题呢?其实有两种可选的解决方案:

  • 方案一:读操作利用多版本并发控制( MVCC ,下章讲解),写操作进行 加锁 。

    普通的SELECT语句在READ COMMITTEDREPEATABLE READ隔离级别下会使用到MVCC读取记录。在 READ COMMITTED 隔离级别下,一个事务在执行过程中每次执行SELECT操作时都会生成一个ReadView,ReadView的存在本身就保证了 事务不可以读取到未提交的事务所做的更改 ,也就是避免了脏读现象;在 REPEATABLE READ 隔离级别下,一个事务在执行过程中只有 第一次执行SELECT操作 才会生成一个ReadView,之后的SELECT操作都 复用 这ReadView,这样也就避免了不可重复读和幻读的问题。

  • 方案二:读、写操作都采用 加锁 的方式。

小结对比发现:

  • 采用 MVCC 方式的话, 读-写 操作彼此并不冲突, 性能更高 。
  • 采用 加锁 方式的话, 读-写 操作彼此需要 排队执行 ,影响性能
  • 一般情况下我们当然愿意采用 MVCC 来解决 读-写 操作并发执行的问题,但是业务在某些特殊情况下,要求必须采用 加锁 的方式执行。
2.锁的不同角度分类
2.1从数据操作的类型划分:读锁、写锁
  • 读锁 :

    也称为 共享锁 、英文用 S 表示。针对同一份数据,多个事务的读操作可以同时进行而不会互相影响,相互不阻塞的。

  • 写锁 :

    也称为 排他锁 、英文用 X 表示。当前写操作没有完成前,它会阻断其他写锁和读锁。这样就能确保在给定的时间里,只有一个事务能执行写入,并防止其他用户读取正在写入的同一资源

2.2从数据操作的粒度划分:表级锁、页级锁、行锁
1. 表锁(Table Lock)

表级别的S锁、X锁

意向锁 (intention lock)

InnoDB 支持 多粒度锁(multiple granularity locking) ,它允许 行级锁 与 表级锁 共存,而意向锁就是其中的一种 表锁

意向锁分为两种:

  • 意向共享锁(intention shared lock, IS):事务有意向对表中的某些行加共享锁(S锁)
-- 事务要获取某些行的 S 锁,必须先获得表的 IS 锁。
SELECT column FROM table ... LOCK IN SHARE MODE;
  • 意向排他锁(intention exclusive lock, IX):事务有意向对表中的某些行加排他锁(X锁)
-- 事务要获取某些行的 X 锁,必须先获得表的 IX 锁。
SELECT column FROM table ... FOR UPDATE;

即:意向锁是由存储引擎 自己维护的 ,用户无法手动操作意向锁,在为数据行加共享 / 排他锁之前,InooDB 会先获取该数据行 所在数据表的对应意向锁 。

③ 自增锁(AUTO-INC锁)

④ 元数据锁(MDL锁)

当对一个表做增删改查操作的时候,加 MDL读锁;当要对表做结构变更操作的时候,加 MDL 写锁

2.InnoDB中的行锁
  • 一旦某个加锁操作没有使用到索引,那么该锁就会退化为表锁
  • 只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁!
  • 即便在条件中使用了索引字段,但是否使用索引来检索数据是由MySQL通过判断不同执行计划的代价来决定的,如果MySQL认为全表扫描效率更高,比如一些很小的表,它就不会使用索引,这种情况下InnoDB将使用表锁,而不是行锁。因此,在分析锁冲突时,别忘了检查SQL的执行计划,以确认是否真正使用了索引。(如果不使用索引那么会加表锁,更加影响效率,尽量加索引)

记录锁(Record Locks)

记录锁是有S锁和X锁之分的,称之为 S型记录锁 和 X型记录锁 。

  • 当一个事务获取了一条记录的S型记录锁后,其他事务也可以继续获取该记录的S型记录锁,但不可以继续获取X型记录锁;
  • 当一个事务获取了一条记录的X型记录锁后,其他事务既不可以继续获取该记录的S型记录锁,也不可以继续获取X型记录锁。

间隙锁(Gap Locks)

gap锁的提出仅仅是为了防止插入幻影记录而提出的。

③ 临键锁(Next-Key Locks)

  • 间隙锁存在于非唯一索引中,锁定开区间范围内的一段间隔,它是基于临键锁实现的。
  • 临键锁存在于非唯一索引中,该类型的每条记录的索引上都存在这种锁,它是一种特殊的间隙锁,锁定一段左开右闭的索引区间。

官网上有介绍,Next-Key Locks是行锁和gap锁的组合

④ 插入意向锁

我们说一个事务在 插入 一条记录时需要判断一下插入位置是不是被别的事务加了 gap锁 ( next-key锁也包含 gap锁 ),如果有的话,插入操作需要等待,直到拥有 gap锁 的那个事务提交。但是InnoDB规定事务在等待的时候也需要在内存中生成一个锁结构表明有事务想在某个 间隙 中 插入 新记录,但是现在在等待。InnoDB就把这种类型的锁命名为 Insert Intention Locks ,官方的类型名称为:LOCK_INSERT_INTENTION ,我们称为 插入意向锁 。插入意向锁是一种 Gap锁 ,不是意向锁,在insert操作时产生。
插入意向锁是在插入一条记录行前,由 INSERT 操作产生的一种间隙锁 。
事实上插入意向锁并不会阻止别的事务继续获取该记录上任何类型的锁。

3.页锁

当某个层级的锁数量超过了这个层级的阈值时,就会进行 锁升级 。锁升级就是用更大粒度的锁替代多个更小粒度的锁,比如InnoDB 中行锁升级为表锁,这样做的好处是占用的锁空间降低了,但同时数据的并发度也下降了

4.死锁

事务1在等待事务2释放id=2的行锁,而事务2在等待事务1释放id=1的行锁。 事务1和事务2在互相等待对方的资源释放,就是进入了死锁状态。当出现死锁以后,有两种策略 :

  • 一种策略是,直接进入等待,直到超时。这个超时时间可以通过参数innodb_lock_wait_timeout 来设置。
  • 另一种策略是,发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务(将持有最少行级排他锁的事务进行回滚),让其他事务得以继续执行。将参数 innodb_deadlock_detect 设置为on ,表示开启这个逻辑。

MVCC

MVCC (Multiversion Concurrency Control),多版本并发控制。顾名思义,MVCC 是通过数据行的多个版本管理来实现数据库的 并发控制 。MVCC在MySQL InnoDB中的实现主要是为了提高数据库并发性能,用更好的方式去处理 读-写冲突 ,做到即使有读写冲突时,也能做到 不加锁 , 非阻塞并发读.

总结:

这里介绍了 MVCC 在 READ COMMITTD 、 REPEATABLE READ 这两种隔离级别的事务在执行快照读操作时访问记录的版本链的过程。这样使不同事务的 读-写 、 写-读 操作并发执行,从而提升系统性能。

核心点在于 ReadView 的原理, READ COMMITTD 、 REPEATABLE READ 这两个隔离级别的一个很大不同就是生成ReadView的时机不同:READ COMMITTD 在每一次进行普通SELECT操作前都会生成一个ReadView。REPEATABLE READ 只在第一次进行普通SELECT操作前生成一个ReadView,之后的查询操作都重复使用这个ReadView就好了。

redis总结

0.说一下 Redis 有什么优点和缺点

优点

  1. 速度快:因为数据存在内存中,类似于 HashMap , HashMap 的优势就是查找和操作的时间复杂度都是O (1) 。
  2. 支持丰富的数据结构:支持 String ,List,Set,Sorted Set,Hash 五种基础的数据结构。
  3. 持久化存储:Redis 提供 RDB 和 AOF 两种数据的持久化存储方案,解决内存数据库最担心的万一 Redis 挂掉,数据会消失掉
  4. 高可用:内置 Redis Sentinel (哨兵模式),提供高可用方案,实现主从故障自动转移。 内置 RedisCluster ,提供集群方案,实现基于槽的分片方案,从而支持更大的 Redis 规模。
  5. 丰富的特性==:Key过期==、计数分布式锁消息队列等。

缺点

  1. 由于 Redis 是内存数据库,所以,单台机器,存储的数据量,跟机器本身的内存大小。虽然Redis 本身有 Key 过期策略,但是还是需要提前预估和节约内存。如果内存增长过快,需要定期删除数据
  2. 如果进行完整重同步,由于需要生成 RDB 文件,并进行传输,会占用主机的 CPU ,并会消耗现网的带宽。不过 Redis 2.8 版本,已经有部分重同步的功能,但是还是有可能有完整重同步的。比如,新上线的备机。
  3. 修改配置文件,进行重启,将硬盘中的数据加载进内存,时间比较久。在这个过程中, Redis不能提供服务

1.说⼀下 Redis Memcached 的区别和共同点:

  1. Redis ⽀持更丰富的数据类型(⽀持更复杂的应⽤场景)。Redis 不仅仅⽀持简单的 k/v 类型的数据,同时还提供 list,set,zset,hash 等数据结构的存储。Memcached 只⽀持最简单的 k/v 数据类型。
  2. Redis ⽀持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进⾏使⽤,⽽ Memecache 把数据全部存在内存之中。
  3. Redis 有灾难恢复机制。 因为可以把缓存中的数据持久化到磁盘上。
  4. Redis 原生就支持集群模式, Redis3.0 版本中,官方便能支持Cluster模式了, Memcached 没有原生的集群模式,需要依赖客户端来实现,然后往集群中分片写入数据。
  5. Memcached 是多线程⾮阻塞 IO 复⽤的⽹络模型;Redis 使⽤单线程多路 IO 复⽤模型。 (Redis 6.0 引⼊了多线程 IO )
  6. Redis ⽀持发布订阅模型、Lua 脚本、事务等功能,⽽ Memcached 不⽀持。并且,Redis⽀持更多的编程语⾔。
  7. Memcache 的单个value最大 1m , Redis 的单个value最大 512m
  8. Memcached过期数据的删除策略只⽤了惰性删除,⽽ Redis 同时使⽤了惰性删除与定期删除
  9. Redis 在服务器内存使⽤完之后,可以将不⽤的数据放到磁盘上。但是,Memcached 在服务器内存使⽤完之后,就会直接报异常。

1Redis** 单线程模型详解

Redis 内部使用文件事件处理器 file event handler ,这个文件事件处理器是单线程的,所以 Redis 才叫做单线程的模型。它采用 IO 多路复用机制同时监听多个 socket,将产生事件的 socket 压入内存队列中,事件分派器根据 socket 上的事件类型来选择对应的事件处理器进行处理

Redis 基于 Reactor 模式来设计开发了⾃⼰的⼀套⾼效的事件处理模型 (Netty 的线程模型也基于 Reactor 模式,Reactor 模式不愧是⾼性能 IO 的基⽯),这套事件处理模型对应的是 Redis中的⽂件事件处理器(file event handler)。由于**⽂件事件处理器(file event handler)是单线程⽅式运⾏的**,所以我们⼀般都说 Redis 是单线程模型。

既然是单线程,那怎么监听⼤量的客户端连接呢?

Redis 通过==IO 多路复⽤程序== 来监听来⾃客户端的⼤量连接(或者说是监听多个 socket),它会将感兴趣的事件及类型(读、写)注册到内核中并监听每个事件是否发⽣。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9vc3p8a3-1676344835177)(MySql总结.assets/image-20221115101731276.png)]

IO多路复用的三种实现方式:

0selectpollepoll
数据结构bitmap数组红黑树
最大连接数1024无上限无上限
轮询O(n)O(n)O(1)

select poll:

将用户态传入的数组拷贝到内核空间,然后查询每个fd对应设备状态

  • 若设备就绪 在设备等待队列中加入一项继续遍历

  • 若遍历完所有fd后,都没发现就绪的设备 挂起当前进程,直到设备就绪或主动超时,被唤醒后它又再次遍历fd。这个过程经历多次无意义遍历。

epoll:

epoll模型修改主动轮询为被动通知,当有事件发生时,被动接收通知。所以epoll模型注册套接字后,主程序可做其他事情,当事件发生时,接收到通知后再去处理。

epoll 是对 select 和poll 的改进:

  1. 不需要每次向内核传入文件socket文件描述符,内核自己保存了一份
  2. 不像poll通过轮询的方式来找出就绪的文件描述符,而是通过异步IO事件来通知用户
  3. 内核会通过 IO 事件告诉用户就绪的文件描述符

这样的好处⾮常明显: I/O 多路复⽤技术的使⽤让 Redis 不需要额外创建多余的线程来监听客户端的⼤量连接,降低了资源的消耗**(和 NIO 中的 Selector 组件很像)。另外, Redis 服务器是⼀个事件驱动程序,服务器需要处理两类事件: 1. ⽂件事件; 2. 时间事件。时间事件不需要多花时间了解,我们接触最多的还是 **⽂件事件(客户端进⾏读取写⼊等操作,涉及⼀系列⽹络通信)。

1.1Redis4.0 增加的多线程

不过,Redis 4.0 增加的多线程主要是针对⼀些⼤键值对的删除操作的命令,使⽤这些命令就会使⽤主处理之外的其他线程来“异步处理”。

⼤体上来说,Redis 6.0 之前主要还是单线程处理。那,Redis6.0 之前 为什么不使⽤多线程?

我觉得主要原因有下⾯ 3 个:

  1. 单线程编程容易并且更容易维护;

  2. Redis 的性能瓶颈不再 CPU ,主要在内存和⽹络

  3. 多线程就会存在死锁、线程上下⽂切换等问题,甚⾄会影响性能

Redis6.0 **引⼊多线程主要是为了提⾼⽹络 IO 读写性能,因为这个算是 Redis 中的⼀个性能瓶颈(Redis 的瓶颈主要受限于内存和⽹络)。虽然,Redis6.0 引⼊了多线程,但是 Redis 的多线程只是在⽹络数据的读写这类耗时操作上使⽤了, 执⾏命令仍然是单线程顺序执⾏。因此,你也不需要担⼼线程安全问题。

2. Redis 给缓存数据设置过期时间有啥⽤?

注意:Redis中除了字符串类型有⾃⼰独有设置过期时间的命令 setex 外,其他⽅法都需要依靠expire 命令来设置过期时间 。

另外, persist 命令可以移除⼀个键的过期时间:过期时间除了有助于缓解内存的消耗,还有什么其他⽤么?很多时候,我们的业务场景就是需要某个数据只在某⼀时间段内存在,⽐如我们的短信验证码可能只在1分钟内有效,⽤户登录的 token 可能只在 1 天内有效。如果使⽤传统的数据库来处理的话,⼀般都是⾃⼰判断过期,这样更麻烦并且性能要差很多。

3.Redis是如何判断数据是否过期的呢?

Redis 通过⼀个叫做**过期字典(可以看作是hash表)**来保存数据过期的时间。

  • 过期字典的键指向Redis数据库中的某个key(键)

  • 过期字典的值是⼀个long long类型的整数,这个整数保存了key所指向的数据库键的过期时间(毫秒精度的UNIX时间戳)。

4.过期的数据的删除策略了解么?

如果假设你设置了⼀批 key 只能存活 1 分钟,那么 1 分钟后,Redis 是怎么对这批 key 进⾏删除的呢?常⽤的过期数据的删除策略就两个(重要!⾃⼰造缓存轮⼦的时候需要格外考虑的东⻄):

  • 惰性删除 :只会在取出key的时候才对数据进⾏过期检查。这样对CPU最友好,但是可能会造成太多过期 key 没有被删除。

  • 定期删除 : 每隔⼀段时间抽取⼀批 key 执⾏删除过期key操作。并且,Redis 底层会通过限制删除操作执⾏的时⻓频率来减少删除操作对CPU时间的影响

  • 定期删除对内存更加友好,惰性删除对CPU更加友好。两者各有千秋,所以Redis 采⽤的是 定期删除+惰性/懒汉式删除

还是可能存在定期删除惰性删除漏掉了很多过期 key 的情况。这样就导致⼤量过期 key 堆积在内存⾥,然后就Out of memory了Redis 内存淘汰机制

5.Redis 内存淘汰机制刷新刷新策略了解么?

  1. volatile-lru(least recently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使⽤的数据淘汰

  2. volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰

  3. volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰

  4. allkeys-lru(least recently used):当内存不⾜以容纳新写⼊数据时,在键空间中,移除最近最少使⽤的 key(这个是最常⽤的)

  5. allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰

  6. no-eviction:禁⽌驱逐数据,也就是说当内存不⾜以容纳新写⼊数据时,新写⼊操作会报错。这个应该没⼈使⽤吧!

4.0 版本后增加以下两种:

  1. volatile-lfu(least frequently used):从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使⽤的数据淘汰

  2. allkeys-lfu(least frequently used):当内存不⾜以容纳新写⼊数据时,在键空间中,移除最不经常使⽤的 key

6.Redis 持久化机制

RDB

Redis会通过主进程单独创建(fork)一个子进程来进行持久化,会先将数据写入到 一个临时文件中,待持久化过程都结束了,再用这个临时文件替换上次持久化好的文件。 整个过程中,主进程是不进行任何IO操作的,这就确保了极高的性能 如果需要进行大规模数据的恢复,且对于数据恢复的完整性不是非常敏感,那RDB方式要比AOF方式更加的高效。RDB的缺点是:最后一次持久化后的数据可能丢失

缺点:

  1. Fork的时候,内存中的数据被克隆了一份,大致2倍的膨胀性需要考虑
  2. 虽然Redis在fork时使用了写时拷贝技术,但是如果数据庞大时还是比较消耗性能。
  3. l 在备份周期在一定间隔时间做一次备份,所以如果Redis意外down掉的话,就会丢失最后一次快照后的所有修改

优点:

  1. 适合**大规模的数据恢复**
  2. 对数据完整性和一致性要求不高更适合使用
  3. 节省磁盘空间(redis会采用LZF算法**进行压缩**。)
  4. 恢复速度快
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

日志的形式来记录每个写操作(增量保存),将Redis执行过的所有写指令记录下来(读操作不记录), 只许追加文件但不可以改写文件,redis启动之初会读取该文件重新构建数据,换言之,redis 重启的话就根据日志文件的内容**将写指令从前到后执行一次以完成数据的恢复工作**.

在三种不同的 AOF 持久化⽅式:

appendfsync always #每次有数据修改发⽣时都会写⼊AOF⽂件,这样会严重降低Redis的速度
appendfsync everysec #每秒钟同步⼀次,显示地将多个写命令同步到硬盘
appendfsync no #让操作系统决定何时进⾏同步

AOF 重写:AOF 重写可以产⽣⼀个新的 AOF ⽂件,这个新的 AOF ⽂件和原有的 AOF ⽂件所保存的数据库状态⼀样,但体积更⼩。

7 .Redis 事务

redis 可以通过 MULTIEXECDISCARDWATCH 等命令来实现事务(transaction)功能

  • MULTI开启事物后, 服务器在收到来自客户端的命令时, 不会立即执行命令, 而是将这些命令全部放进一个事务队列里, 然后返回 QUEUED , 表示命令已入队:

  • DISCARD 命令用于取消一个事务, 它清空客户端的整个事务队列, 然后将客户端从事务状态调整回非事务状态, 最后返回字符串 OK 给客户端, 说明事务已被取消。

  • WATCH 命令用于在事务开始之前监视任意数量的键: 当调用 EXEC 命令执行事务时, 如果任意一个被监视的键已经被其他客户端修改了, 那么整个事务不再执行, 直接返回失败。

三大特性:

  • 单独的隔离操作
    事务中的所有命令都会序列化、按顺序地执行。事务在执行的过程中,不会被其他客户端发送来的命令请求所打断

  • 没有隔离级别的概念

​ 队列中的命令没有**提交之前都不会实际被执行**,因为事务提交前任何指令都不会被实际执行

  • 不保证原子性

​ 事务中如果有一条命令执行失败,其后的命令仍**然会被执行,没有回滚** ,回滚需要增加很多工作,而不支持回滚则可以保持简单、快速的特性。

8. 缓存穿透,击穿,雪崩

缓存穿透
  • key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会压到数据源,从而可能压垮数据源。

  • 例如:某个⿊客故意制造我们缓存中不存在的 key 发起⼤量请求,导致⼤量请求落到数据库。

解决方案

  1. 缓存⽆效 key以及null
    如果缓存数据库都查不到某个 key 的数据就写⼀个到 Redis 中去并设置过期时间,具体命令如下: SET key value EX 10086.这种⽅式可以解决请求的 key**变化不频繁的情况**,如果⿊客恶意攻击,每次构建不同的请求 key,会导致 Redis 中缓存⼤量⽆效的 key 。很明显,这种⽅案并不能从根本上解决此问题。

  2. 设置可访问的名单(白名单)

  3. 进行实时监控
    当发现Redis的命中率开始急速降低,需要排查访问对象和访问的数据,和运维人员配合,可以设置黑名单限制服务

  4. 布隆过滤器

    • 我们需要的就是判断 key 是否合法,通过它我们可以⾮常⽅便地判断⼀个给定数据是否存在于海量数据中.

    • 把所有可能存在的请求的值都存放在布隆过滤器中,当⽤户请求过来,先判断⽤户发来的请求的值是否存在于布隆过滤器中。不存在的话,直接返回请求参数错误信息给客户端,存在的话才会⾛下⾯的流程。

    • 布隆过滤器说某个元素存在,⼩概率会误判。布隆过滤器说某个元素不在,那么这个元素⼀定不在.

缓存击穿
  • key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
  • 例如:key可能会在某些时间点被超高并发地访问,是一种非常==“热点”==的数据。这个时候,需要考虑一个问题:缓存被“击穿”的问题。

解决方案

  1. 预先设置热门数据
    在redis高峰访问之前,把一些热门数据提前存入到redis里面,加大这些热门数据key的时长

  2. 实时调整

    现场监控哪些数据热门,实时调整key的过期时长

  3. 使用锁
    (1) 就是在缓存失效的时候(判断拿出来的值为空),不是立即去load db。

    (2)先使用缓存工具的某些带成功操作返回值的操作(比如Redis的SETNX)去set一个mutex key

    (3)当操作返回成功时,再进行load db的操作,并回设缓存,最后删除mutex key;

    (4)当操作返回失败,证明有线程在load db,当前线程睡眠一段时间再重试整个get缓存的方法。

  • 在缓存失效后,通过互斥锁或者队列来控制读数据写缓存的线程数量,比如某个key只允许一个线程查询数据和写缓存,其他线程等待。这种方式会阻塞其他的线程,此时系统的吞吐量会下降
缓存雪崩
  • key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。
  • 缓存雪崩与缓存击穿的区别在于这里针对很多key缓存,前者则是某一个key

解决方案

  1. 构建多级缓存架构
    nginx缓存 + redis缓存 +其他缓存(ehcache等)
  2. 使用锁或队列
    用加锁或者队列的方式保证来保证不会有大量的线程对数据库一次性进行读写,从而避免失效时大量的并发请求落到底层存储系统上。不适用高并发情况
  3. 设置过期标志更新缓存
    记录缓存数据是否过期(设置提前量),如果过期会触发**通知另外的线程在后台去更新实际key的缓存**。
  4. 将缓存失效时间分散开
    比如我们可以在原有的失效时间基础上增加一个随机值,比如1-5分钟随机,这样每一个缓存的过期时间的重复率就会降低,就很难引发集体失效的事件。
  5. 使用熔断机制,限流降级。当流量达到一定的阈值,直接返回“系统拥挤”之类的提示,防止过多的请求打在数据库上将数据库击垮,至少能保证一部分用户是可以正常使用,其他用户多刷新几次也能得到结果。

9.如何保证缓存和数据库数据的⼀致性?

Cache Aside Pattern(旁路缓存模式):更新 DB,然后直接删除 cache

如果更新数据库成功,⽽删除缓存这⼀步失败的情况的话,简单说两个解决⽅案

  • 缓存失效时间变短(不推荐,治标不治本)

  • 增加cache更新重试机制(常⽤)

    缓存删除失败的话,我们就隔⼀段时间进⾏重试,重试次数可以⾃⼰定。如果多次重试还是失败的话,我们可以把当前更新失败的 key 存⼊队列中,等缓存服务可⽤之后将 缓存中对应的 key 删除即可。

10.为什么 Redis 单线程模型效率也能那么高?

  1. C语言实现,效率高
  2. 纯内存操
  3. 基于非阻塞的IO复用模型机制
  4. 单线程的话就能避免多线程的频繁上下文切换问题
  5. 丰富的数据结构全称采用hash结构,读取速度非常快,对数据存储进行了一些优化,比如亚索表,跳表等)

11.Redis 的同步机制了解是什么?

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-L8n8E1hh-1676344835178)(MySql总结.assets/format,png.png)]

全量同步:

  1. slave第一次启动时,连接Master,发送PSYNC命令,格式为psync {runId} {offset}
  2. {runId} 为master的运行id;{offset}为slave自己的复制偏移量
  3. 由于此时是slave第一次连接master,slave不知道master的runId,也不知道自己偏移量,这时候会传一个问号和-1,告诉master节点是第一次同步。格式为psync ? -1
  4. 当master接收到psync ? -1时,就知道slave是要全量复制,就会将自己的runIdoffset告知slave,回复命令+fullresync {runId} {offset}。同时,master会执行bgsave命令来生成RDB文件,并使用缓冲区记录此后的所有写命令
    • 此时收到有偏移量说明不是第一次采用**增量复制**
    • 根据参数offset在自==身复制积压缓冲区查找==,如果偏移量之后的数据存在缓冲区中,则对从节点发送+CONTINUE响应,表示可以进行部分复制;否则进行全量复制。
  5. master bgsave执行完毕,**异步**向Slave发送RDB文件,同时继续新的命令进入复制积压缓冲区。RDB文件发送完毕后,开始向Slave发送存储在缓冲区写命令
  6. slave收到RDB文件,丢弃所有旧数据,开始载入RDB文件;并执行Master发来的所有的存储在缓冲区里的写命令。
  7. 此后 master 每执行一个写命令,就向Slave发送相同的写命令。

复制积压缓冲区

  • 存在于主节点(master),默认大小为1MB,可以通过参数rel_backlog_size来修改默认大小
  • 复制积压缓冲区是保存在主节点上的一个固定长度的队列。当从节点(slave)连接主节点时被创建,这时主节点(master)响应写命令时,不但会把命令发送给从节点,还会写入复制积压缓冲区。
  • 由于缓冲区本质上是先进先出的定长队列,所以能实现保存最近已复制数据的功能,用于部分复制和复制命令丢失的数据补救。复制缓冲区相关统计信息保存在主节点的info replication中。

总结:

redis2.8之前的版本中断线情况下SYNC进行全量同步的低效问题,在Redis 2.8之后使用psync命令代替sync命令执行同步操作,psync**具备了数据全量重同步 和 **

增量同步

12.pipeline 有什么好处,为什么要用 pipeline?

  • 使用 pipeline(管道)的好处在于可以将多次 I/O 往返的时间缩短为一次,但是要求管道中执行的指令间没有因果关系。

  • 用 pipeline 的原因在于可以实现请求/响应服务器的功能,当客户端尚未读取旧响应时,它也可以处理新的请求。如果客户端存在多个命令发送到服务器时,那么客户端无需等待服务端的每次响应才能执行下个命令,只需最后一步从服务端读取回复即可。

13.怎么使用 Redis 实现消息队列?

一般使用 list 结构作为队列, rpush 生产消息, lpop 消费消息。当 lpop 没有消息的时候,要适当sleep 一会再重试

  • 面试官可能会问可不可以不用 sleep 呢?list 还有个指令叫 blpop(阻塞读,如果为空则阻塞直到有数据) ,在没有消息的时候,它会阻塞住直到消息到来。

  • 面试官可能还问能不能生产一次消费多次呢?使用 pub / sub 主题订阅者模式,可以实现 1:N的消息队列。

    • 无法持久化保存消息,如果 Redis 服务器宕机或重启,那么所有的消息将会丢失;
    • 不支持消费者确认机制,稳定性不能得到保证,例如当消费者获取到消息之后,还没来得及执行就宕机了。
    • 发布订阅模式是“发后既忘”的工作模式,如果有订阅者离线重连之后就不能消费之前的历史消息
  • 阿里内部资料面试官可能还问 Redis 如何**实现延时队列**?

    • 使用sortedset ,拿时间戳作为 score ,消息内容作为 key 调用 zadd 来生产消息,消费者用zrangebyscore 指令获取 N 秒之前的数据轮询进行处理。
    • zest作为时延队列不能存储相同元素的值。

14.什么是 bigkey?会存在什么影响?

bigkey 是指键值占用内存空间非常大的 key。例如一个字符串 a 存储了 200M 的数据。

  • 网络阻塞;获取 bigkey 时,传输的数据量比较大,会增加带宽的压力
  • IO阻塞延迟增大;因为 bigkey 占用的空间比较大,所以操作起来效率会比较低,IO写的时候会耗费较长的时时间,Redis的核心线程是单线程,也就是工作线程是单线程,单线程中的任务处理是串行执行的,前面的任务完成之后,后面的任务才能继续执行,所以因为单个BigKey的原因可能造成IO阻塞延迟
  • 导致内存空间不平衡;一个 bigkey 存储数据量比较大,同一个 key 在同一个节点或服务器中存储,会造成一定影响。

15.熟悉哪些 Redis 集群模式?

  1. Redis Sentinel(主从哨兵模式
    体量较小时,选择 Redis Sentinel ,单主 Redis 足以支撑业务
  • 哨兵通过发送命令(独立进程),让Redis服务器返回监控其运行状态,包括主服务器和从服务器。

  • 当哨兵监测到master宕机,会自动将slave切换成master,然后通过发布订阅模式通知其他的从服务器,修改配置文件,让它们切换主机。

  1. Redis Cluster

Redis 官方提供的集群化方案,体量较大时,选择 Redis Cluster ,通过分片,使用更多内存

  1. Twemprox

  2. Codis

16.是否使用过 Redis Cluster 集群,集群的原理是什么?

  1. 自动将数据进行分片,每个 master 上放一部分数据,分担压力

  2. 集群节点挂掉会自动故障转移,部分 master 不可用时,还是可以继续工作的

  3. 在 redis cluster 架构下,每个 redis 要放开两个端口号,比如一个是 6379,另外一个就是 加1w 的端口号,比如 16379。16379 端口号是用来进==行节点间通信的,redis集群采用gossip二进制协议来进行同学,用来进行故障检测、配置更新、故障转移授权==。cluster bus 用了另外一种二进制的协议,gossip 协议,用于==节点间进行==高效的数据交换,占用更少的网络带宽和处理时间()。

  4. 采用hash槽方式,存储分布在多个 Redis 实例上

  5. 可以相对平滑扩/缩容节点

17.分布式寻址算法

hash 算法

来了一个 key,首先计算 hash 值,然后对**节点数取模。然后打在不同的 master 节点上。一旦某一个 master 节点宕机**,所有请求过来,都会基于最新的剩余 master 节点数去取模,尝试去取数据。这会导致大部分的请求过来,全部无法拿到有效的缓存,导致大量的流量涌入数据库。

一致性哈希算法
  • 一致性 hash 算法将整个 hash 值空间组织成一个虚拟的圆环,整个空间按顺时针方向组织,下一步将各个 master 节点(使用服务器的 ip 或主机名)进行 hash。这样就能确定每个节点在其哈希环上的位置。

  • 来了一个 key,首先计算 hash 值,并确定此数据在环上的位置,从此位置沿环顺时针“行走”,遇到的第一个 master 节点就是 key 所在位置。

  • 在一致性哈希算法中,如果一个节点挂了,受影响的数据仅仅是此节点到环空间前一个节点(沿着逆时针方向行走遇到的第一个节点)之间的数据,其它不受影响。增加一个节点也同理。

  • 燃鹅,一致性哈希算法在节点太少时,容易因为节点分布不均匀而造成缓存热点的问题。为了解决这种热点问题,一致性 hash 算法引入了虚拟节点机制,即对每一个节点计算多个 hash,每个计算结果位置都放置一个虚拟节点。这样就实现了数据的均匀分布,负载均衡。

hash slot 算法
  • redis cluster 有固定的 16384 个 hash slot(保证1000台redis),对每个 key 计算 CRC(循环冗余校验码) 值,然后对 16384 取模,可以获取 key 对应的 hash slot。

  • redis cluster 中每个 master 都会持有部分 slot,比如有 3 个 master,那么可能每个 master 持有 5000 多个 hash slot。hash slot 让 node 的增加和移除很简单,增加一个 master,就将其他 master 的 hash slot 移动部分过去,减少一个 master,就将它的 hash slot 移动到其他 master 上去。移动 hash slot 的成本是非常低的。客户端的 api,可以对指定的数据,让他们走同一个 hash slot,通过 hash tag 来实现。

  • 任何一台机器宕机,另外两个节点,不影响的。因为 key 找的是 hash slot,不是机器。

18.Redis 常见性能问题和解决方案有哪些?

  • Master 最好不要做任何持久化工作,如 RDB 内存快照和 AOF 日志文件;
  • 如果数据比较重要,某个 Slave 开启 AOF 备份数据,策略设置为每秒同步一次
  • 为了主从复制的速度和连接的稳定性,Master 和 Slave 最好在同一个局域网内;
  • 尽量避免在压力很大的主库增加从库
  • 主从复制不要用图状结构,用单向链表结构更为稳定,即:Master <- Slave1 <- Slave2 <-Slave3….;这样的结构方便解决单点故障问题,实现 Slave 对 Master 的替换。如果 Master 挂了,可以立刻启用 Slave1 做 Master,其他不变。

19.假如 Redis 里面有 1 亿个 key,其中有 10w 个 key 是以某个固定的已知的前缀开头的,如果将它们全部找出来?

我们可以使用 keys 命令和 scan 命令,但是会发现使用 scan 更好。通过match来匹配,count查询的条数

scan 176 MATCH *11* COUNT 1000

1. 使用 keys 命令

直接使用 keys 命令查询,但是如果是在生产环境下使用会出现一个问题,keys 命令是遍历查询的,查询的时间复杂度为 O(n),数据量越大查询时间越长。而且 Redis 是单线程,keys 指令会导致线程阻塞一段时间,会导致线上 Redis 停顿一段时间,直到 keys 执行完毕才能恢复。这在生产环境是不允许的。除此之外,需要注意的是,这个命令没有分页功能,会一次性查询出所有符合条件的 key 值,会发现查询结果非常大,输出的信息非常多。所以不推荐使用这个命令。

2. 使用 scan 命令

scan 命令可以实现和 keys 一样的匹配功能,但是 scan 命令在执行的过程不会阻塞线程,并且查找的数据可能存在重复,需要客户端操作去重。因为 scan 是通过游标方式查询的(通过记录上次的位置,下次继续查询),所以不会导致Redis 出现假死的问题。Redis 查询过程中会把游标返回给客户端,单次返回空值且游标不为 0,则说明遍历还没结束,客户端继续遍历查询。scan 在检索的过程中,被删除的元素是不会被查询出来的,但是如果在迭代过程中有元素被修改,scan 不能保证查询出对应元素。相对来说,scan 指令查找花费的时间会比 keys 指令长。

20.什么情况下可能会导致 Redis 阻塞

内部原因

  • 如果 Redis 主机的 CPU 负载过高,也会导致系统崩溃;

  • 数据持久化占用资源过多

  • 对 Redis 的 API 或指令使用不合理,导致 Redis 出现问题。

外部原因

外部原因主要是服务器的原因,例如服务器的 CPU 线程在切换过程中竞争过大,内存出现问题、网络问题等

21.缓存和数据库谁先更新呢?

写请求过来,将写请求缓存到缓存队列中,并且开始执行写请求的具体操作(删除缓存中的数据,更新数据库,更新缓存)。

  1. 如果在更新数据库过程中,又来了个读请求,将读请求再次存入到缓存队列(可以搞n个队列,采用key的hash值进行队列个数取模hash%n,落到对应的队列中,队列需要保证顺序性)中,顺序性保证等待队列前的写请求执行完成,才会执行读请求之前的写请求删除缓存失败,直接返回,此时数据库中的数据是旧值,并且与缓存中的数据是一致的,不会出现缓存一致性的问题。
  2. 写请求删除缓存成功,则更新数据库,如果更新数据库失败,则直接返回,写请求结束,此时数据库中的值依旧是旧值,读请求过来后,发现缓存中没有数据, 则会直接向数据库中请求,同时将数据写入到缓存中,此时也不会出现数据一致性的问题.
  3. 更新数据成功之后,再更新缓存,如果此时更新缓存失败,则缓存中没有数据,数据库中是新值 ,写请求结束,此时读请求还是一样,发现缓存中没有数据,同样会从数据库中读取数据,并且存入到缓存中,其实这里不管更新缓存成功还是失败, 都不会出现数据一致性的问题。上面这方案解决了数据不一致的问题,主要是使用了串行化,每次操作进来必须按照顺序进行。如果某个队列元素积压太多,可以针对读请求进行过滤,提示用户刷新页面,重新请求。

潜在的问题,留给大家自己去想吧,因为这个问题属于发散性。

1,请求时间过长,大量的写请求堆压在队列中,一个读请求来得等都写完了才可以获取到数据。

2,读请求并发高

3,热点数据路由问题,导致请求倾斜

22.单线程的redis为什么这么快

  1. 内存存储:Redis是使用内存(in-memeroy)存储,没有磁盘IO上的开销。数据存在内存中,类似于 HashMap,HashMap 的优势就是查找和操作的时间复杂度都是O(1)。

  2. 单线程实现( Redis 6.0以前):Redis使用单个线程处理请求,避免了多个线程之间线程切换锁资源争用的开销。注意:单线程是指的是在核心网络模型中,网络请求模块使用一个线程来处理,即一个线程处理所有网络请求。

  3. 非阻塞IO:Redis使用多路复用IO技术,将epoll作为I/O多路复用技术的实现,再加上Redis自身的事件处理模型将epoll中的连接、读写、关闭都转换为事件,不在网络I/O上浪费过多的时间。

  4. 优化的数据结构:Redis有诸多可以直接应用的优化数据结构的实现,应用层可以直接使用原生的数据结构提升性能。

  5. 使用底层模型不同:Redis直接自己构建了 VM (虚拟内存)机制 ,因为一般的系统调用系统函数的话,会浪费一定的时间去移动和请

    Redis的VM(虚拟内存)机制就是暂时把不经常访问的数据(冷数据)从内存交换到磁盘中,从而腾出宝贵的内存空间用于其它需要访问的数据(热数据)。通过VM功能可以实现冷热数据分离,使热数据仍在内存中、冷数据保存到磁盘。这样就可以避免因为内存不足而造成访问速度下降的问题。

    Redis提高数据库容量的办法有两种:一种是可以将数据分割到多个RedisServer上;另一种是使用虚拟内存把那些不经常访问的数据交换到磁盘上。需要特别注意的是Redis并没有使用OS提供的Swap,而是自己实现。

23.Redis 为什么是单线程的

官方FAQ表示,因为Redis是基于内存的操作,CPU不是Redis的瓶颈,Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现,而且CPU不会成为瓶颈,那就顺理成章地采用单线程的方案了(毕竟采用多线程会有很多麻烦!)Redis利用队列技术将并发访问变为串行访问 )绝大部分请求是纯粹的内存操作(非常快速)2)采用单线程,避免了不必要的上下文切换和竞争条件 )

非阻塞IO优点

24.redis分布式锁

  • 即同一时间只能有一个进程获取锁标记,我们可以通过redis的setnx实现,只有第一次执行的才会成功并返回1,其它情况返回0。

  • 释放锁其实只需要把锁的key删除即可,使用del xxx指令。不过,如果在我们执行del之前,服务突然宕机,那么锁就永远无法删除了。所以我们可以通过setex 命令设置过期时间即可。

25.集群不可用情况

  1. 当访问一个 Master 和 Slave 节点==都挂了的槽的时候,==会报槽无法获取。
  2. 当集群 Master 节点个数小于 3 个的时候,或者集群可用节点个数为偶数的时候,基于 fail 的这种选举机制的自动主从切换过程可能会不能正常工作,一个是标记 fail 的过程,一个是选举新的 master 的过程,都有可能异常。

26. Redis的常用场景有哪些?

1、缓存

缓存现在几乎是所有中大型网站都在用的必杀技,合理的利用缓存不仅能够提升网站访问速度,还能大大降低数据库的压力。Redis提供了键过期功能,也提供了灵活的键淘汰策略,所以,现在Redis用在缓存的场合非常多。

2、排行榜

很多网站都有排行榜应用的,如京东的月度销量榜单、商品按时间的上新排行榜等。Redis提供的有序集合数据类构能实现各种复杂的排行榜应用。

3、计数器

什么是计数器,如电商网站商品的浏览量、视频网站视频的播放数等。为了保证数据实时效,每次浏览都得给+1,并发量高时如果每次都请求数据库操作无疑是种挑战和压力。Redis提供的incr命令来实现计数器功能,内存操作,性能非常好,非常适用于这些计数场景。

4、分布式会话

集群模式下,在应用不多的情况下一般使用容器自带的session复制功能就能满足,当应用增多相对复杂的系统中,一般都会搭建以Redis等内存数据库为中心的session服务,session不再由容器管理,而是由session服务及内存数据库管理。

5、分布式锁

在很多互联网公司中都使用了分布式技术,分布式技术带来的技术挑战是对同一个资源的并发访问,如全局ID、减库存、秒杀等场景,并发量不大的场景可以使用数据库的悲观锁、乐观锁来实现,但在并发量高的场合中,利用数据库锁来控制资源的并发访问是不太理想的,大大影响了数据库的性能。可以利用Redis的setnx功能来编写分布式的锁,如果设置返回1说明获取锁成功,否则获取锁失败,实际应用中要考虑的细节要更多。

6、 社交网络

点赞、踩、关注/被关注、共同好友等是社交网站的基本功能,社交网站的访问量通常来说比较大,而且传统的关系数据库类型不适合存储这种类型的数据,Redis提供的哈希、集合等数据结构能很方便的的实现这些功能。如在微博中的共同好友,通过Redis的set能够很方便得出。

7、最新列表

Redis列表结构,LPUSH可以在列表头部插入一个内容ID作为关键字,LTRIM可用来限制列表的数量,这样列表永远为N个ID,无需查询最新的列表,直接根据ID去到对应的内容页即可。

8、消息系统

消息队列是大型网站必用中间件,如ActiveMQ、RabbitMQ、Kafka等流行的消息队列中间件,主要用于业务解耦、流量削峰及异步处理实时性低的业务。Redis提供了发布/订阅及阻塞队列功能,能实现一个简单的消息队列系统。另外,这个不能和专业的消息中间件相比。

27. Redis的数据类型有哪些?

有五种常用数据类型:String、Hash、Set、List、SortedSet。以及三种特殊的数据类型:Bitmap、HyperLogLog、Geospatial ,其中HyperLogLog、Bitmap的底层都是 String 数据类型,Geospatial 的底层是 Sorted Set 数据类型。

五种常用的数据类型

1、String:String是最常用的一种数据类型,普通的key- value 存储都可以归为此类。其中Value既可以是数字也可以是字符串。使用场景:常规key-value缓存应用。常规计数: 微博数, 粉丝数。

2、Hash:Hash 是一个键值(key => value)对集合。Redishash 是一个 string 类型的 field 和 value 的映射表,hash 特别适合用于存储对象,并且可以像数据库中update一个属性一样只修改某一项属性值。

3、Set:Set是一个无序的天然去重的集合,即Key-Set。此外还提供了交集、并集等一系列直接操作集合的方法,对于求共同好友、共同关注什么的功能实现特别方便。

4、List:List是一个有序可重复的集合,其遵循FIFO的原则,底层是依赖双向链表实现的,因此支持正向、反向双重查找。通过List,我们可以很方面的获得类似于最新回复这类的功能实现。

5、SortedSet:类似于java中的TreeSet,是Set的可排序版。此外还支持优先级排序,维护了一个score的参数来实现。适用于排行榜和带权重的消息队列等场景。

三种特殊的数据类型

1、Bitmap:位图,Bitmap想象成一个以位为单位数组,数组中的每个单元只能存0或者1,数组的下标在Bitmap中叫做偏移量。使用Bitmap实现统计功能,更省空间。如果只需要统计数据的二值状态,例如商品有没有、用户在不在等,就可以使用 Bitmap,因为它只用一个 bit 位就能表示 0 或 1。

2、Hyperloglog。HyperLogLog 是一种用于统计基数的数据集合类型,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大

时,计算基数所需的空间总是固定 的、并且是很小的。每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。场景:统计网页的UV(即Unique Visitor,不重复访客,一个人访问某个网站多次,但是还是只计算为一次)。

要注意,HyperLogLog 的统计规则是基于概率完成的,所以它给出的统计结果是有一定误差的,标准误算率是 0.81%。

3、Geospatial :主要用于存储地理位置信息,并对存储的信息进行操作,适用场景如朋友的定位、附近的人、打车距离计算等。

28. 什么是缓存预热?

缓存预热是指系统上线后,提前将相关的缓存数据加载到缓存系统。避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题,用户直接查询事先被预热的缓存数据。

如果不进行预热,那么Redis初始状态数据为空,系统上线初期,对于高并发的流量,都会访问到数据库中, 对数据库造成流量的压力。

缓存预热解决方案:

  • 数据量不大的时候,工程启动的时候进行加载缓存动作;

  • 数据量大的时候,设置一个定时任务脚本,进行缓存的刷新;

  • 数据量太大的时候,优先保证热点数据进行提前加载到缓存。

29. 什么是缓存降级?

缓存降级是指缓存失效或缓存服务器挂掉的情况下,不去访问数据库,直接返回默认数据或访问服务的内存数据。降级一般是有损的操作,所以尽量减少降级对于业务的影响程度。

在进行降级之前要对系统进行梳理,看看系统是不是可以丢卒保帅;从而梳理出哪些必须誓死保护,哪些可降级;比如可以参考日志级别设置预案:

  • 一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;

  • 警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警;

  • 错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;

  • 严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。
    持优先级排序,维护了一个score的参数来实现。适用于排行榜和带权重的消息队列等场景。

三种特殊的数据类型

1、Bitmap:位图,Bitmap想象成一个以位为单位数组,数组中的每个单元只能存0或者1,数组的下标在Bitmap中叫做偏移量。使用Bitmap实现统计功能,更省空间。如果只需要统计数据的二值状态,例如商品有没有、用户在不在等,就可以使用 Bitmap,因为它只用一个 bit 位就能表示 0 或 1。

2、Hyperloglog。HyperLogLog 是一种用于统计基数的数据集合类型,HyperLogLog 的优点是,在输入元素的数量或者体积非常非常大

时,计算基数所需的空间总是固定 的、并且是很小的。每个 HyperLogLog 键只需要花费 12 KB 内存,就可以计算接近 2^64 个不同元素的基 数。场景:统计网页的UV(即Unique Visitor,不重复访客,一个人访问某个网站多次,但是还是只计算为一次)。

要注意,HyperLogLog 的统计规则是基于概率完成的,所以它给出的统计结果是有一定误差的,标准误算率是 0.81%。

3、Geospatial :主要用于存储地理位置信息,并对存储的信息进行操作,适用场景如朋友的定位、附近的人、打车距离计算等。

28. 什么是缓存预热?

缓存预热是指系统上线后,提前将相关的缓存数据加载到缓存系统。避免在用户请求的时候,先查询数据库,然后再将数据缓存的问题,用户直接查询事先被预热的缓存数据。

如果不进行预热,那么Redis初始状态数据为空,系统上线初期,对于高并发的流量,都会访问到数据库中, 对数据库造成流量的压力。

缓存预热解决方案:

  • 数据量不大的时候,工程启动的时候进行加载缓存动作;

  • 数据量大的时候,设置一个定时任务脚本,进行缓存的刷新;

  • 数据量太大的时候,优先保证热点数据进行提前加载到缓存。

29. 什么是缓存降级?

缓存降级是指缓存失效或缓存服务器挂掉的情况下,不去访问数据库,直接返回默认数据或访问服务的内存数据。降级一般是有损的操作,所以尽量减少降级对于业务的影响程度。

在进行降级之前要对系统进行梳理,看看系统是不是可以丢卒保帅;从而梳理出哪些必须誓死保护,哪些可降级;比如可以参考日志级别设置预案:

  • 一般:比如有些服务偶尔因为网络抖动或者服务正在上线而超时,可以自动降级;

  • 警告:有些服务在一段时间内成功率有波动(如在95~100%之间),可以自动降级或人工降级,并发送告警;

  • 错误:比如可用率低于90%,或者数据库连接池被打爆了,或者访问量突然猛增到系统能承受的最大阀值,此时可以根据情况自动降级或者人工降级;

  • 严重错误:比如因为特殊原因数据错误了,此时需要紧急人工降级。

;