《Java 后端面试经》专栏文章索引:
《Java 后端面试经》Java 基础篇
《Java 后端面试经》Java EE 篇
《Java 后端面试经》数据库篇
《Java 后端面试经》多线程与并发编程篇
《Java 后端面试经》JVM 篇
《Java 后端面试经》操作系统篇
《Java 后端面试经》设计模式篇
《Java 后端面试经》计算机网络篇
《Java 后端面试经》 数据库篇
- 索引的基本原理
- Mysql 聚集和非聚集索引的区别
- MySQL 索引的数据结构,各自优劣?
- 索引设计的原则
- 请简述常用的索引有哪些种类?
- MySQL 索引失效的几种情况?
- MySQL 数据库中索引的工作机制是什么?
- 数据库索引的原理,为什么是 B+ 树?
- 唯一索引和主键索引的区别,唯一、主键索引与聚簇、非聚簇索引的关系?
- 什么是三星索引?
- 什么是最左匹配原则?
- 谈谈覆盖索引
- InnoDB 一棵 B+ 树可以存放多少行数据?
- 什么是锁,锁的作用是什么?
- lock 锁和 latch 锁的区别
- Mysql 锁的类型有哪些?
- MySQL 执行计划怎么看
- MySQL 常用函数有哪些
- 什么是事务
- 事务的基本特性和隔离级别
- 关心过业务系统里面的 SQL 耗时嘛?统计过慢查询嘛?对慢查询都怎么优化过?
- ACID 靠什么保证的?
- 什么是 MVCC
- 分表后非 sharding_key 的查询怎么处理,分表后的排序?
- MySQL 主从同步原理
- MySQL 中有哪些存储引擎?
- 简述 MyISAM 和 InnoDB 的区别
- 数据表设计时,字段如何选择?
- MySQL 中 VARCHAR(M) 最多存储多少数据?
- 简述 MySQL 中索引类型及对数据库的性能的影响
- 如何提高 insert 的性能?
- 说说对 Redis 的了解
- Redis 有哪些数据类型?
- 什么是 Redis 持久化?Redis 有哪几种持久化方式?优缺点是什么?
- Redis 数据过期后的删除策略?
- Redis 的数据淘汰策略有哪些
- 什么是缓存穿透?如何避免?什么是缓存击穿,如何避免?什么是缓存雪崩?如何解决?
索引的基本原理
索引的用途:
索引是用来快速地寻找那些具有特定值的记录,是帮助 MySQL 高效获取数据的一种数据结构。如果没有索引,执行查询时需要遍历整张表。
索引的基本原理:就是把无序的查询变成有序的查询:
- 根据索引列的内容进行排序。
- 根据排序结果生成倒排表。
- 在倒排表内容上拼上数据地址链。
- 在查询的时候,先拿到倒排表内容,再取出数据地址链,从而拿到具体数据。
Mysql 聚集和非聚集索引的区别
无论是聚集索引还是非聚集索引,其内部都是 B+ 树,即高度平衡的多叉树,叶子节点存放着所有的数据。
聚集索引:将数据存储与索引放到了一块,并且是按照一定的顺序组织的,找到索引也就找到了数据,数据的物理存放顺序与索引顺序是一致的,即:只要索引是相邻的,那么对应的数据也一定是相邻地存放在磁盘上的。一句话就是,聚集索引能够在 B+ 树索引的叶子节点上直接找到数据。
非聚集索引:叶子节点不存储数据,存储的是数据行地址,也就是说根据索引查找到数据行的位置再去磁盘查找数据,这个就有点类似一本书的目录,比如我们要找第三章第一节,那我们先在这个目录里面找,找到对应的页码后再去对应的页码看文章。
聚集索引和非聚集索引的区别:
- 一个表中只能拥有一个聚集索引,而非聚集索引一个表可以存在多个。
- 聚集索引,索引中键值的逻辑顺序决定了表中相应行的物理顺序;非聚集索引,索引中索引的逻辑顺序与磁盘上行的物理存储顺序不同。
- 索引是通过二叉树的数据结构来描述的,我们可以这么理解聚集索引:聚集索引的叶子节点就是数据节点。而非聚集索引的叶节点仍然是索引节点,只不过有一个指针指向对应的数据块。
- 聚集索引:物理存储按照索引排序;非聚集索引:物理存储不按照索引排序;
聚集索引的优势:
- 查询通过聚集索引可以直接获取数据,相比非聚集索引需要第二次查询(非覆盖索引的情况下)效率要更高。
- 由于定义了数据的逻辑顺序,聚集索引对于范围值查询的效率很高。
- 聚集索引适合用在排序的场合,非聚集索引不适合。
聚簇索引的劣势:
- 维护索引很昂贵,特别是插入新行或者主键被更新导致要分页(page split)的时候,建议在大量插入新行后,选在负载较低的时间段,通过 OPTIMIZE TABLE 优化表,因为必须被移动的行数据可能造成碎片,使用独享表空间可以弱化碎片。
- 表因为使用 UUID(随机 ID)作为主键,使数据存储稀疏,这就会出现聚簇索引有可能有比全表扫描更慢,所以建议使用 int 的 auto_increment 作为主键。
- 如果主键比较大的话,要辅助索引将会变得更大,因为辅助索引的叶子存储在的是主键值,过长的主键值,会导致非叶子节点占用更多的物理空间。
InnoDB 中一定有主键,主键一定是聚集索引,不手动设置,则会使用 unique 索引,没有 unique 索引,则会使用数据库内部的一个行的隐藏 id 来当作主键索引。在聚集索引之上创建的索引称为辅助索引,辅助索引访问数据总是需要二次查找,非聚集索引都是辅助索引,像复合索引、前缀索引、唯一索引,辅助索引叶子节点存储的不再是行的物理位置,而是主键值。
MyISAM 使用的是非聚集索引,没有聚集索引,非聚集索引的两棵 B+ 树看上去没有什么不同,节点的结构完全一致只是存储的内容不同而已,主键索引 B+ 树的节点存储了主键,辅助键索引 B+ 树存储了辅助键。表数据存储在独立的地方,这两棵 B+ 树的叶子节点都使用一个地址指向真正的表数据,对于表数据来说,这两个键没有任何差别,由于索引树是独立的,通过辅助键检索无需访问主键的索引树。
如果涉及到大数据量的排序、全表扫描、count 之类的操作的话,还是 MyISAM 占优势些,因为索引所占空间小,这些操作是需要在内存中完成的。
MySQL 索引的数据结构,各自优劣?
索引的数据结构和具体存储引擎的实现有关,在 InnoDB 中使用较多的索引有 Hash 索引,B+ 树索引和全文索引。InnoDB 存储引擎的默认索引实现为:B+ 树索引。对于哈希索引来说,底层的数据结构就是哈希表,因此在绝大多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余大部分场景,建议选择 B+ 树索引。
B+ 树是一个平衡的多叉树,从根节点到每个叶子节点的高度差值不超过 1,而且同层级的节点间有指针相互链接。在 B+ 树上的常规检索,从根节点到叶子节点的搜索效率基本相当,不会出现大幅波动,而且基于索引的顺序扫描时,也可以利用双向指针快速左右移动,效率非常高。因此,B+ 树索引被广泛应用于数据库、文件系统等场景。
哈希索引就是采用一定的哈希算法,把键值换算成新的哈希值,检索时不需要类似 B+ 树那样从根节点到叶子节点逐级查找,只需一次哈希算法即可立刻定位到相应的位置,速度非常快。
如果是等值查询,那么哈希索引明显有绝对优势,因为只需要经过一次算法即可找到相应的键值。前提是键值都是唯一的。如果键值不是唯一的,就需要先找到该键所在位置,然后再根据链表往后扫描,直到找到相应的数据。
如果是范围查询检索,这时候哈希索引就毫无用武之地了,因为原先是有序的键值,经过哈希算法后,有可能变成不连续的了,就没办法再利用索引完成范围查询检索。
哈希索引也没办法利用索引完成排序,以及 like ‘xxx%’ 这样的部分模糊查询(这种部分模糊查询,其实本质上也是范围查询)。
哈希索引也不支持多列联合索引的最左匹配规则。B+ 树索引的关键字检索效率比较平均,不像 B 树那样波动幅度大,在有大量重复键值情况下,哈希索引的效率也是极低的,因为存在哈希冲突问题。
速记版:
Hash 与 B+ 树的区别
- B+ 树可以进行范围查询,Hash 索引不能。
- B+ 树支持联合索引的最左侧原则,Hash 索引不支持。
- B+ 树支持 order by 排序,Hash 索引不支持。
- Hash 索引在等值查询上比 B+ 树效率更高。
- B+ 树使用 like 进行模糊查询的时候,like 后面(比如%开头)的话可以起到优化的作用,Hash 索引根本无法进行模糊查询。
当存储同数量级的数据的时候,B+ 树的高度比 B 树的高度小,这样的话进程 I/O 操作的次数就少,效果就高。因为 B+ 树的所有非叶子节点只存索引,数据存在叶子节点,一般3层的树高度,即可存千万级别的数据,而 B 树不行。(具体的计算可以到网上去看看,有面试官可能会问你怎么算出来的。
索引设计的原则
查询更快,占用空间更小:
- 适合索引的列是出现在 where 子句中的列,或者出现连接子句中指定的列。
- 基数较小的列,索引效果较差,没有必要在此列建立索引。
- 使用短索引,如果对长字符串列进行索引,应该指定一个前缀长度,这样能够节省大量索引空间,如果搜索词超过索引前缀长度,则使用索引排除不匹配的行,然后检查其余行是否可能匹配。
- 不要过度索引。索引需要额外的磁盘空间,并降低写操作的性能。在修改表内容的时候,索引会进行更新甚至重构,索引列越多,这个时间就会越长,所以只保持需要的索引有利于查询即可。
- 定义有外键的数据列一定要建立索引。
- 更新频繁字段不适合创建索引。
- 若是不能有效区分数据的列不适合做索引列(如性别,男女未知,最多也就三种,区分度是在太低)。
- 尽量的扩展索引,不要新建索引。比如表中已经有 a 的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。
- 对于那些查询中很少涉及的列,重复值比较多的列不要建立索引。
- 对于定义为 text、image 和 bit 的数据类型的列不要建立索引。
请简述常用的索引有哪些种类?
- 普通索引:即针对数据库表创建索引。
- 唯一索引:与普通索引类似,不同的就是:MySQL 数据库索引列的值必须唯一,但允许有空值。
- 主键索引:它是一种特殊的唯一索引,不允许有空值。一般是在建表的时候同时创建主键索引。
- 联合索引:即将数据库表中的多个字段联合起来作为一个组合索引。为了进一步提高 MySQL 的效率,就要考虑建立联合索引。
MySQL 索引失效的几种情况?
- 查询条件中有 or,除非所有的查询条件都建有索引,否则索引失效。
- 使用 like 进行模糊查询时,% 在关键词前。
- 违背最左匹配原则。
- 索引字段作为查询条件时,使用了计算或者函数。
- is not null 会导致索引失效。
MySQL 数据库中索引的工作机制是什么?
数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中数据。
数据库索引的原理,为什么是 B+ 树?
可以从几个维度去看这个问题,查询是否够快,效率是否稳定,存储数据多少,以及查找磁盘次数,为什么不是二叉树,为什么不是平衡二叉树,为什么不是B树,而偏偏是 B+ 树呢?
为什么不是一般二叉树?
如果二叉树特殊化为一个链表,相当于全表扫描。平衡二叉树相比于二叉查找树来说,查找效率更稳定,总体的查找速度也更快。
为什么不是平衡二叉树呢?
我们知道,在内存比在磁盘的数据,查询效率快得多。如果树这种数据结构作为索引,那我们每查找一次数据就需要从磁盘中读取一个节点,也就是我们说的一个磁盘块,但是平衡二叉树可是每个节点只存储一个键值和数据的,如果是 B 树,可以存储更多的节点数据,树的高度也会降低,因此读取磁盘的次数就降下来啦,查询效率就快啦。
那为什么不是 B 树而是 B+ 树呢?
-
B+ 树非叶子节点上是不存储数据的,仅存储键值,而 B 树节点中不仅存储键值,也会存储数据。InnoDB 中页的默认大小是 16KB,如果不存储数据,那么就会存储更多的键值,相应的树的阶数(节点的子节点树)就会更大,树就会更矮更胖,如此一来我们查找数据进行磁盘的 IO 次数又会再次减少,数据查询的效率也会更快。
-
B+ 树索引的所有数据均存储在叶子节点,而且数据是按照顺序排列的,链表连着的。那么B+ 树使得范围查找,排序查找,分组查找以及去重查找变得异常简单。而 B 树每个节点 key 和 data 在一起,则无法区间查找。
唯一索引和主键索引的区别,唯一、主键索引与聚簇、非聚簇索引的关系?
唯一索引与主键索引:
- 唯一索引是在表上一个或者多个字段组合建立的索引,这个(或这几个)字段的值组合起来在表中不可以重复。一张表可以建立任意多个唯一索引,但一般只建立一个。
- 主键是一种特殊的唯一索引,区别在于,唯一索引列允许 null 值,而主键列不允许为 null值。一张表最多建立一个主键,也可以不建立主键。
聚簇索引、非聚簇索引、主键索引:
聚簇索引的叶子节点就是数据节点,而非聚簇索引的叶子节点仍然是索引节点,只不过有指
向对应数据块的指针。
聚簇索引的顺序,就是数据在硬盘上的物理顺序。一般情况下主键就是默认的聚簇索引。
一张表只允许存在一个聚簇索引,因为真实数据的物理顺序只能有一种。
如果一张表上还没有聚簇索引,为它新创建聚簇索引时,就需要对已有数据重新进行排序,所以对表进行修改速度较慢是聚簇索引的缺点,对于经常更新的列不宜建立聚簇索引。
聚簇索引性能最好,因为一旦具有第一个索引值的记录被找到,具有连续索引值的记录也一
定物理地紧跟其后。一张表只能有一个聚簇索引,所以非常珍贵,必须慎重设置,一般要根
据这个表最常用的 SQL 查询方式选择某个(或多个)字段作为聚簇索引(或复合聚簇索
引)。
聚簇索引与唯一索引:
严格来说,聚簇索引不一定是唯一索引,聚簇索引的索引值并不要求是唯一的,唯一聚簇索
引才是!在一个有聚簇索引的列上是可以插入两个或多个相同值的,这些相同值在硬盘上的
物理排序与聚簇索引的排序相同,仅此而已。
什么是三星索引?
对于一个查询而言,一个三星索引,可能是其最好的索引。如果查询使用三星索引,一次查询通常只需要进行一次磁盘随机读以及一次窄索引片的扫描,因此其相应时间通常比使用一个普通索引的相应时间少几个数量级。
一个查询相关的索引行是相邻的或者至少相距足够靠近的则获得一星。如果索引中的数据顺序和查找中的排列顺序一致则获得二星,如果索引中的列包含了查询中需要的全部列则获得三星。三星索引在实际的业务中如果无法同时达到,一般我们认为第三颗星最重要,第一和第二颗星重要性差不多,根据业务情况调整这两颗星的优先度。
什么是最左匹配原则?
最左前缀匹配原则和联合索引的索引存储结构和检索方式是有关系的。
索引的底层是一颗 B+ 树,那么联合索引当然还是一颗 B+ 树,只不过联合索引的健值数量不是一个,而是多个。但是构建一颗 B+ 树只能根据一个值来构建,因此数据库依据联合索引最左的字段来构建B+树。
在组合索引树中,最底层的叶子节点按照第一列 a 列从左到右递增排列,但是 b 列和 c 列是无
序的,b 列只有在 a 列值相等的情况下小范围内递增有序,而 c 列只能在 a,b 两列相等的情况
下小范围内递增有序。
就像上面的查询,B+ 树会先比较 a 列来确定下一步应该搜索的方向,往左还是往右。如果 a
列相同再比较 b 列。但是如果查询条件没有 a 列,B+ 树就不知道第一步应该从哪个节点查
起。
例子:假如创建一个(a,b) 的联合索引,那么它的索引树是这样的
可以看到a的值是有顺序的,1,1,2,2,3,3,而 b 的值是没有顺序的 1,2,1,4,1,2。所以 b = 2 这种查询条件没有办法利用索引,因为联合索引首先是按 a 排序的,b 是无序的。
同时我们还可以发现在 a 值相等的情况下,b 值又是按顺序排列的,但是这种顺序是相对的。所以最左匹配原则遇上范围查询就会停止,剩下的字段都无法使用索引。例如 a = 1 and b = 2, a, b 字段都可以使用索引,因为在 a 值确定的情况下 b 是相对有序的,而 a>1 and b=2,a 字段可以匹配上索引,但 b 值不可以,因为 a 的值是一个范围,在这个范围中 b 是无序的。
可以说创建的 idx_abc(a,b,c) 索引,相当于创建了 (a)、(a,b)(a,b,c)三个索引。
组合索引的最左前缀匹配原则:使用组合索引查询时,mysql 会一直向右匹配直至遇到范围查询(>、<、between、like) 就停止匹配。
谈谈覆盖索引
覆盖索引并不是说是索引结构,覆盖索引是一种很常用的优化手段。因为在使用辅助索引的
时候,我们只可以拿到主键值,相当于获取数据还需要再根据主键查询主键索引再获取到数
据。如果一个索引包含了满足查询语句中字段与条件的数据就叫做覆盖索引,即只需扫描索引而无须回表。
举例子:
假设我们只需要查询商品的名称、价格信息,我们有什么方式来避免回表呢?我们可以建立
一个组合索引,即商品编码、名称、价格作为一个组合索引。如果索引中存在这些数据,查
询将不会再次检索主键索引,从而避免回表。
从辅助索引中查询得到记录,而不需要通过聚集索引查询获得,MySQL 中将其称为覆盖索
引。使用覆盖索引的好处很明显,我们不需要查询出包含整行记录的所有信息,因此可以减
少大量的 I/O 操作。
通常在 InnoDB 中,除了查询部分字段可以使用覆盖索引来优化查询性能之外,统计数量也
会用到。例如, SELECT COUNT(*) 时,如果不存在辅助索引,此时会通过查询聚集索引来统计行数,如果此时正好存在一个辅助索引,则会通过查询辅助索引来统计行数,减少 I/O 操作。
InnoDB 一棵 B+ 树可以存放多少行数据?
约 2 千万行。
计算机在存储数据的时候,有最小存储单元,在计算机中磁盘存储数据最小单元是扇区(这就好比我们今天进行现金流通的最小单位是 1 毛),一个扇区的大小是 512 字节,而文件系统(例如,XFS/EXT4)的最小单元是块,一个块的大小是 4K,而对于我们的 InnoDB 存储引擎也有自己的最小存储单元—页(Page),一个页的大小是 16K.
InnoDB 的所有数据文件(后缀为 ibd 的文件),他的大小始终是 16384(16k) 的整数倍。
数据表中的数据都是存储在页中的,所以一个页中能存储多少行数据呢?假设一行数据的大小是 1k,那么一个页可以存放 16 行这样的数据。
对于 B+ 树而言,只有叶子节点存放数据,非叶子节点存放的是只保存索引信息和下一层节点的指针信息。一个非叶子节点能存放多少指针?
假设主键 ID 为常用的 bigint,长度为 8 字节,而指针大小在 InnoDB 源码中设置为 6 字节,这样一共 14 字节,我们在一个页中能存放多少这样的单元,其实就代表有多少指针,即 16384/14 = 1170 个。
一颗高度为 2 的 B+ 树,存在一个根节点和若干个叶子节点能存放 1170 * 16 = 18720 条这样的数据记录。
根据同样的原理,可以算出一个高度为 3 的 B+ 树可以存放:1170 * 1170 * 16 = 21902400 条记录。
什么是锁,锁的作用是什么?
锁是数据库系统区别文件系统的一个关键特性,锁机制用于管理对共享资源的并发访问,保持数据的完整性和一致性。
lock 锁和 latch 锁的区别
数据库中有表锁和行锁等。
lock 锁:锁的对象是事务,用于锁定数据库中的对象,如表、页、行等,并且 lock 锁一般在
commit 或 rollback 后释放,有死锁机制。
latch 锁:一般称为轻量级锁,要求锁定的时间必须非常短,在 InnoDB 中又可以分为 mutex (互斥量) 和 rwlock (读写锁)。目的是用来保证并发线程操作临界资源的正确性,并且通常没有死锁检测的机制。
Mysql 锁的类型有哪些?
基于锁的属性分类:共享锁和排他锁。
基于锁的粒度分类:行级锁(INNODB)、表级锁(INNODB、MYISAM)、页级锁(BDB 引擎)、记录锁、间隙锁、临建锁。
基于锁的状态分类:意向共享锁、意向排他锁。
1、共享锁(Share Lock)
共享锁又称读锁,简称 S 锁,当一个事务为数据加上读锁之后,其他事务只能对该数据加读锁,而不能对数据加写锁,直到所有的读锁释放之后其他事务才能对其进行加持写锁。共享锁的特性主要是为了支持并发的读取数据,读取数据的时候不支持修改,避免出现重复读的问题。
2、排他锁(Exclusive Lock)
排他锁又称写锁,简称 X
锁,当一个事务为数据加上写锁时,其他请求将不能再为数据加任何锁,直到该锁释放之后,其他事务才能对数据进行加锁,排他锁的目的是在数据修改时候,不允许其他人同时修改,也不允许其他人读取,避免了出现脏数据和脏读数据的问题。
3、表锁
表锁是指上锁的时候锁住的是整个表,当下一个事务访问该表的时候,必须等前一个事务释放了锁才能进行对表进行访问。
特点:粒度大,加锁简单,容易冲突。
4、行锁
行锁是指上锁的时候锁住的是表的某一行或多行记录,其他事务访问同一张表时,只有被锁住的是记录不能访问,其他的记录可正常访问。
特点:粒度小,加锁比表锁麻烦,不容易冲突,相比表锁支持的并发要高。
5、记录锁(Record Lock)
记录锁也属于行锁中的一种,只不过记录锁的范围只是表中的某一条记录,记录锁是说事务在加锁后锁住的只是表的某一条记录。
精准条件命中,并且命中的条件字段是唯一索引。
加了记录锁之后数据可以避免数据在查询的时候被修改的重复读问题,也避免了在修改的事务未提交前被其他事务读取的脏读问题。
6、页锁
页级锁是 MySQL中锁定粒度介于行级锁和表锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折中的页级,一次锁定相邻的一组记录。
特点:开销和加锁时间介于表锁和行锁之间,会出现死锁,锁定粒度介于表锁和行锁之间,并发度一般。
7、间隙锁(Gap Lock)
属于行锁中的一种,间隙锁是在事务加锁后其锁住的是表记录的某一区间,当表的相邻 ID 之间出现空隙会形成一个区间,遵循左开右闭原则。
范围查询并且查询未命中记录,查询条件必须命中索引,间隙锁只会出现在 REPEATABLE_READ(重复读)的事务级别中。
触发条件:防止幻读问题,事务并发的时候,如果没有间隙锁,就会发生如下的问题,在同一个事务里,A 事务的两次查询出的结果会不一样。
比如表里面的数据 ID 为 1,4,5,7,10,那么会形成以下几个空隙区间,-n-1 区间,1-4 区间,7-10 区间,10-n 区间(-n 代表负无穷大,n 代表正无穷大)。
8、临建锁(Next-Key Lock)
也属于行锁的一种,并且它是 INNODB 的行锁默认算法,总结来说就是记录锁和间隙锁的组合,临建锁会把查询出来的记录锁,同时也会把该范围查询内的所有间隙空间也会锁住,再之它会把相邻的下一个区间也会锁住。
触发条件:范围查询并命中,查询命中了索引。
结合记录锁和间隙锁的特性,临建锁避免了在范围查询时出现脏读、重复读、幻读问题,加了临建锁之后,在范围区间内数据不允许被修改和锁入。
如果当事务 A 加锁成功之后就设置一个状态告诉后面的人,已经有人对表里的行加了一个排他锁了,你们不能对整个表加共享锁或排他锁了,那么后面需要对整个表加锁的人只需要获取整个状态就知道自己是不是可以对表加锁,避免了对整个索引树的每个节点扫描是否加锁,而这个状态就是意向锁。
意向共享锁:当一个事务试图对整个表进行加共享锁之后,首先需要这个表的意向共享锁。
意向排他锁:当一个事务试图对整个表进行加排他锁之前,首先需要获得这个表的意向排他锁。
MySQL 执行计划怎么看
执行计划就是 SQL 的执行查询的顺序,以及如何使用索引查询,返回的结果集的行数。
explain select * from table where x = ? and y = ?
- id :是一个有顺序的编号,是查询的顺序号,有几个 select 就显示几行。id 的顺序是按 select 出现的顺序增长的。id 列的值越大执行优先级越高越先执行,id 列的值相同则从上往下执行,id列的值为 NULL 最后执行。
- selectType 表示查询中每个 select 子句的类型
- SIMPLE: 表示此查询不包含 UNION 查询或子查询
- PRIMARY: 表示此查询是最外层的查询(包含子查询)
- SUBQUERY: 子查询中的第一个 SELECT
- UNION: 表示此查询是 UNION 的第二或随后的查询
- DEPENDENT UNION: UNION 中的第二个或后面的查询语句, 取决于外面的查询
- UNION RESULT, UNION 的结果
- DEPENDENT SUBQUERY: 子查询中的第一个 SELECT, 取决于外面的查询. 即子查询依赖于外层查询的结果.
- DERIVED:衍生,表示导出表的SELECT(FROM子句的子查询)
- table:表示该语句查询的表。
- type:优化sql的重要字段,也是我们判断 sql 性能和优化程度重要指标。他的取值类型范围。
const:通过索引一次命中,匹配一行数据。
system: 表中只有一行记录,相当于系统表。
eq_ref:唯一性索引扫描,对于每个索引键,表中只有一条记录与之匹配。
ref: 非唯一性索引扫描,返回匹配某个值的所有。
range: 只检索给定范围的行,使用一个索引来选择行,一般用于between、<、>。
index: 只遍历索引树。
ALL: 表示全表扫描,这个类型的查询是性能最差的查询之一。 那么基本就是随着表的数量增多,执行效率越慢。
执行效率:
ALL < index < range< ref < eq_ref < const < system. 最好是避免 ALL 和 index. - possible_keys:它表示Mysql在执行该sql语句的时候,可能用到的索引信息,仅仅是可能,实际不一定会用到
- key:此字段是 mysql 在当前查询时所真正使用到的索引。 他是 possible_keys 的子集
- key_len:表示查询优化器使用了索引的字节数,这个字段可以评估组合索引是否完全被使用,这也是我们优化sql时,评估索引的重要指标
- rows:mysql 查询优化器根据统计信息,估算该sql返回结果集需要扫描读取的行数,这个值相关重要,索引优化之后,扫描读取的行数越多,说明索引设置不对,或者字段传入的类型之类的问题,说明要优化空间越大
- filtered:返回结果的行占需要读到的行(rows列的值)的百分比,就是百分比越高,说明需要查询到数据越准确, 百分比越小,说明查询到的数据量大,而结果集很少。
- extra
using filesort :表示 mysql 对结果集进行外部排序,不能通过索引顺序达到排序效果。一般有using filesort都建议优化去掉,因为这样的查询 cpu 资源消耗大,延时大。
using index:覆盖索引扫描,表示查询在索引树中就可查找所需数据,不用扫描表数据文件,往往说明性能不错。
using temporary:查询有使用临时表, 一般出现于排序, 分组和多表 join 的情况, 查询效率不高,建议优化。
using where :sql使用了where过滤,效率较高。
MySQL 常用函数有哪些
聚集函数: avg、count、max、min、sum.
用于处理字符串的函数:
- 合并字符串函数:concat(str1,str2,str3…)
- 比较字符串大小函数:strcmp(str1,str2)
- 获取字符串字节数函数:length(str)
- 获取字符串字符数函数:char_length(str)
- 字母大小写转换函数:大写:upper(x),ucase(x);小写lower(x),lcase(x)
用于处理数值的函数:
- 绝对值函数:abs(x)
- 向上取整函数:ceil(x)
- 向下取整函数:floor(x)
- 取模函数:mod(x,y)
- 随机数函数:rand()
- 四舍五入函数:round(x,y)
- 数值截取函数:truncate(x,y)
用于处理时间日期的函数:
- 获取当前日期:curdate(),current_date()
- 获取当前时间:curtime(),current_time()
- 获取当前日期时间:now()
- 从日期中选择出月份数:month(date),monthname(date)
- 从日期中选择出周数:week(date)
什么是事务
事务是数据库区别于文件系统的重要特性之一,事务可以一条非常简单的 SQL 语句组成,也可以由一组复杂的 SQL 语句组成,事务是访问并更新数据库中各种数据项的一个程序执行单元。在事务中的操作,要么都做修改,要么都不做,这就是事务的主要目的。
事务的基本特性和隔离级别
事务基本特性 ACID 分别是:
原子性指的是一个事务中的操作要么全部成功,要么全部失败。
一致性指的是数据库总是从一个一致性的状态转换到另外一个一致性的状态。比如 A 转账给 B 100 块钱,假设 A 只有 90 块,支付之前我们数据库里的数据都是符合约束的。但是如果事务执行成功了,我们的数据库数据就破坏约束了,因此事务不能成功,这里我们说事务提供了一致性的保证。
隔离性指的是一个事务的修改在最终提交前,对其他事务是不可见的。
持久性指的是一旦事务提交,所作的修改就会永久保存到数据库中。
-
read uncommit 未提交读,在事务 A 读取数据时,事务 B 读取和修改数据加了共享锁。这种隔离级别,会导致脏读、不可重复读以及幻读。
-
read commit 已提交读,在事务 A 读取数据时增加了共享锁,一旦读取,立即释放锁,事务 B 读取修改数据时增加了行级排他锁,直到事务结束才释放锁。也就是说,事务 A 在读取数据时,事务 B 只能读取数据,不能修改。当事务 A 读取到数据后,事务 B才能修改。这种隔离级别,可以避免脏读,但依然存在不可重复读以及幻读的问题。MySQL 数据库默认的隔离级别是可重复读。
-
repeatable read 可重复读,在事务 A 读取数据时增加了共享锁,事务结束,才释放锁,事务 B 读取、修改数据时增加了行级排他锁,直到事务结束才释放锁。也就是说,事务 A 在没有结束事务时,事务 B 只能读取数据,不能修改。当事务 A 结束事务,事务 B 才能修改。这种隔离级别,可以避免脏读、不可重复读,但依然存在幻读的问题。
-
serializable 串行,在事务 A 读取数据时增加了共享锁,事务结束,才释放锁,事务 B 读取修改数据时增加了表级排他锁,直到事务结束才释放锁。可序列化解决了脏读、不可重复读、幻读等问题,但隔离级别越来越高的同时,并发性会越来越低。
脏读(Dirty Read): 事务 A、B 交替执行,事务 A 被事务 B 干扰到了,因为事务 A 读取到事务 B 未提交的数据,这就是脏读。
不可重复读(Non-repeatable read): 在一个事务范围内,两个相同的查询,读取同一条记录,却返回了不同的数据,这就是不可重复读。
幻读(Phantom Read): 事务 A 查询一个范围的结果集,另一个并发事务 B 往这个范围中插入、删除了数据,并静悄悄地提交,然后事务 A 再次查询相同的范围,两次读取得到的结果集不一样了,这就是幻读。
关心过业务系统里面的 SQL 耗时嘛?统计过慢查询嘛?对慢查询都怎么优化过?
在业务系统中,除了使用主键进行的查询,其他的都会在测试库上测试其耗时,慢查询的统计主要由运维在做,会定期将业务中的慢查询反馈给我们。
慢查询的优化首先要搞明白慢查询的原因是什么?是查询条件没有命中索引?是 load 了不需要的数据列?还是数据量太大?
所以优化也是针对这三个方向来的:
- 首先分析语句,看看是否 load 了额外的数据,可能是查询了多余的行并且抛弃掉了,可能是加载了许多结果中并不需要列,对语句进行分析以及重写。
- 分析语句的执行计划,然后获得其使用索引的情况,之后修改语句或者修改索引,使得语句可以尽可能的命中索引。
- 如果对语句的优化已经无法进行,可以考虑表中的数据量是否太大,如果是的话可以进行横向或者纵向的分表。
ACID 靠什么保证的?
-
Atom 原子性由 undo log 日志保证,它记录了需要回滚的日志信息,事务回滚时撤销已经执行成功的 SQL.
-
Consistency 一致性是由其他三大特性保证,程序代码要保证业务上的一致性。
-
Isolation 隔离性由 MVCC 来保证。
-
Durability 持久性由内存 + redo log 来保证,mysql 修改数据同时在内存和 redo log 记录这次操作,宕机的时候可以从 redo log 恢复。
InnoDB redo log 写盘,InnoDB 事务进入 prepare 状态。
如果前面 prepare 成功,binlog 写盘,再继续将事务日志持久化到 binlog,如果持久化成功,那么 InnoDB 事务则进入 commit 状态(在 redo log 里面写入一个 commit 记录)
redolog 的刷盘会在系统空闲时进行。
什么是 MVCC
多版本并发控制:读取数据时通过一种类似快照的方式将数据保存下来,这样读锁就和写锁不冲突了,不同的事务 session 会看到自己特定版本的数据,叫做版本链。
MVCC 只在 READ COMMITTED(已提交读) 和 REPEATABLE READ(可重复读) 两个隔离级别下工作。其他两个隔离级别(未提交读和串行化)和 MVCC 不兼容,因为 READ UNCOMMITTED(未提交读)总是读取最新的数据行,而不是符合当前事务版本的数据行。而 SERIALIZABLE(串行化) 则会对所有读取的行都加锁。
聚集索引记录中有两个必要的隐藏列:
trx_id:用来存储每次对某条聚集索引记录进行修改的时候的事务 id.
roll_pointer:每次对哪条聚集索引记录有修改的时候,都会把老版本写入 undo log 日志中,这个 roll_pointer 就是存了一个指针,它指向这条聚集记录的上一个版本的位置,通过它来获得上一个版本的记录信息。(注意插入操作的 undo log 日志没有这个属性,因为它没有老版本)
已提交读和可重复读的区别就在于它们生成 ReadView 的策略不同。
开始事务时创建 Readview,Readview 维护当前活动的事务 id,即未提交的事务 id,排序生成一个数组。访问数据,获取数据中的事务 id(获取的是事务 id 最大的记录),对比 Readview.
如果在 Readview 的左边(比 Readview 都小),可以访问(在左边意味着该事务已经提交)。
如果在 Readview 的右边(比 Readview 都大),或者就在 Readview 中,不可以访问,获取 roll_pointer,取上一版本重新对比(在右边意味着该事务在 Readview 生成之后出现,在 Readview 中意味着该事务还未提交)。
已提交读隔离级别下的事务在每次查询的开始都会生成一个独立的 Readview,而可重复读隔离级别则是在第一次读的时候生成一个 readview,之后的读都复用之前的 readview. 这就是 MySQL 的 MVVC,通过版本链,实现多版本,可并发读-写,写-读。通过 readview 生成策略的不同实现不同的隔离级别。
分表后非 sharding_key 的查询怎么处理,分表后的排序?
- 可以做一个 mapping 表,比如这时候商家要查询订单列表怎么办呢?不带 user_id 查询的话你总不能扫全表吧?所以我们可以做一个映射关系表,保存商家和用户的关系,查询的时候先通过商家查询到用户列表,再通过 user_id 去查询。
- 宽表,对数据实时性要求不是很高的场景,比如查询订单列表,可以把订单表同步到离线(实时)数仓,再基于数仓去做成一张宽表,再基于其他如 es 提供查询服务。
- 数据量不是很大的话,比如后台的一些查询之类的,也可以通过多线程扫表,然后再聚合结果的方式来做,或者异步的形式也是可以的。
排序字段是唯一索引:
- 首先第一页的查询:将各表的结果集进行合并,然后再次排序。
- 第二页及以后的查询,需要传入上一页排序字段的最后一个值,及排序方式。
- 根据排序方式,及这个值进行查询。如排序字段 date,上一页最后值为 3,排序方式降序。查询的时候 sql 为 select … from table where date < 3 order by date desc limit 0,10。这样再将几个表的结果合并排序即可。
MySQL 主从同步原理
MySQL 主从同步的过程:
MySQL 的主从复制中主要有三个线程:master(binlog dump thread)、slave(I/O thread、SQL thread),master 一条线程和 slave 中的两条线程。
- 主节点 binlog,主从复制的基础是主库记录数据库的所有变更记录到 binlog。binlog 是数据库服务器启动的那一刻起,保存所有修改数据库结构或内容的一个文件。
- 主节点 log dump 线程,当 binlog 有变动时,log dump 线程读取其内容并发送给从节点。
- 从节点 I/O 线程接收 binlog 内容,并将其写入到 relay log 文件中。
- 从节点的 SQL 线程读取 relay log 文件内容对数据更新进行重放,最终保证主从数据库的一致性。
注:主从节点使用 binlog 文件 + position 偏移量来定位主从同步的位置,从节点会保存其已接收到的偏移量,如果从节点发生宕机重启,则会自动从 position 的位置发起同步。
由于 MySQL 默认的复制方式是异步的,主库把日志发送给从库后从不关心从库是否已经处理,这样会产生一个问题就是假设主库挂了,从库处理失败了,这时候从库升为主库后,日志就丢失了,由此产生两个概念。
全同步复制
主库写入 binlog 后强制同步日志到库,所有的从库都执行完成后才返回给客户端,但是很显然这个方式的话性能会受到严重影响。
半同步复制
和全同步不同的是,半同步复制的逻辑是这样,从库写入日志成功后返回 ACK 确认给主库,主库收到至少一个从库的确认就认为写操作完成。
MySQL 中有哪些存储引擎?
MySQL 体系架构如下图:
从体系结构图中可以发现,MySQL 数据库区别于其他数据库的最重要的一个特点是其插件式的表存储引擎。
插件式存储引擎的好处是,每个存储引擎都有各自的特点,能够根据具体的应用建立不同存储殷勤表。
InnoDB 存储引擎
InnoDB 是 MySQL 的默认事务型存储引擎,也是最重要、使用最广泛的存储引擎。它被设计用来处理大量的短期(short-lived)事务,应该优先考虑 InnoDB 引擎。
MyISAM 存储引擎
在 MySQL 5.5.8 之前的版本,MyISAM 是默认的存储引擎。但是 MyISAM 不支持事务和行级锁,而且崩溃后无法安全恢复。同时 MyISAM 对整张表加锁,很容易因为表锁的问题导致典型的性能问题。
Memory 引擎
Memory 表至少比 MyISAM 表要快一个数量级,数据文件是存储在内存中。Memory 表的结构在重启后还会保留,但数据会丢失。Memory 表在很多场景可以发挥好的作用。
用于查找(lookup)或者映射(mapping)表,例如将邮编和州名映射的表。
用于缓存周期性聚合数据(periodically aggregated data)的结果。
用于保存数据分析中产生的中间数据。
Archive 引擎
Archive 存储引擎只支持 INSERT 和 SELECT 操作,会缓存所有的写并利用 zlib 对插入的行进行压缩,所以比 MyISAM 表的磁盘 I/O 更少。但是每次 SELECT 查询都需要执行全表扫描。所以 Archive 表更适合日志和数据采集类应用。
CSV 引擎
CSV 引擎可以将普通的 CSV 文件(逗号分隔值的文件)作为 MySQL 的表来处理,但这种表不支持索引。因此 CSV 引擎可以作为一种数据交换的机制,非常有用。
简述 MyISAM 和 InnoDB 的区别
- InnoDB 支持事务,MyISAM 不支持事务。
这是 MySQL 将默认存储引擎从 MyISAM 变成 InnoDB 的重要原因之一。 - InnoDB 支持外键,而 MyISAM 不支持。
对一个包含外键的 InnoDB 表转为 MyISAM 会失败。 - InnoDB 是聚集索引,MyISAM 是非聚集索引。
聚簇索引的文件存放在主键索引的叶子节点上,因此 InnoDB 必须要有主键,通过主键索引效率很高。但是辅助索引需要两次查询,先查询到主键,然后再通过主键查询到数据。因此,主键不应该过大,因为主键太大,其他索引也都会很大。
而 MyISAM 是非聚簇索引,数据文件是分离的,索引保存的是数据文件的指针。主键索引和辅助索引是独立的。 - InnoDB 不保存表的具体行数,执行 select count(*) from table 时需要全表扫描。而 MyISAM 用一个变量保存了整个表的行数,执行上述语句时只需要读出该变量即可,速度很快。
- InnoDB 最小的锁粒度是行锁,MyISAM 最小的锁粒度是表锁。
一个更新语句会锁住整张表,导致其他查询和更新都会被阻塞,因此并发访问受限。这也是 MyISAM 将默认引擎从 MyISAM 变成 InnoDB 的重要原因之一。
数据表设计时,字段如何选择?
- 字段类型优先级
整型 > data, time > enum char > varchar > blob, text
选用字段长度最小,优先使用定长型、数值型字段中避免使用 ”ZEROFILL“.
time:定长运算快、节省时间,考虑时区,写 sql 不方便。
enum:能约束值得目的,内部用整型来存储,但与 char 联查时,内部要经历串与值得转化。
char:定长,考虑字符集和校对集。
varchar:不定长,要考虑字符集的转换与排序时的校对集,速度慢。
text,blob:无法使用内存临时表(排序操作只能在磁盘上运行)。
注意:date,time 的选择可以直接使用时间戳,enum(“男”,”女“),内部转成数字来存储,多了一个转换的过程,可以使用 tinyint 代替最好使用 tinyint. - 可以选整型就不选字符串
整型是定长的,没有国家/地区之分,没有字符集差异。例如,tinyint 和 char(1) 从空间上看都是一个字节,但是 order by 排序 tinyint 快。原因是后者需要考虑字符集与校对集(就是排序优先集)。 - 够用就行不要慷慨
大的字段影响内存影响速度。以年龄为例,tinyint unsigned not null 可以 存储 255 岁,足够了,用 int 浪费 3 个字节。以 varchar(10),varchar(300) 存储的内容相同,但在表中查询时,varchar(300) 要花更多内存。 - 尽量避免使用 NULL
Null 不利于索引,也不利于查询。==null 或者 !=null 都查询不到值,只有使用 is null 或者 is not null 才可以。因此可以在创建字段时候使用 not null default 的形式。 - char 与 varchar 选择
char 长度固定,处理速度要比 varchar 快很多,但是相对较费存储空间。所以对存储空间要求不大,但在速度上有要求的可以使用 char 类型,反之可以使用 varchar 类型。
MySQL 中 VARCHAR(M) 最多存储多少数据?
对于 VARCHAR(M) 类型的列最多可以定义 65535 个字节。其中的 M 代表该类型最多存储的字符数量,但在实际存储时并不能放这么多。
MySQL 对一条记录占用的最大存储空间是有限制的,除了 BLOB 或者 TEXT 类型的列之外,其他所有的列(不包括隐藏列和记录头信息)占用的字节长度加起来不能超过 65535 个字节。
简述 MySQL 中索引类型及对数据库的性能的影响
普通索引:允许被索引的数据列包含重复的值。
唯一索引:可以保证数据记录的唯一性。
主键:是一种特殊的唯一索引,在一张表中只能定义一个主键索引,主键用于唯一标识一条记录,使用关键字 PRIMARY KEY 来创建。
联合索引:索引可以覆盖多个数据列,如像 INDEX(column A, column B)的索引。
全文索引:通过建立倒排索引,可以极大的提高检索效率,解决判断字段是否包含的问题,是目前搜索引擎使用的一种关键技术,可以通过 ALTER TABLE table_name ADD FULLTEXT(column) 创建全文索引。
索引可以极大的提高数据的查询速度。通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。
但是如果滥用索引,会降低插入、删除、更新表的速度,因为在执行这些写操作时,还要操作索引文件。
索引需要占用物理空间,除了数据表占数据空间之外,每一个索引还要占用一定的物理空间,如果要建立聚簇索引,那么需要的空间就会更大,如果非聚簇索引很多,一旦聚簇索引改变,那么所有非聚簇索引都会跟着变。
如何提高 insert 的性能?
- 合并多条 insert 为一条
即:insert into t values(a,b,c) (d,e,f)
原因分析:主要原因是多条 insert 合并后日志量(MySQL 的 binlog 和 innodb 的事务日志)减少了,降低日志刷盘的数据量和频率,从而提高效率。通过合并 SQL 语句,同时也能较少 SQL 语句解析的次数,减少网络传输的 IO. - 修改参数 bulk_insert_buffer_size,调大批量插入的缓存
- 设置 innodb_flush_log_at_trx_commit = 0
相对于 innodb_flush_log_at_trx_commit = 1 可以十分明显的提升导入速度,innodb_flush_log_at_trx_commit 参数解释如下:
0:log_buffer 中的数据将以每秒一次的频率写入到 log_file 中,且同时会进行文件系统到磁盘的同步操作,但是每个事务的 commit 并不会触发任何 log buffer 到 log file 的刷新或者文件系统到磁盘的刷新操作。
1:在每次事务提交的时候将 log buffer 中的数据都会写入到 log file,同时也会触发文件系统到磁盘的刷新操作。
2:事务提交会触发 log buffer 到 log file 的刷新,但是并不会触发磁盘文件系统到磁盘的同步。此外,每秒会有一次文件系统到磁盘同步操作。
4. 手动使用事务
因为 MySQL 默认是 autocommit 的,这样每插入一条数据,都会进行一次 commit. 所以,为了减少创建事务的消耗,我们可用手工使用事务。即 START TRANSCTION;insert …,insert … commit;即执行多个 insert 后再一起提交。一般 1000 条 insert 提交一次。
说说对 Redis 的了解
首先 Redis 是基于 C 语言编写的,而且是基于内存的数据库,读写速度很快。在项目中也经常会使用 Redis,一般会用来做缓存、或者分布式锁,也可以来设计消息队列,同时还支持事务 、持久化、Lua 脚本、多种集群方案。
追问1:与 Memcached 的区别是什么?
共同点 :
- 都是基于内存的数据库,一般都用来当做缓存使用。
- 都有过期策略。
- 两者的性能都非常高。
区别 :
- Redis 支持更丰富的数据类型(支持更复杂的应用场景)。Redis 不仅仅支持简单的 k/v 类型的数据,同时还提供 list,set,zset,hash 等数据结构的存储。Memcached 只支持最简单的 k/v 数据类型。
- Redis 支持数据的持久化,可以将内存中的数据保持在磁盘中,重启的时候可以再次加载进行使用,而 Memecache 把数据全部存在内存之中。
- Redis 有灾难恢复机制。 因为可以把缓存中的数据持久化到磁盘上。
- Redis 在服务器内存使用完之后,可以将不用的数据放到磁盘上。但是,Memcached 在服务器内存使用完之后,就会直接报异常。
- Memcached 没有原生的集群模式,需要依靠客户端来实现往集群中分片写入数据。但是 Redis 目前是原生支持 cluster 模式的。
- Memcached 是多线程,非阻塞 IO 复用的网络模型。Redis 使用单线程的多路 IO 复用模型。 (Redis 6.0 引入了多线程 IO )
- Redis 支持发布订阅模型、Lua 脚本、事务等功能,而 Memcached 不支持,并且,Redis 支持更多的编程语言。
- Memcached 过期数据的删除策略只用了惰性删除,而 Redis 同时使用了惰性删除与定期删除。
Redis 有哪些数据类型?
常见的有五种基本数据类型和三种特殊数据类型:
基本数据结构:String、 list、set、zset 和 hash:
String:一般常用在需要计数的场景,比如用户的访问次数、热点文章的点赞转发数量等。
list:发布与订阅或者说消息队列、慢查询。
hash:系统中对象数据的存储。
set:需要存放的数据不能重复以及需要获取多个数据源交集和并集等场景
zset:需要对数据根据某个权重进行排序的场景。比如在直播系统中,实时排行信息包含直播间在线用户列表,各种礼物排行榜,弹幕消息(可以理解为按消息维度的消息排行榜)等信息。
三种特殊数据类型:位图(bitmaps) 、计数器 (hyperloglogs) 和地理空间 (geospatial indexes).
什么是 Redis 持久化?Redis 有哪几种持久化方式?优缺点是什么?
持久化就是把内存的数据写到磁盘中去,防止服务宕机了内存数据丢失。Redis 提供了两种
持久化方式:RDB(默认)快照方式 和 AOF 追加方式:
- RDB(Redis DataBase):通过创建快照来获取存储在内存里面的数据在某个时间点上的副本。在创建快照之后,用户可以对快照进行备份,可以将快照复制到其他服务器从而创建相同数据的服务器副本。(如果系统真的发生崩溃,用户将丢失最近一次生成快照之后更改的所有数据。)
- AOF(Append Only File):将被执行的写命令写到 AOF 文件的末尾。
比较:
- AOF 文件比 RDB 更新频率高,优先使用 AOF 还原数据。
- AOF 比 RDB 更安全也更大。
- RDB 性能比 AOF 好。
- 如果两个都配了优先加载 AOF.
Redis 数据过期后的删除策略?
常用的过期数据的删除策略就两个(重要!自己造缓存轮子的时候需要格外考虑的东西):
- 惰性删除 :只会在取出 key 的时候才对数据进行过期检查。这样对 CPU 最友好,但
是可能会造成太多过期 key 没有被删除。 - 定期删除 : 每隔一段时间抽取一批 key 执行删除过期 key 操作。并且 Redis 底层会通过限制删除操作执行的时长和频率来减少删除操作对 CPU 时间的影响。
定期删除对内存更加友好,惰性删除对 CPU 更加友好。两者各有千秋,所以Redis 采用的是定期删除+惰性/懒汉式删除 。
但是,仅仅通过给 key 设置过期时间还是有问题的。因为还是可能存在定期删除和惰性删除漏掉了很多过期 key 的情况。这样就导致大量过期 key 堆积在内存里,然后就 Out of memory了。
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:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。
什么是缓存穿透?如何避免?什么是缓存击穿,如何避免?什么是缓存雪崩?如何解决?
缓存穿透:一般的缓存系统,都是按照 key 去缓存查询,如果不存在对应的 value,就应该去后端系统查找(比如DB)。一些恶意的请求会故意查询不存在的 key,请求量很大,就会对后端系统造成很大的压力,这就叫做缓存穿透。
如何避免?
- 对查询结果为空的情况也进行缓存,缓存时间设置短一点,或者该 key 对应的数据 insert 了之后清理缓存。
- 对一定不存在的 key 进行过滤。可以把所有的可能存在的 key 放到一个大的 bitmap 中,查询时通过该 bitmap 过滤。
缓存击穿:也称为热点 key 问题,就是一个被高并发访问并且缓存重建业务较复杂的 key 突然失效了,无数的请求访问会在瞬间给数据库带来巨大的冲击。
如何解决?
- 使用互斥锁:当缓存失效时,不立即去加载数据库,先使用如 Redis 的 setnx 去设置一个互斥锁,当操作成功返回时再进行加载数据的操作并回设缓存,否则重试获取缓存的方法。
- 不设置 TTL:物理 TTL 永远不过期,但逻辑过期(后台异步线程去刷新)。
解决方案 | 优点 | 缺点 |
---|---|---|
互斥锁 | 没有额外的内存消耗;保证一致性;实现简单 | 线程需要等待,性能受影响;可能有死锁风险 |
逻辑过期 | 线程无需等待,性能较好 | 不保证一致性;有额外的内存消耗;实现复杂 |
缓存雪崩:在同一时段大量的缓存 key 同时失效或者 Redis 服务宕机,导致大量请求到达数据库,带来巨大压力。
如何解决?
- 给不同的 key 的 TTL 添加随机值。
- 利用 Redis 集群提高服务的可用性。
- 给缓存业务添加降级限流策略。
- 给业务添加多级缓存。
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/5171.html