1.MySQL架构图:
什么是索引?: 索引是帮助MySQL高效获取数据的数据结构
2.索引的数据结构选择:
1.1 hash表
1.2 二叉树/红黑树索引格式
缺点: 树的深度会影响效率,增加IO次数,红黑树在数据量增大的时候需要旋转,也影响效率
1.3 B树
图说明:
每个节点占用一个磁盘块,一个节点上有两个升序排序的关键字和三个指向子树根节点的指针,指针存储的是子节点所在磁盘块的地址。两个关键词划分成的三个范围域对应三个指针指向的子树的数据的范围域。以根节点为例,关键字为16和34,P1指针指向的子树的数据范围为小于16,P2指针指向的子树的数据范围为16-34,P3指针指向的子树的数据范围为大于34.
查找关键字过程:
- 根据根节点找到碰盘块,1读入内存。【磁盘I/O操作第1次】
- 比较关健字28在区间(16,34),找到磁盘块1的指针P2
- 根据P2指针找到磁盘块3,读入内存。【磁盘1/O操作第2次】
- 比较关健字28在区问(27,29)。找到磁盘块3的指针P2
- 根据P2指针找到磁盘块8,读入内存。【磁盘1/O操作第3次】
- 在磁盘块8中的关键字列表中找到关键字28.
缺点:
- 每个节点都有key,同时也包含data,而每个页存储空间是有限的,如果data比较大的话会导致每个节点存储的key的数量变少
- 当存储的数据量很大时会导致树的深度较大,增加查询时磁盘IO的次数,进而影响查询性能
1.4 B+树
非叶子节点存储key和指针,叶子节点存储数据(使得每次磁盘IO读取尽可能多的数据key),最多只需要3层就可达千万级数据
- INNODB 实现B+树:
叶子节点存储数据,如果创建索引的键是其他字段(非主键),那么叶子节点存储的数据是该记录的主键,然后再通过主键查找到该记录
- MyISAM 实现B+树:
叶子节点存储的是数据的地址,再根据地址取读取数据(对应MyISAM 两个文件 。myi存储的是索引文件,myd存储的是数据文件)
3. 索引的分类:
创建索引:
- create table( …unique key(字段))
- alter table tableName add/drop index indexName(字段)
- create index indexName on table(字段)
添加索引可以提高数据的读取速度,提高项目的并发能力和抗压能力
- 主键索引 (primary key): 主键是一种唯一性索引,但必须指定为primay key
- 唯一索引 (unique):索引列的所有值都只能出现一次,必须唯一,但值可以为空
- 普通索引 : 基本的索引类型,值可以为空,没有唯一性的限制
- 全文索引 :全文索引的类型为 fulltext 全文索引可以在varchar,char,text类型上创建 ES替代
- 组合索引 : 多列值组成一个索引,专门用于组合索引
例 :创建索引: alter table staffs add index idx_nap(name,age,pos)
优点: 加快查询
缺点: 额外的存储空间,且增删改数据需要额外维护索引
组合索引(a,b,c)的使用情况(范围后不在使用索引):
4.MySQL聚簇索引和非聚簇索引
MySQL的索引类型跟存储引擎相关
4.1 聚簇索引:
** 数据和索引存放在一起**
- innoDB 存储引擎: .idb(索引和数据),.frm(表结构)
- Myisam存储引擎:.myd(数据文件) .myi(索引文件), frm(表结构)
Myisam: 只有非聚簇索引
InnoDB: 有聚簇索引(只有一个聚簇索引,可有多个非聚簇索引)和非聚簇索引: innoDB在插入数据的时候,数据和索引(有多个索引也只和一个索引绑定)放到一起,有主键就用主键,没就用唯一键,再没就用rowid(6字节),为了避免数据冗余(同一份数据存储多份),多个索引中,只有跟绑定的索引在一块的才是聚簇索引,其他索引的叶子节点存储的是聚簇索引的key值(key值类似于主键的值,所以需要额外的一次回表操作)
自增主键 VS 非自增主键
自增主键的插入数据模式,正符合了递增插入的场景。每次插入一条新记录,都是追加操作,都不涉及到挪动其他记录,也不会触发叶子节点的分裂。而有业务逻辑的字段做主键,由于每次插入主键的值近似于随机,则会产生很多移动数据,页分列,进而造成了大量的碎片,大大影响性能。
聚簇索引的优缺点:
4.2 hash索引
memory 存储引擎使用
数据结构 : hash表
应用场景 : 存储索引占用个很大的空间时使用
当需要存储大量的URL,并且根据URL进行搜索查找,如果使用B+树,存储的内容就会很大
select id from url where url=""
也可以利用将url使用CRC32冗余校验做哈希,可以使用以下查询方式:
select id fom url where url="" and url_crc=CRC32("")
此查询性能较高原因是使用体积很小的索引来完成查找
4.3 存储引擎的对比:
MyISAM | InnoDB | |
---|---|---|
索引类型 | 非聚簇索引 | 聚簇索引 |
支持事务 | 否 | 是 |
支持表锁 | 是 | 是 |
支持行锁 | 否 | 是 |
支持外键 | 否 | 是 |
支持全文索引 | 是 | 是(5.6后支持) |
适合操作类型 | 大量select | 大量 insert,delete,update |
数据结构 | B+树 | B+树 |
memory存储引擎的数据结构为 : hash表
5.MySQL面试专业术语:
在name字段上创建索引 : alter table add index idx(name)
给表创建 id(主键索引)
name(普通索引) 叶子节点存储的是 聚簇索引的key值
- 回表: 对于sql: select * from table where name = ma ,
- 先根据 name 查 id ,再根据id查询整条记录,走了2棵B+树(普通索引查询到聚簇索引的key值后,再根据key值在聚簇索引中获取所有记录)
- 索引覆盖: 对于sql: select id,name from table where name = zhangshan,
- 可以直接在普通索引的B+树查询要返回的字段,无需再去聚簇索引查
主键自增(满足业务需求情况下):
- 减少磁盘块的分裂(每个磁盘块存储数据满了要分裂为两块,同时上层磁盘块也要改变,维护B+树
- 最左匹配原则: 对于聚合索引(name ,age),会先匹配 name 再 age
- using index skip scan: 索引跳跃扫描(MySQL8之后),优化器根据索引中的前导列(放在前面的索引列)的唯一值的数量来决定是否使用 Skip Scan
- 索引下推: 对于 …wehere name = ? and age= ? 对索引中包含的字段先做判断,直接过滤掉不满足条件的记录,减少回表次数。
- 没有索引下推之前: 先根据name在存储引擎中获取符合规则的数据,然后在server层对age进行过滤
- 有索引下推后(减少IO量): 根据 name 和 age 两个条件 来从存储引擎中获取对应的数据
6.索引设计原则:
- 经常查询的字段,where 字段,order by 字段 ,join on 字段
- 范围(<,<=,=,>,>=,BETWEEN)进行搜索的列上创建索引
- 选择索引时,字段越小越好
- 尽量建组合索引,但组合索引列不要太多(最左匹配原则)
不建索引的列:
- 更新频繁的列,不要有索引(索引维护麻烦)
- 离散度小的表,没必要创建索引
- 大文本,大对象不建索引(额外空间问题,B+树变高,IO次数增加)
索引失效:
-
最左匹配原则,MySQL从左向右匹配,遇到范围查询,不等于,会停止匹配
using index skip scan 索引跳跃扫描,前一个索引字段数据少,优化器进行优化,MySQL8.0跳过这个索引
-
**select **, is not null , or , **模糊匹配 like “%” , **
-
在索引列上使用函数
-
查找数据类型和索引字段的数据类型不一致
7.MySQL limit 查询优化
问题 : 查第 600000 条后的 10条数据?
解决的本质方法 : 快速找到第 offset 条数据的 ID ,然后在主键索引树定位到这 size 条数据
主键索引的 limit 过程:
-
粗暴方式 : select * from page order by id limit 600000, 10;
server 层调用innodb的接口,会在innodb的主键索引中获取第 0 到 (600000+10)条完整数据,IO量很大,返回给server 层之后根据offset的值挨个抛弃,最后只留下后面的size 条,再返回给server端,可以看到当 offset 很大时,server层会从引擎层获取到很多无用的数据
-
优化: select * from page where id >=(select id from page order by id limit 6000000, 1) order by id limit 10;
优化本质 : 减少取前 600000条数据的字段
优化: 只取前 600000数据的 id字段进行匹配 而不是取整行数据 ,取出第600000条数据的 ID,再走一次主键索引,通过B+树快速定位到 第600000 条数据的 ID ,时间复杂度为 lg n 。
非主键索引 limit 的过程:
server层会调用innodb的接口,在innodb里的非主键索引中获取到第 offset 条数据对应的主键id后,回表到主键索引中找到对应的完整行数据,然后返回给server层,server层将其放到结果集中,返回给客户端。
- 粗暴方式 : select * from page order by name limit 600000, 10;
也就是说非主键索引的limit过程,比主键索引的limit过程,多了个回表的消耗。
-
当非主键索引 offset 值很大时 : 走全表扫描
这是因为server层的优化器,会在执行器执行sql语句前,判断下哪种执行计划的代价更小。很明显,优化器在看到非主键索引的600w次回表之后,摇了摇头,还不如全表一条条记录去判断算了,于是选择了全表扫描。
-
优化: select * from page t1, (select id from page order by user_name limit 6000000, 100) t2 WHERE t1.id = t2.id;
通过
select id from page order by user_name limit 6000000, 100
。 先走innodb层的user_name非主键索引取出id,因为只拿主键id,不需要回表,所以这块性能会稍微快点,在返回server层之后,同样抛弃前600w条数据,保留最后的100个id。然后再用这100个id去跟t1表做id匹配,此时走的是主键索引,将匹配到的100条行数据返回。这样就绕开了之前的600w条数据的回表。
深度分页
当offset变得超大时,比如到了百万千万的量级,这里就产生了个专门的术语,叫深度分页。大部分时候是不应该出现深度分页的场景的
解决 :
- 我们可以将所有的数据根据id主键进行排序,然后分批次取,将当前批次的最大id作为下次筛选的条件进行查询。 查询性能都很稳定。 变成像抖音那样只能上划或下划,专业点,叫瀑布流
8.查询细节优化
门的术语,叫深度分页。大部分时候是不应该出现深度分页的场景的
解决 :
- 我们可以将所有的数据根据id主键进行排序,然后分批次取,将当前批次的最大id作为下次筛选的条件进行查询。 查询性能都很稳定。 变成像抖音那样只能上划或下划,专业点,叫瀑布流
8.查询细节优化