B+树

https://www.cs.usfca.edu/~galles/visualization/BPlusTree.html

InnoDB数据页和索引结构

数据页

索引结构

索引数据结构

索引是帮助MySQL高效获取数据的排好序数据结构

索引数据结构是B+树,下面这些数据结构有一定的缺点

  • 二叉树

    层数太高,若递增或递减添加,会形成类链表的数据结构,效率较低

  • 红黑树

  • Hash表

    不利于范围查找

  • B-Tree

    不利于范围查找

  • B+Tree

    • 非叶子节点不存储data,只存储索引(冗余),可以放更多的索引
    • 叶子节点包含所有索引字段
    • 叶子节点用指针连接,提高区间访问的性能

MyISAM存储引擎

索引文件和数据文件是分离的(非聚集)

*.frm:描述表结构的文件

*.MYI:索引信息文件(索引树)

*.MYD:数据信息文件

InnoDB存储引擎

表数据文件本身就是按B+Tree组织的一个索引结构文件
聚集索引----叶节点包含了完整的数据记录

*.frm:描述表结构的文件

*.ibd:索引树,同时叶节点包含数据全部信息

为什么InnoDB表必须有主键?

表数据文件本身就是按B+Tree组织的一个索引结构文件。若没有显式声明主键,则其内部会生成一个row_id主键索引。

为什么推荐使用整型的自增主键?

  1. 整型方便比较大小,节省空间。
  2. 自增插入,对原有的树结构影响不大。

为什么非主键索引结构叶子节点存储的是主键值?

1. 保持一致性
当数据库表进行DML操作时,同一行记录的页地址会发生改变,因非主键索引保存的是主键的值,无需进行更改。

2. 节省存储空间

索引增删语句

创建索引

# 建表时创建
CREATE TABLE user (
	user_id INT AUTO_INCREMENT COMMENT '用户id',
	name VARCHAR(30) COMMENT '用户名',
	email VARCHAR(50) COMMENT '邮箱',
	birthday DATETIME COMMENT '生日',
	
    PRIMARY KEY (user_id),  # 主键索引
	UNIQUE INDEX index_name (name), # 唯一索引
	INDEX index_3 (name, email, birthday)  # 普通索引、组合索引
) ENGINE = INNODB DEFAULT CHARSET = utf8;

# 建表后创建
ALTER TABLE user ADD UNIQUE INDEX index1(name); # 唯一索引
ALTER TABLE user ADD INDEX (name, email, birthday)  # 普通索引、组合索引

查看索引

show index from 表名;

删除索引

# 删除index1索引
ALTER TABLE user drop INDEX index1; 

索引分类

逻辑区分

根据索引的具体用途,MySQL 中的索引在逻辑上分为以下 5 类:

1) 普通索引

普通索引是 MySQL 中最基本的索引类型,它没有任何限制,唯一任务就是加快系统对数据的访问速度。

普通索引允许在定义索引的列中插入重复值和空值。

ALTER TABLE user ADD INDEX index1(id);

2) 唯一索引

唯一索引与普通索引类似,不同的是创建唯一性索引的目的不是为了提高访问速度,而是为了避免数据出现重复。

唯一索引列的值必须唯一,允许有空值。如果是组合索引,则列值的组合必须唯一。

ALTER TABLE user ADD UNIQUE INDEX index1(id);

3) 主键索引

主键索引就是专门为主键字段创建的索引,也属于索引的一种。

主键索引是一种特殊的唯一索引,不允许值重复或者值为空。

创建主键索引通常使用 PRIMARY KEY 关键字。

4) 空间索引(了解)

空间索引是对空间数据类型的字段建立的索引,使用 SPATIAL 关键字进行扩展。

创建空间索引的列必须将其声明为 NOT NULL,空间索引只能在存储引擎为 MyISAM 的表中创建。

空间索引主要用于地理空间数据类型 GEOMETRY。这类索引很少会用到。

5) 全文索引(了解)

全文索引主要用来查找文本中的关键字,只能在 CHAR、VARCHAR 或 TEXT 类型的列上创建。

全文索引允许在索引列中插入重复值和空值。

不过对于大容量的数据表,生成全文索引非常消耗时间和硬盘空间。

创建全文索引使用 FULLTEXT 关键字。

实际使用区分

索引在逻辑上分为以上 5 类,但在实际使用中,索引通常被创建成单列索引和组合索引。

1)单列索引

单列索引就是索引只包含原表的一个列。在表中的单个字段上创建索引,单列索引只根据该字段进行索引。

