文章目录
- 1. 数据库索引结构
- 1.1 Hash结构
- 1.2 树结构
- 1.3 Mysql索引怎么提升效率?
- 2. 执行计划 explain
- id
- select_type
- table
- type
- possible_keys
- key
- key_len
- ref
- rows
- fitered
- extra
1. 数据库索引结构
我们都知道mysql数据库的常用存储结构是B+树,为什么是B+树?试想我们要设计数据库存储结构的话,会选择用什么基本数据结构去设计?KV结构,更关键的是怎么合理地安排其中的Key,让其布局更合理,更加符合操作系统的特性
要让我们的布局显得合理,需要了解数据场景和操作系统相关知识。
-
数据布局
- 假设我们的数据库要存储GB级别的数据,假设是8GB,那操作系统会怎么处理?数据是存在磁盘还是内存?分治思想是操作系统存储数据的基本思想。操作系统有两大特性:
局部性原理: 时间局部性:之前被访问过的数据很有可能再次被访问 空间局部性:数据和程序都有聚集成群的倾向 磁盘预读: 内存跟磁盘在进行交互的时候,有一个最小的逻辑单位,这个单位称之为页,或者叫data page,一般是4KB或者8KB,由操作系统决定,我们在进行数据读取的时候,一般会读取页的整数倍,也就是4K,8K,16K,Innodb存储引擎在进行数据加载的时候读取的是16KB的数据
-
IO问题
- 操作系统是hash布局,分段分页的设计,每一次IO是一次硬盘寻址过程,和硬件速度相关,要降低IO带来的影响,可以从两个方面解决,①减少IO读取量②减少IO次数
基于上述两个特性,为KV结构的设计合理的数据结构布局。
1.1 Hash结构
hash表结构的基本查找/读取速度为O(1),当发生hash碰撞甚至极端的hash碰撞时,会降速到O(n)级别。
同时还有两个问题:
- 数据倾斜
- 经过hash计算的数据分布是无序的,无法进行范围查询,当需要范围查询的时候,效率比较低
1.2 树结构
树的指针结构非常符合磁盘预读的特性,一个指针指向一个数据块
比较熟悉的树结构有二叉树、BST、AVL、红黑树等。能按照定义结构使数据有序,平衡等,但是考虑到上述的树都是二叉结构,而数据库的值有很多,即使是AVL和红黑树在数据量较大时,树的度数呈线性相关。
所以必须有一种多叉有序树的结构来解决
B树
- B树的阶:节点的最多子节点个数叫做阶。
- B树的搜索:从根节点开始,对节点内的元素进行二分查找,如果找到就结束,否则进入查找元素所属范围的子节点再进行二分查找,直到找到或者到达叶子节点;
- B树的所有节点都会存放数据;
B树的问题是B树的所有节点都会存放数据,不能很好地覆盖范围查询(<)的情况
B+树
1.3 Mysql索引怎么提升效率?
在InnoDB引擎中默认是以B+树作为索引的数据结构,有下面5中分类
- 主键索引:一种特殊的唯一索引,不允许有空值。
- 普通索引:最基本的索引,没有任何限制
- 唯一索引:与"普通索引"类似,不同的就是:索引列的值必须唯一,但允许有空值。
- 联合/组合索引:为了更多的提高mysql效率可建立组合索引,遵循”最左前缀“原则。
- 全文索引:仅可用于 MyISAM 表,针对较大的数据,生成全文索引很耗时好空间。
聚簇索引和非聚簇索引
那么这些索引具体的data保存的是什么?
现有(id:int,name:varchar,age:int)
结构的数据表user一张,其中id为主键,name为普通索引
那么这时候就有两颗索引B+树,如果每次构造B+树都把整行的数据存储到data中,显然会复制太多的数据,不合适。
所以Mysql规定的表数据结构中,是以主键索引的列为key;如果没有主键,则用唯一键;如果没有唯一键,则自动生成6字节的row_id作为B+树的key,叶子节点保存整行数据。这种和整行数据组织在一起的叫做聚簇索引
而非聚簇索引,叶子节点data保存的是聚簇索引的索引值(即可以是primary key或unique key或row_id)
回表
从这些非聚簇索引,我们可以很快找到聚簇索引中的data值返回,这个过程称为回表
如select * from user where name='张三'
先根据name B+树匹配到对应的叶子节点,查询到对应行记录的id值,再根据id去id的B+树检索整行记录,这个过程就称之为回表,回表会造成查询两次索引树,应该尽量避免。
索引覆盖
可以通过直接查询索引字段来避免回表,如select id,name from user where name='张三'
,这个过程称为索引覆盖(using index)。可以把一些常用的列设置为联合索引来优化sql
联合索引与最左匹配,索引失效
创建索引的时候可以选择多个列来共同组成索引,此时叫做组合索引或者联合索引
要遵循最左匹配原则
id,name,age; id主键,name,age组合索引
select * from table where name='zhangsan' and age=12(用索引)
select * from table where name='zhangsan'(用索引)
select * from table where age=12(索引失效)
select * from table where age=12 and name='zhangsan'(优化器)
多列的联合索引怎么确定顺序?
索引下推
name和age是联合索引
explain select * from user WHERE name LIKE '张%' AND age=12;
没有索引下推之前:先根据name从存储引擎中拉取数据到mysql的server层,然后在server层对age进行数据过滤
有了索引下推之后:根据name和age两个条件进行数据筛选,将筛选之后的结果返回给server层所谓下推,就是指server层该做的下推到存储引擎。索引下推是数据库自带的,减少了server层的数据量
判断条件部分能否用索引中的列来做检查,条件不满足,则处理下一行索引记录;
条件满足,使用索引中的主键去定位并读取完整的行记录(就是所谓的回表);
索引失效
-
不满足最左匹配
索引idx_code_age_name,索引顺序为code,age,name explain select * from user_fail where code='101' and age=21 and name='zhangsan'; explain select * from user_fail where code='101' and name='zhangsan'; explain select * from user_fail where code='1%'; explain select * from user_fail where code='%1'; explain select * from user_fail where age=21 and name='zhangsan';
-
索引列上有计算
explain select * from user_fail where id+1=2;
-
索引列使用了函数
explain select * from user_fail where SUBSTR(height,1,2)=17;
-
字段类型不同(隐式转换)
code的类型为varchar explain select * from user_fail where code=101; explain select * from user_fail where code='101';
-
列对比
explain select * from user_fail where id=height
-
使用or关键字
# id是主键索引,height是普通索引,address无索引 explain select * from user_fail where id=1 or height='175'; explain select * from user_fail where id=1 or height='175' or address='a2';
-
not in
和not exists
explain select * from user_fail where height in (173,174,175,176); explain select * from user_fail t1 where exists (select 1 from user_fail t2 where t2.height=173 and t1.id=t2.id) explain select * from user_fail where height not in (173,174,175,176); explain select * from user_fail t1 where not exists (select 1 from user_fail t2 where t2.height=173 and t1.id=t2.id)
-
order by
explain select * from user_fail order by code limit 100; explain select * from user_fail where code='101'order by name;
2. 执行计划 explain
当我们需要了解sql语句的执行,像有没有使用索引,使用情况,join语句的执行过程等,我们可以通过explain + sql
的方式查看执行计划。
官网
这其中最常用的信息是type,possible_keys,key,extra
id
查询执行顺序栈:
id 值相同时表示从上向下执行
id 值相同被视为一组
如果是子查询,id 值会递增,id 值越高,优先级越高
select_type
查询类型
1.simple:表示查询中不包含子查询或者 union
EXPLAIN select * from actor where id=1;
2.primary:当查询中包含任何复杂的子部分,最外层的查询被标记成 primary
3.derived:在 from 的列表中包含的子查询被标记成 derived
4.subquery:在 select 或 where 列表中包含了子查询,则子查询被标记成 subquery
用个例子来了解primary、subquery和derived
set session optimizer_switch='derived_merge=off';#关闭mysql5.7新特性对衍生表的合并优化
explain select (select 1 from actor where id = 1) from (select * from film where id = 1) der;
set session optimizer_switch='derived_merge=on'; #还原默认配置
5.union:
两个 select 查询时前一个标记为 PRIMARY,后一个标记为 UNION。
union 出现在 from 从句子查询中,外层 select 标记为 PIRMARY。
explain select 1 union all select 1;
table
显示这一行的数据是关于哪张表的。
当 from 子句中有子查询时,table列是 <derive[id]>,表示当前查询依赖 id=N 的查询,于是先执行 id=N 的查询。
当有 union 时,UNION RESULT 的 table 列的值为<union1,2>,1和2表示参与 union 的 select 行id。
type
表示存储引擎使用了哪种访问类型。
从最好到最差的连接类型为 system > const > eq_reg > ref > range > index > ALL。
一般来说,得保证查询达到range级别,最好达到ref以上
1. ALL:即全表扫描,扫描你的聚簇索引的所有叶子节点。通常情况下这需要增加索引来进行优化了。
explain select * from actor;
2. system:表中只有一行数据。属于 const 的特例。
3. const :查询结果最多有一个匹配行。因为只有一行,所以可以被视为常量。const 查询速度非常快,因为只读一次。一般情况下把主键或唯一索引作为唯一条件的查询都是 const
explain select * from actor WHERE id=1;
4.eq_ref:查询时查询外键表全部数据。且只能查询主键列或关联列。且外键表中外键列中数据不能有重复数据,且这些数据都必须在主键表中有对应数据(主键表中数据可以有没有用到的)
explain select * from film_actor left join film on film_actor.film_id = film.id
5.ref:比 eq_ref,不使用唯一索引,而是使用普通索引或者唯一性索引的部分前缀,索引要和某个值相比较,可能会找到多个符合条件的行。
explain select * from film where name = 'kungfu';
关联表查询,idx_film_actor_id是film_id和actor_id的联合索引,这里使用到了film_actor的左边前缀film_id部分
explain select film_id from film left join film_actor on film.id = film_actor.film_id;
6. range:把这个列当作条件只检索其中一个范围。常见 where 从句中出现 between、<、>、>=、in 等。主要应用在具有索引的列中
explain select * from actor where id > 1;
7. index:扫描全索引就能拿到结果,一般是扫描某个二级索引,这种扫描不会从索引树根节点开始快速查找,而是直接对二级索引的叶子节点遍历和扫描,速度还是比较慢的,这种查询一般为使用覆盖索引,二级索引一般比较小,所以这种通常比ALL快一些
explain select * from film;
注意第一个例子和最后一个例子
possible_keys
查询条件字段涉及到的索引,可能没有使用。
explain 时可能出现 possible_keys 有列,而 key 显示 NULL 的情况,这种情况是因为表中数据不多,mysql认为索引对此查询帮助不大,选择了全表查询。
如果该列是NULL,则没有相关的索引。在这种情况下,可以通过检查 where 子句看是否可以创造一个适当的索引来提高查询性能,然后用 explain 查看效果
key
实际使用的索引。如果为 NULL,则没有使用索引。
key_len
表示索引中使用的字节数,查询中使用的索引的长度(最大可能长度),并非实际使用长度,理论上长度越短越好。key_len 是根据表定义计算而得的,不是通过表内检索出的。
1. 字符串,char(n)或varchar(n),n代表字符数,而不是字节数,如utf-8中,一个数字或字母占1个字节,一个汉字占3个字节
char(n):如果存汉字长度就是3n字节
varchar(n):如果存汉字则长度是3n+2字节,加2表示存储字符串的长度
2. 数值类型
tinyint:1字节
smallint:2字节
int:4字节
bigint:8字节
3. 时间类型
date: 3字节
timestramp: 4字节
datetime:8字节
索引最大长度是768字节,超过限制会被mysql用left函数截断
ref
查询结果可能找到多个符合条件的行,如果可能的话,通常是一个常量 const。
rows
根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数。
这个不是结果集里的行数。
fitered
filtered表示通过查询条件获取的最终记录行数占通过type字段指明的搜索方式搜索出来的记录行数的百分比。当比较低的时候,可以考虑加索引
EXPLAIN SELECT * FROM user_fail WHERE CODE LIKE '%2';
extra
using filesort: sql通过排序操作获取结果时,会出现using filesort标记,这里虽然叫filesort但是不一定用了磁盘文件进行排序,具体看数据的量或存储引擎。
using temporatry: 用临时表保存中间结果,常见用group by操作
using index: 使用了索引覆盖
using index condition: 发生了索引下推的情况
using where: 全表扫描时,mysql服务层应用where条件过滤数据。使用索引访问数据时,而where子句中有除了索引包含的字段之外的条件时会出现
distinct: 优化distince操作,在找到第一匹配的数据后即停止同样值的操作