11.什么是自适应哈希索引?
自适应Hash索引(Adatptive Hash Index,内部简称AHI)是InnoDB的三大特性之一,还有两个是 Buffer Pool简称BP、双写缓冲区(Doublewrite Buffer)。
1、自适应即我们不需要自己处理,当InnoDB引擎根据查询统计发现某一查询满足hash索引的数据结构特点,就会给其建立一个hash索引;
2、hash索引底层的数据结构是散列表(Hash表),其数据特点就是比较适合在内存中使用,自适应Hash索引存在于InnoDB架构中的缓存中(不存在于磁盘架构中),见下面的InnoDB架构图。
3、自适应hash索引只适合搜索等值的查询,如select * from table where index_col=‘xxx’,而对于其他查找类型,如范围查找,是不能使用的;
Adaptive Hash Index是针对B+树Search Path的优化,因此所有会涉及到Search Path的操作,均可使用此Hash索引进行优化.
根据索引键值(前缀)快速定位到叶子节点满足条件记录的Offset,减少了B+树Search Path的代价,将B+树从Root节点至Leaf节点的路径定位,优化为Hash Index的快速查询。
InnoDB的自适应Hash索引是默认开启的,可以通过配置下面的参数设置进行关闭。
innodb_adaptive_hash_index = off
自适应Hash索引使用分片进行实现的,分片数可以使用配置参数设置:
innodb_adaptive_hash_index_parts = 8
12.为什么LIKE以%开头索引会失效?
like查询为范围查询,%出现在左边,则索引失效。%出现在右边索引未失效.
场景1: 两边都有% 或者 字段左边有%,索引都会失效。
EXPLAIN SELECT * FROM users WHERE user_name LIKE '%tom%';EXPLAIN SELECT * FROM users WHERE user_name LIKE '%tom';
场景2: 字段右边有%,索引生效
EXPLAIN SELECT * FROM users WHERE user_name LIKE 'tom%';
解决%出现在左边索引失效的方法,使用覆盖索引
EXPLAIN SELECT user_name FROM users WHERE user_name LIKE '%jack%';EXPLAIN SELECT user_name,user_age,user_level FROM users WHERE user_name LIKE '%jack%';
对比场景1可以知道, 通过使用覆盖索引 type = index
,并且 extra = Using index
,从全表扫描变成了全索引扫描.
like 失效的原因
- %号在右: 由于B+树的索引顺序,是按照首字母的大小进行排序,%号在右的匹配又是匹配首字母。所以可以在B+树上进行有序的查找,查找首字母符合要求的数据。所以有些时候可以用到索引.
- %号在左: 是匹配字符串尾部的数据,我们上面说了排序规则,尾部的字母是没有顺序的,所以不能按照索引顺序查询,就用不到索引.
- 两个%%号: 这个是查询任意位置的字母满足条件即可,只有首字母是进行索引排序的,其他位置的字母都是相对无序的,所以查找任意位置的字母是用不上索引的.
13.自增还是UUID?数据库主键的类型该如何选择?
auto_increment的优点:
- 字段长度较uuid小很多,可以是bigint甚至是int类型,这对检索的性能会有所影响。
- 在写的方面,因为是自增的,所以主键是趋势自增的,也就是说新增的数据永远在后面,这点对于性能有很大的提升。
- 数据库自动编号,速度快,而且是增量增长,按顺序存放,对于检索非常有利。
- 数字型,占用空间小,易排序,在程序中传递也方便。
auto_increment的缺点:
- 由于是自增,很容易通过网络爬虫知晓当前系统的业务量。
- 高并发的情况下,竞争自增锁会降低数据库的吞吐能力。
- 数据迁移或分库分表场景下,自增方式不再适用。
UUID的优点:
- 不会冲突。进行数据拆分、合并存储的时候,能保证主键全局的唯一性
- 可以在应用层生成,提高数据库吞吐能力
UUID的缺点:
- 影响插入速度, 并且造成硬盘使用率低。与自增相比,最大的缺陷就是随机io,下面我们会去具体解释
- 字符串类型相比整数类型肯定更消耗空间,而且会比整数类型操作慢。
uuid 和自增 id 的索引结构对比
1、使用自增 id 的内部结构
自增的主键的值是顺序的,所以 InnoDB 把每一条记录都存储在一条记录的后面。
- 当达到页面的最大填充因子时候(InnoDB 默认的最大填充因子是页大小的 15/16,会留出 1/16 的空间留作以后的修改)。
- 下一条记录就会写入新的页中,一旦数据按照这种顺序的方式加载,主键页就会近乎于顺序的记录填满,提升了页面的最大填充率,不会有页的浪费。
- 新插入的行一定会在原有的最大数据行下一行,MySQL 定位和寻址很快,不会为计算新行的位置而做出额外的消耗。减少了页分裂和碎片的产生。
2、使用 uuid 的索引内部结构
插入UUID: 新的记录可能会插入之前记录的中间,因此需要移动之前的记录
被写满已经刷新到磁盘上的页可能会被重新读取
因为 uuid 相对顺序的自增 id 来说是毫无规律可言的,新行的值不一定要比之前的主键的值要大,所以 innodb 无法做到总是把新行插入到索引的最后,而是需要为新行寻找新的合适的位置从而来分配新的空间。
这个过程需要做很多额外的操作,数据的毫无顺序会导致数据分布散乱,将会导致以下的问题:
- 写入的目标页很可能已经刷新到磁盘上并且从缓存上移除,或者还没有被加载到缓存中,innodb 在插入之前不得不先找到并从磁盘读取目标页到内存中,这将导致大量的随机 IO。
- 因为写入是乱序的,innodb 不得不频繁的做页分裂操作,以便为新的行分配空间,页分裂导致移动大量的数据,一次插入最少需要修改三个页以上。
- 由于频繁的页分裂,页会变得稀疏并被不规则的填充,最终会导致数据会有碎片。
- 在把随机值(uuid 和雪花 id)载入到聚簇索引(InnoDB 默认的索引类型)以后,有时候会需要做一次 OPTIMEIZE TABLE 来重建表并优化页的填充,这将又需要一定的时间消耗。
结论:使用 InnoDB 应该尽可能的按主键的自增顺序插入,并且尽可能使用单调的增加的聚簇键的值来插入新行。如果是分库分表场景下,分布式主键ID的生成方案 优先选择雪花算法生成全局唯一主键(雪花算法生成的主键在一定程度上是有序的)。
14.InnoDB与MyISAM的区别?
InnoDB和MyISAM是使用MySQL时最常用的两种引擎类型,我们重点来看下两者区别。
-
事务和外键
InnoDB支持事务和外键,具有安全性和完整性,适合大量insert或update操作
MyISAM不支持事务和外键,它提供高速存储和检索,适合大量的select查询操作 -
锁机制
InnoDB支持行级锁,锁定指定记录。基于索引来加锁实现。
MyISAM支持表级锁,锁定整张表。 -
索引结构
InnoDB使用聚集索引(聚簇索引),索引和记录在一起存储,既缓存索引,也缓存记录。
MyISAM使用非聚集索引(非聚簇索引),索引和记录分开。 -
并发处理能力
MyISAM使用表锁,会导致写操作并发率低,读之间并不阻塞,读写阻塞。
InnoDB读写阻塞可以与隔离级别有关,可以采用多版本并发控制(MVCC)来支持高并发 -
存储文件
InnoDB表对应两个文件,一个.frm表结构文件,一个.ibd数据文件。InnoDB表最大支持64TB;
MyISAM表对应三个文件,一个.frm表结构文件,一个MYD表数据文件,一个.MYI索引文件。从MySQL5.0开始默认限制是256TB。
MyISAM 适用场景
- 不需要事务支持(不支持)
- 并发相对较低(锁定机制问题)
- 数据修改相对较少,以读为主
- 数据一致性要求不高
InnoDB 适用场景
- 需要事务支持(具有较好的事务特性)
- 行级锁定对高并发有很好的适应能力
- 数据更新较为频繁的场景
- 数据一致性要求较高
- 硬件设备内存较大,可以利用InnoDB较好的缓存能力来提高内存利用率,减少磁盘IO
两种引擎该如何选择?
- 是否需要事务?有,InnoDB
- 是否存在并发修改?有,InnoDB
- 是否追求快速查询,且数据修改少?是,MyISAM
- 在绝大多数情况下,推荐使用InnoDB
扩展资料:各个存储引擎特性对比
15.B树和B+树的区别是什么?
1)B-Tree介绍
B-Tree是一种平衡的多路查找树,B树允许一个节点存放多个数据. 这样可以在尽可能减少树的深度的同时,存放更多的数据(把瘦高的树变的矮胖).
B-Tree中所有节点的子树个数的最大值称为B-Tree的阶,用m表示.一颗m阶的B树,如果不为空,就必须满足以下条件.
m阶的B-Tree满足以下条件:
- 每个节点最多拥有m-1个关键字(根节点除外),也就是m个子树
- 根节点至少有两个子树(可以没有子树,有就必须是两个)
- 分支节点至少有(m/2)颗子树 (除去根节点和叶子节点其他都是分支节点)
- 所有叶子节点都在同一层,并且以升序排序
什么是B-Tree的阶 ?
所有节点中,节点【60,70,90】拥有的子节点数目最多,四个子节点(灰色节点),所以上面的B-Tree为4阶B树。
B-Tree结构存储索引的特点
为了描述B-Tree首先定义一条记录为一个键值对[key, data] ,key为记录的键值,对应表中的主键值(聚簇索引),data为一行记录中除主键外的数据。对于不同的记录,key值互不相同
- 索引值和data数据分布在整棵树结构中
- 白色块部分是指针,存储着子节点的地址信息。
- 每个节点可以存放多个索引值及对应的data数据
- 树节点中的多个索引值从左到右升序排列
B-Tree的查找操作
B-Tree的每个节点的元素可以视为一次I/O读取,树的高度表示最多的I/O次数,在相同数量的总元素个数下,每个节点的元素个数越多,高度越低,查询所需的I/O次数越少.
B-Tree总结
- 优点: B树可以在内部节点存储键值和相关记录数据,因此把频繁访问的数据放在靠近根节点的位置将大大提高热点数据的查询效率。
- 缺点: B树中每个节点不仅包含数据的key值,还有data数据. 所以当data数据较大时,会导致每个节点存储的key值减少,并且导致B树的层数变高.增加查询时的IO次数.
- 使用场景: B树主要应用于文件系统以及部分数据库索引,如MongoDB,大部分关系型数据库索引则是使用B+树实现
2)B+Tree
B+Tree是在B-Tree基础上的一种优化,使其更适合实现存储索引结构,InnoDB存储引擎就是用B+Tree实现其索引结构。
B+Tree的特征
- 非叶子节点只存储键值信息.
- 所有叶子节点之间都有一个链指针.
- 数据记录都存放在叶子节点中.
B+Tree的优势
- B+Tree是B Tree的变种,B Tree能解决的问题,B+Tree也能够解决(降低树的高度,增大节点存储数据量)
- B+Tree扫库和扫表能力更强,如果我们要根据索引去进行数据表的扫描,对B Tree进行扫描,需要把整棵树遍历一遍,而B+Tree只需要遍历他的所有叶子节点即可(叶子节点之间有引用)。
- B+Tree磁盘读写能力更强,他的根节点和支节点不保存数据区,所有根节点和支节点同样大小的情况下,保存的关键字要比B Tree要多。而叶子节点不保存子节点引用。所以,B+Tree读写一次磁盘加载的关键字比B Tree更多。
- B+Tree排序能力更强,如上面的图中可以看出,B+Tree天然具有排序功能。
- B+Tree查询效率更加稳定,每次查询数据,查询IO次数一定是稳定的。当然这个每个人的理解都不同,因为在B Tree如果根节点命中直接返回,确实效率更高。
16.一个B+树中大概能存放多少条索引记录?
MySQL设计者将一个B+Tree的节点的大小设置为等于一个页. (这样做的目的是每个节点只需要一次I/O就可以完全载入), InnoDB的一个页的大小是16KB,所以每个节点的大小也是16KB, 并且B+Tree的根节点是保存在内存中的,子节点才是存储在磁盘上.
假设一个B+树高为2,即存在一个根节点和若干个叶子节点,那么这棵B+树的存放总记录数为:
根节点指针数*单个叶子节点记录行数.
- 计算根节点指针数: 假设表的主键为INT类型,占用的就是4个字节,或者是BIGINT占用8个字节, 指针大小为6个字节,那么一个页(就是B+Tree中的一个节点) ,大概可以存储: 16384B / (4B+6B) = 1638 ,一个节点最多可以存储1638个索引指针.
- 计算每个叶子节点的记录数:我们假设一行记录的数据大小为1k,那么一页就可以存储16行数据,16KB / 1KB = 16.
- 一颗高度为2的B+Tree可以存放的记录数为: 1638 * 16=26208 条数据记录, 同样的原理可以推算出一个高度3的B+Tree可以存放: 1638 * 1638 * 16 = 42928704条这样的记录.
所以InnoDB中的B+Tree高度一般为1-3层,就可以满足千万级别的数据存储,在查找数据时一次页的查找代表一次 IO,所以通过主键索引查询通常只需要 1-3 次 IO 操作即可查找到数据。
17.explain 用过吗,有哪些主要字段?
使用 EXPLAIN
关键字可以模拟优化器来执行SQL查询语句,从而知道MySQL是如何处理我们的SQL语句的。分析出查询语句或是表结构的性能瓶颈。
MySQL查询过程
通过explain我们可以获得以下信息:
- 表的读取顺序
- 数据读取操作的操作类型
- 哪些索引可以被使用
- 哪些索引真正被使用
- 表的直接引用
- 每张表的有多少行被优化器查询了
Explain使用方式: explain+sql语句, 通过执行explain可以获得sql语句执行的相关信息
explain select * from users;
18.type字段中有哪些常见的值?
type字段在 MySQL 官网文档描述如下:
The join type. For descriptions of the different types.
type字段显示的是连接类型 ( join type表示的是用什么样的方式来获取数据),它描述了找到所需数据所使用的扫描方式, 是较为重要的一个指标。
下面给出各种连接类型,按照从最佳类型到最坏类型进行排序:
-- 完整的连接类型比较多
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL-- 简化之后,我们可以只关注一下几种
system > const > eq_ref > ref > range > index > ALL
一般来说,需要保证查询至少达到 range级别,最好能到ref,否则就要就行SQL的优化调整
下面介绍type字段不同值表示的含义:
type类型 | 解释 |
---|---|
system | 不进行磁盘IO,查询系统表,仅仅返回一条数据 |
const | 查找主键索引,最多返回1条或0条数据. 属于精确查找 |
eq_ref | 查找唯一性索引,返回数据最多一条, 属于精确查找 |
ref | 查找非唯一性索引,返回匹配某一条件的多条数据,属于精确查找,数据返回可能是多条. |
range | 查找某个索引的部分索引,只检索给定范围的行,属于范围查找. 比如: > 、 < 、in 、between |
index | 查找所有索引树,比ALL快一些,因为索引文件要比数据文件小. |
ALL | 不使用任何索引,直接进行全表扫描 |
19.Extra有哪些主要指标,各自的含义是什么?
Extra 是 EXPLAIN 输出中另外一个很重要的列,该列显示MySQL在查询过程中的一些详细信息
extra类型 | 解释 |
---|---|
Using filesort | MySQL中无法利用索引完成的排序操作称为 “文件排序” |
Using index | 表示直接访问索引就能够获取到所需要的数据(覆盖索引),不需要通过索引回表 |
Using index condition | 搜索条件中虽然出现了索引列,但是有部分条件无法使用索引, 会根据能用索引的条件先搜索一遍再匹配无法使用索引的条件。 |
Using join buffer | 使用了连接缓存, 会显示join连接查询时,MySQL选择的查询算法 |
Using temporary | 表示MySQL需要使用临时表来存储结果集,常见于排序和分组查询 |
Using where | 意味着全表扫描或者在查找使用索引的情况下,但是还有查询条件不在索引字段当中 |
20.如何进行分页查询优化?
-
一般性分页
一般的分页查询使用简单的 limit 子句就可以实现。limit格式如下:
SELECT * FROM 表名 LIMIT [offset,] rows
- 第一个参数指定第一个返回记录行的偏移量,注意从0开始;
- 第二个参数指定返回记录行的最大数目;
- 如果只给定一个参数,它表示返回最大的记录行数目;
思考1:如果偏移量固定,返回记录量对执行时间有什么影响?
select * from user limit 10000,1; select * from user limit 10000,10; select * from user limit 10000,100; select * from user limit 10000,1000; select * from user limit 10000,10000;
结果:在查询记录时,返回记录量低于100条,查询时间基本没有变化,差距不大。随着查询记录量越大,所花费的时间也会越来越多。
思考2:如果查询偏移量变化,返回记录数固定对执行时间有什么影响?
select * from user limit 1,100; select * from user limit 10,100; select * from user limit 100,100; select * from user limit 1000,100; select * from user limit 10000,100;
结果:在查询记录时,如果查询记录量相同,偏移量超过100后就开始随着偏移量增大,查询时间急剧的增加。(这种分页查询机制,每次都会从数据库第一条记录开始扫描,越往后查询越慢,而且查询的数据越多,也会拖慢总查询速度。)
-
分页优化方案
优化1: 通过索引进行分页
直接进行limit操作 会产生全表扫描,速度很慢. Limit限制的是从结果集的M位置处取出N条输出,其余抛弃.
假设ID是连续递增的,我们根据查询的页数和查询的记录数可以算出查询的id的范围,然后配合 limit使用
EXPLAIN SELECT * FROM user WHERE id >= 100001 LIMIT 100;
优化2:利用子查询优化
-- 首先定位偏移位置的id SELECT id FROM user_contacts LIMIT 100000,1;-- 根据获取到的id值向后查询. EXPLAIN SELECT * FROM user_contacts WHERE id >= (SELECT id FROM user_contacts LIMIT 100000,1) LIMIT 100;
原因:使用了id做主键比较(id>=),并且子查询使用了覆盖索引进行优化。