单列索引可以是普通索引,也可以是唯一性索引,还可以是全文索引。只要保证该索引只对应一个字段即可。

ALTER TABLE user ADD UNIQUE INDEX index1(name);

# 只根据前四个字符建立索引
ALTER TABLE user ADD UNIQUE INDEX index1(name(3));

2)多列索引

组合索引也称为复合索引或多列索引。相对于单列索引来说,组合索引是将原表的多个列共同组成一个索引。多列索引是在表的多个字段上创建一个索引。该索引指向创建时对应的多个字段,可以通过这几个字段进行查询。但是,只有查询条件中使用了这些字段中第一个字段时,索引才会被使用。

# 在student表中的 name 和 address 字段上建立名为 index1 的索引
ALTER TABLE user ADD UNIQUE INDEX index1(name,address);

是否是主键索引

仅限InnoDB

1)主键索引

2)二级索引

InnoDB存储引擎,需要回表

一条SQL的执行过程

连接器
一条SQL语句从客户端传过来首先会创建一个连接,用username和password认证身份
连接完成后如果没有其他操作便处于空闲状态 默认8h自动断开空闲连接
连接还分为长连接和短连接
长连接:持续使用同一个连接处理请求
短连接:一个连接仅执行几次后便断开,然后重新建立连接
因为创建连接的过程比较复杂,所以建议尽量使用长连接

查询缓存
建立连接后先去查询缓存
但是大家基本不用mysql的缓存功能
因为只要有对该表数据更新,表上的所有缓存都会清空然后重新创建缓存
所以一般默认不查缓存
PS.MySQL8.0直接将查询缓存功能删掉了

分析器
经过缓存器后来到分析器
先做词法分析 分析出sql语句中的关键字
然后做语法分析 判断是否有语法错误

优化器
经过分析后MySQL知道你要做什么了,但是在实际执行之前还得经过优化器优化一下
在表中有多个索引的时候,由优化器来决定使用哪个索引
或者有多表关联(join)的时候决定连接顺序,选择效率高的方案

执行器
执行器开始执行之前会验证是否有读/写权限,没有则返回权限错误
有的话就打开表调用指定的存储引擎接口获取执行结果集,返回给客户端

索引实践

1. 全值匹配

全值匹配指的是和索引中的所有列进行匹配

2. 最左前缀法则

如果索引了多列, 要遵守最左前缀法则。指的是查询从索引的最左前列开始并且不跳过索引中的列。

name  birthday email
SELECT * FROM user WHERE name= 'LiLei' AND birthday = '2018-09-30 00:00:00';

SELECT * FROM user WHERE name= 'LiLei' AND email = '123@qq.com' AND birthday = '2018-09-30 00:00:00';

3. 不在索引列上做任何转换、函数操作

不在索引列上做任何转换、函数操作,会导致索引失效而转向全表扫描

SELECT * FROM user WHERE left(name,3) = 'LiLei';
SELECT * FROM user WHERE name = 'LiLei';

select * from user where date(birthday) ='2018-09-30';
select * from user where birthday >='2018-09-30 00:00:00' and birthday <='2018-09-30 23:59:59';

4. 存储引擎不能使用索引中范围条件右边的列

# index(name, age, email)
# age > 22为范围条件,其后的email不能使用索引
SELECT * FROM user WHERE name= 'LiLei' AND age > 22 AND email = '123@qq.com';
SELECT * FROM user WHERE name= 'LiLei' AND age = 22 AND email = '123@qq.com';

5. 尽量使用覆盖索引

尽量使用覆盖索引(只访问索引的查询(索引列包含查询列)),减少select *语句

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

7. is null, is not null 索引失效

8. like以通配符开头('%abc...')索引失效

9. 字符串不加单引号索引失效

SELECT * FROM user WHERE name = '1000';
SELECT * FROM user WHERE name = 1000; #不加单引号,会转换数字为字符串,索引失效


10. 少用or或in

少用or或in,用它查询时,mysql不一定使用索引,mysql内部优化器会根据检索比例、 表大小等多个因素整体评估是否使用索引,详见范围查询优化

11. 范围查询优化

# 没走索引原因:mysql内部优化器会根据检索比例、表大小等多个因素整体评估是否使用索引。比如这个例子,可能是由于单次数据量查询过大导致优化器最终选择不走索引
select * from user where age >=1 and age <=2000;

# 优化方法:可以讲大的范围拆分成多个小范围
select * from user where age >=1 and age <=1000;
select * from user where age >=1001 and age <=2000;

Explain详解

每个表会输出一行,如果有两个表通过 join 连接查询,那么会输出两行

Explain两个变种

  • explain extended:会在 explain 的基础上额外提供一些查询优化的信息。紧随其后通 过 show warnings 命令可以得到优化后的查询语句,从而看出优化器优化了什么。

  • explain partitions:相比 explain 多了个 partitions 字段,如果查询是基于分区表的话,会显示查询将访问的分区。

Explain中的列

id列

id列的编号是 select 的序列号,有几个select 就有几个id,并且id的顺序是按 select 出现的顺序增长的。

id列越大执行优先级越高,id相同则从上往下执行,id为NULL最后执行。

select_type列

select_type 表示对应行是简单还是复杂的查询。

  • simple:简单查询。查询不包含子查询和union
  • primary:复杂查询中最外层的 select
  • subquery:包含在 select 中的子查询(不在 from 子句中)
  • derived:包含在 from 子句中的子查询。MySQL会将结果存放在一个临时表中,也称为派生表
  • union:在 union 中的第二个和随后的 select

table列

这一列表示 explain 的一行正在访问哪个表。 当 from 子句中有子查询时,table列是 格式,表示当前查询依赖 id=N 的查 询,于是先执行 id=N 的查询。 当有 union 时,UNION RESULT 的 table 列的值为,1和2表示参与 union 的 select 行id。

type列

表示关联类型或访问类型,即MySQL决定如何查找表中的行,查找数据行记录的大概范围。

依次从最优到最差分别为:system > const > eq_ref > ref > range > index > ALL

一般来说,需要保证查询达到range级别,最好达到ref

NULL:mysql能够在优化阶段分解查询语句,在执行阶段用不着再访问表或索引。例如:在 索引列中选取最小值,可以单独查找索引来完成,不需要在执行时访问表

const, system:mysql能对查询的某部分进行优化并将其转化成一个常量(可以看show warnings 的结果)。用于 primary key 或 unique key 的所有列与常数比较时,所以表最多有一个匹配行,读取1次,速度比较快。system是const的特例,表里只有一条元组匹配时为 system

eq_ref:primary key 或 unique key 索引的所有部分被连接使用 ,最多只会返回一条符合条件的记录。这可能是在 const 之外最好的联接类型了,简单的 select 查询不会出现这种 type

ref:相比 eq_ref,不使用唯一索引,而是使用普通索引或者唯一性索引的部分前缀,索引要和某个值相比较,可能会找到多个符合条件的行。

index:扫描全表索引,不获取索引的数据,这通常比ALL快一些。

ALL:即全表扫描,意味着mysql需要从头到尾去查找所需要的行。通常情况下这需要增加索 引来进行优化了

key列

这一列显示mysql实际采用哪个索引来优化对该表的访问。 如果没有使用索引,则该列是 NULL。

key_len列

这一列显示了mysql在索引里使用的字节数,通过这个值可以算出具体使用了索引中的哪些 列。

举例来说,film_actor的联合索引 idx_film_actor_id 由 film_id 和 actor_id 两个int列组成, 并且每个int是4字节。通过结果中的key_len=4可推断出查询使用了第一个列:film_id列来执 行索引查找。

rows列

mysql估计要读取并检测的行数

Extra列

这一列展示的是额外信息。常见的重要值如下:

using index: 索引覆盖。什么是索引覆盖呢?就是,查询的列都在索引里面可以找到,这样就不用去数据库里面查找了。性能全表扫描高。如果这里用到了where条件,那么条件必须是索引的前缀列。
using where: 查询的列没有被索引覆盖,并且where 的条件非索引的前缀列。没有从索引里面查数据
using where,using index: 查询的列被索引覆盖,且where条件非索引前缀列。从索引取数据,但是没有从索引里面查数据
null :查询的列没有被索引覆盖,但是where条件是索引前缀列。没有从索引取数据,但是从索引查数据
using index condition: 未被索引覆盖,where条件是前缀列,但是是前缀列的范围查询。就是说前缀列使用了大于小于等符号。
using temporary: 使用了临时表来处理数据。这里讲一下临时表和衍生查询,衍生查询侧重的是查询,查询的表来自于select产出的表,不是直接查找数据库的表。临时表是经过处理的表,比如group by,distinct 等。
using filesort: 文件排序。这里说明没有走索引排序

Select tables optimized away:使用某些聚合函数(比如 max、min)来访问存在索引的某个字段


hhhhh