本篇博客偏理论, 将会介绍如下知识:html
数据库的数据通常存储在磁盘中, 相比较内存, 磁盘的访问速度较慢索引就是能够帮助数据库快速从磁盘中找到数据的一种数据结构python
虽然会下降, 可是通常的应用系统,读写比例在10:1左右,并且插入操做和通常的更新操做不多出现性能问题,遇到最多的,也是最容易出问题的,仍是一些复杂的查询操做,因此查询语句的优化显然是重中之重mysql
缘由 :sql
1.一个软件慢会影响用户体验, 可是慢的缘由有不少, 你不能当即肯定就是 SQL 的问题, 当你定位到 SQL 问题的时候就已通过去好久了, 问题没有获得及时的解决数据库
2.大多数DBA都是管理型DBA而非开发型, 因此即使是DBA从日志中看到了慢查询sql, 也会由于其不懂业务而很难分析出慢的缘由windows
就好比买火车票(无索引) : 若是没有12360火车票订购软件, 摆在咱们面前的就是成千上万辆火车, 选择那一辆的条件有火车类型、出发和终点、时间等等, 咱们须要一辆一辆火车去比对本身的筛选条件, 运气好第一辆就是要找的火车, 运气很差第一千辆才是要找的火车缓存
加入索引 : 如今咱们只须要在12360软件上选择高铁, 就能筛选掉不是高铁的火车, 缩小了查询范围; 再输入出发点和终点, 又缩小了查询范围; 再输入时间, 范围又减小, 最终找到本身须要的车次, 由不固定查询次数变成很小的固定查询次数数据结构
IO延迟 = 平均寻道时间 + 平均延迟时间(通常为9ms)--->例子:假设当前硬盘转轴(盘片)转速是7200/min,也就是120/s,那么转一圈须要花费1/120≈8ms,半圈也就是4ms(假设找到数据要半圈)函数
9ms左右对于咱们来说很短, 但对于一台500-MIPS的机器来讲每秒能够执行5亿条指令, 换句话说执行一次IO的时间能够执行40万条指令,数据库动辄十万百万乃至千万级数据,每次9毫秒的时间, 这简直是场灾难性能
考虑到磁盘IO是很是高昂的操做,计算机操做系统作了一些优化,当一次IO时,不光把当前磁盘地址的数据,而是把相邻的数据也都读取到内存缓冲区内,由于局部预读性原理告诉咱们,当计算机访问一个地址的数据的时候,与其相邻的数据也会很快被访问到。每一次IO读取的数据咱们称之为一页(page)。具体一页有多大数据跟操做系统有关,通常为4k或8k,也就是咱们读取一页内的数据时候,实际上才发生了一次IO,这个理论对于索引的数据结构设计很是有帮助
索引的数据结构是 B+树, 而 B+树 是通过 二叉排序树 到 二叉平衡树 再到 B树 最后到 B+树 演变过来的, 下面简单介绍一下:
对于一列数字 : 五、六、七、八、九、10
利用二叉排序树咱们只须要3次便可找到匹配的数据; 若是在数字列中一条条的查找的话,咱们须要5次才能找到
上面咱们讲解了利用二叉排序树能够快速的找到数据; 可是,若是上面的二叉排序树是这样的构造:
平均查找长度是3, 若是咱们调整一下关键字的序列
调整以后平均查找长度是 2.2, 从上面咱们能够看出平均查找长度与数的高度有关, 平均查找长度越小, 查找速度就越快, 因此咱们应该尽量的让这棵树矮
这里引入了平衡因子的概念, 左子树的高度减右子数的高度就是平衡因子, 平衡因子的绝对值小于或等于一就是平衡二叉树, 大于一就是非平衡二叉树, 以下图平衡因子为 4 就是非平衡二叉树
咱们调整一下关键字序列, 各子数平衡因子绝对值都小于或等于 1, 那么这就是一颗平衡二叉树
若是咱们要存储海量的数据呢?能够想象到二叉树的节点将会很是多,高度也会及其高,咱们查找数据时也会进行不少次磁盘IO,咱们查找数据的效率将会极低
从上图能够看出,B树相对于平衡二叉树,每一个节点(B树中节点称之为页)存储了更多的键值(key)和数据(data),而且每一个节点拥有更多的子节点,子节点的个数通常称为阶,上述图中的B树为3阶B树,高度也会很低。 基于这个特性,B树查找数据读取磁盘的次数将会不多,数据的查找效率也会比平衡二叉树高不少
假设每一个节点能够储存两个值(不表明只能存两个), 咱们找到75:
- 先与 页1 比较,在 35 右边找到 p3指针 定位到 页4
- 与 页4 中的索引对比, 在 65-87 之间, 找到指针 p2, 定位到 页10
- 与 页10 中的索引对比, 找到相对应的 75
B+ 树非叶子节点上是不存储数据的,仅存储键值,而 B 树节点中不只存储键值,也会存储数据
之因此这么作是由于在数据库中页的大小是固定的,InnoDB 中页的默认大小是 16KB。若是不存储数据,那么就会存储更多的键值,相应的树的阶数(节点的子节点树)就会更大,树就会更矮更胖,如此一来咱们查找数据进行磁盘的 IO 次数又会再次减小,数据查询的效率也会更快
B+ 树的阶数是等于键值的数量的,若是咱们的 B+ 树一个节点能够存储 1000 个键值,那么 3 层 B+ 树能够存储 1000×1000×1000=10 亿个数据。
通常根节点是常驻内存的,因此通常咱们查找 10 亿数据,只须要 2 次磁盘 IO
3层的b+树能够表示上百万的数据,若是上百万的数据查找只须要两次IO,性能提升将是巨大的,若是没有索引,每一个数据项都要发生一次IO,那么总共须要百万次的IO,显然成本很是很是高
- 索引字段要尽可能的小 : 磁盘块的大小也就是一个数据页的大小,是固定的. 若是数据项占的空间越小,数据项的数量越多,树的高度就越低, 查询过的IO次数就越少. 这就是为何每一个数据项,即索引字段要尽可能的小,好比int占4字节,要比bigint8字节少一半。这也是为何b+树要求把真实的数据放到叶子节点而不是内层节点,一旦放到内层节点,磁盘块的数据项会大幅度降低, 降低则会致使每层可存储的数据就少, 由于磁盘块是固定的, 从而要增长层次, 进而致使树增高, 树增高意味着找到底层数据的IO次数增多, 致使查询速度大幅度降低
- 索引的最左匹配特性 : 当b+树的数据项是复合的数据结构,好比(name,age,sex)的时候,b+数是按照从左到右的顺序来创建搜索树的. 好比当(张三,20,F)这样的数据来检索的时候,b+树会优先比较name来肯定下一步的所搜方向,若是name相同再依次比较age和sex,最后获得检索的数据. 但当(20,F)这样的没有name的数据来的时候,b+树就不知道下一步该查哪一个节点,由于创建搜索树的时候name就是第一个比较因子,必需要先根据name来搜索才能知道下一步去哪里查询。 好比当(张三,F)这样的数据来检索时,b+树能够用name来指定搜索方向,但下一个字段age的缺失,因此只能把名字等于张三的数据都找到,而后再匹配性别是F的数据了, 这个是很是重要的性质,即索引的最左匹配特性
数据库中的 B+树 索引能够分为汇集索引(clustered index)和辅助索引(secondary index), 汇集索引与辅助索引相同的是:无论是汇集索引仍是辅助索引,其内部都是B+树的形式,即高度是平衡的, 不一样的是 :
汇集索引的叶子节点存放的是一整行完整的信息, 而辅助索引的叶子节点存放的并不是完整信息(下面介绍)
InnoDB 汇集索引的叶子节点存储行记录,所以 InnoDB 必需要有且只有一个汇集索引
若是表定义了 PK (Primary Key,主键),那么 PK 就是汇集索引
若是表没有定义 PK,则第一个不为空且惟一(NOT NULL UNIQUE) 的列就是汇集索引
不然 InnoDB 会另外建立一个隐藏的 ROWID 做为汇集索引
因为这种机制是直接定位行记录,所以使得基于 PK 的查询速度很是快
表中除了汇集索引外其余索引都是辅助索引(Secondary Index,也称为非汇集索引)
与汇集索引的区别是:辅助索引的叶子节点不包含行记录的所有数据。叶子节点除了包含键值之外,每一个叶子节点中的索引行中还包含一个书签(bookmark)。该书签用来告诉InnoDB存储引擎去哪里能够找到与索引相对应的行数据
辅助索引的存在并不影响数据在汇集索引中的组织,所以每张表上能够有多个辅助索引,但只能有一个汇集索引
当经过辅助索引来寻找数据时,InnoDB存储引擎会遍历辅助索引并经过叶子级别的指针得到指向主键索引的主键,而后再经过主键索引来找到一个完整的行记录
上面的三种索引, 惟一索引除了能够增长查询速度以外各自还具备约束条件, 而普通索引index key没有任何的约束条件,只是用来帮助你加快速查询数据
注意:联合索引不是用来加速查询用的,不在咱们的而研究范围以内
hash类型的索引:查询单条快,范围查询慢 btree类型的索引:b+树,层数越多,数据量指数级增加(咱们就用它,由于innodb默认支持它)
InnoDB 支持事务,支持行级别锁定,支持 B-tree、Full-text 等索引,不支持 Hash 索引 MyISAM 不支持事务,支持表级别锁定,支持 B-tree、Full-text 等索引,不支持 Hash 索引 Memory 不支持事务,支持表级别锁定,支持 B-tree、Hash 等索引,不支持 Full-text 索引 NDB 支持事务,支持行级别锁定,支持 Hash 索引,不支持 B-tree、Full-text 等索引 Archive 不支持事务,支持表级别锁定,不支持 B-tree、Hash、Full-text 等索引
🍓方式一 : 建立表时建索引 create table [表名] ( [unique|fulltext|spatial] [index|key] [索引名] [字段名(长度)] [asc|desc] ); 🍓方式二 : 在已存在的表上建立 create [unique|fulltext|spatial] index [索引名] on [表名] [字段名(长度)] [asc|desc]; 🍓方式二 : alter 在已存在的表上建立索引 alter table [表名] add [unique|fulltext|spatial] index [索引名] [字段名(长度)] [asc|desc];
🍓方式一 create table t01( id int, name char(10), age int, sex enum("male","female"), unique key unique_id(id), index index_name(name) # index没有key ); 🍓方式二 create index index_age on t01(age); 🍓方式三 alter table t01 add index index_sex(sex);
drop index [索引名] on t01; # 语法 drop index index_age on t01; # 示例
🍓建立表 create table t01( id int, name varchar(10), sex enum("male","female"), email varchar(18) ); 🍓建立存储过程,进行自动插入记录 delimiter %%% create procedure p01() begin declare i int default 1; while(i<3000000)do insert t01 value(i,"shawn","male",concat("shawn",i,"@163.com")); set i=i+1; end while; end %%% delimiter ; 🍓查看存储过程 show create procedure p01\G # \G 垂直显示结果 🍓调用存储过程 call p01(); # windows执行测试大概一个半小时,3百万条记录,200多M 🍓删除存储过程 drop procedure p01;
select * from t01 where id=3000000;
没有索引,mysql不知道有没有这条记录, 因此从头至尾的对记录进行遍历,有多少磁盘块就要进行多少I\O,速度很慢
create index index_id on t01(id); # 为 id 字段创建普通索引
观察 data 文件夹下的 t01 表数据文件大小增长了
select * from t01 where id=3000000; # 能够观察到速度明显的提高
mysql先去索引表里根据 b+树 的搜索原理很快搜索到 id 等于3000000的记录,直接命中索引, IO大大下降,于是速度明显提高
咱们以没有创建索引的字段设置为条件来进行查询, 能够发现速度依然很慢
select * from t01 email="shawn3000000@163.com"; # 而且记录越大查询越慢
对 email 字段创建索引试试
create index index_email on t01(email); # 字段数据越大,创建的时间越长(因此建议不要使用数据很大的字段创建索引,这里只是作实验) select * from t01 where email="shawn3000000@163.com"; # 再去查询,能够发现速度是数量级的提高
并非说建立了索引就必定能加速查询, 有些状况就算命中了索引也未必能起到很好的提速效果, 下面来测试一下各类状况 (若是不想看过程,能够直接看小结末尾的结论)
出现上面的状况就是由于字段的区分度过低, 在 B+树 中对于这些字段没法比较大小, 由于值都是相等的, 毫无疑问,只能增长树的高度来保证这些数据的存储, 树的高度越高, 查询速度就越慢
🍓"and"与"or"的逻辑 [条件1] and [条件2] : 全部条件都成立才算成立,但凡要有一个条件不成立则最终结果不成立 [条件1] or [条件2] : 只要有一个条件成立则最终结果就成立 🍓"and"的工做原理 条件: a = 10 and b = 'xxx' and c > 3 and d =4 索引: 制做联合索引(d,a,b,c) 工做原理: 对于连续多个and:mysql会按照联合索引,从左到右的顺序找一个区分度高的索引字段(这样即可以快速锁定很小的范围),加速查询,即按照d—>a->b->c的顺序 🍓"or"的工做牌原理 条件: a = 10 or b = 'xxx' or c > 3 or d =4 索引: 制做联合索引(d,a,b,c) 工做原理: 对于连续多个or:mysql会按照条件的顺序,从左到右依次判断,即a->b->c->d
create table user( -> id int not null auto_increment, -> name char(16) not null, -> age int not null, -> primary key(id), # id 为主键并设置索引(汇集索引) -> index index_name(name)); # name 字段设置索引(辅助索引) insert user(name,age) value -> ("shawn",23), -> ("song",22), -> ("hai",20), -> ("xing",18), -> ("yanxi",45), -> ("zichen",25);
select * from user where id=2;
上面为主键查询方式, 即经过汇集索引, 能找到 id 为 2 的完整记录
select * from user where name="song";
上面为辅助索引查询方式, 则须要先搜索 name 索引树,获得 song 对应的 id 值为 2,再到 id 索引树搜索一次, 这个过程称为回表
select id from user where name="hai";
上面语句查询的条件是 name 字段, name 字段有索引树, 而且上面保存有 name 和 id 的值, 能够直接提供查询结果, 不须要进行回表操做, 也就是说, 在这个查询里面, 索引 name 已经覆盖了咱们所要查询的 id 字段需求, 这就称为覆盖索引
select age from user where name="xing";
上面语句经过 name 索引树找到 name 字段对应的 "xing" 和 id 值, 但没有 age 字段信息, 因而经过 id 字段进行回表操做查找到知足条件的数据
🔰联合索引是指对表上的多个列合起来作一个索引. 联合索引的建立方法与单个索引的建立方法同样,不一样之处在仅在于有多个索引列
🔰最左前缀匹配原则, 是很是重要的原则, mysql会从左到右进行匹配
drop index index_name on user; create index index_name_age on user(name,age); # 实际应用中应该把最经常使用的字段放在最左边
select name,age from user where name="song"; # 条件字段 name select name,age from user where name="song" and age>18; # 条件字段 name + age
select name,age from user where age=2; # 条件字段 age (不会走联合索引)
索引下推(index condition pushdown )简称ICP,在Mysql5.6的版本上推出,用于优化查询
使用最左前缀匹配原则 + 联合查询能够加快查询速度, 若是咱们的条件存在范围查询, 那么 SQL 语句是怎么运行的呢?
select * from user where name like "s%" and age=22;
如上表的记录, "s" 开头的记录有两条
Innodb 会忽略 age 这个字段, 直接经过 name 来进行查询, 在(name,age)这个联合索引上找到两条结果, 而后拿到 id 为 1 和 2 进行"两次回表查询"
Innodb 不会忽略 age 这个字段, 而是在索引内部就判断了 age 是否等于 22, 不等于 22 的记录直接跳过 所以在(name,age)这个联合索引上只匹配到一个记录, 此时拿着这一个 id 去回表到全部的数据只须要"回表一次"
官方文档 : https://dev.mysql.com/doc/refman/8.0/en/explain-output.html
ps : 强调 rows 是核心指标,绝大部分 rows 小的语句执行必定很快。因此优化语句基本上都是在优化rows
explain 简称查看执行计划,使用 explain 关键字能够模拟优化器执行SQL查询语句,从而知道MySQL 是如何处理 SQL 语句的
语法 : explain + [SQL语句]
explain select * from t01;
字段 | 说明 |
---|---|
id | MySQL Query Optimizer 选定的执行计划中查询的序列号。表示查询中执行 select 子句或操做表的顺序,id值越大优先级越高,越先被执行; 若id 相同,执行顺序由上至下 |
select_type 查询类型 | 说明 |
---|---|
SIMPLE | 简单的 select 查询,不使用 union 及子查询 |
PRIMARY | 最外层的 select 查询 |
UNION | UNION 中的第二个或随后的 select 查询,不 依赖于外部查询的结果集 |
DEPENDENT UNION | UNION 中的第二个或随后的 select 查询,依 赖于外部查询的结果集 |
SUBQUERY | 子查询中的第一个 select 查询,不依赖于外 部查询的结果集 |
DEPENDENT SUBQUERY | 子查询中的第一个 select 查询,依赖于外部 查询的结果集 |
DERIVED | 用于 from 子句里有子查询的状况。 MySQL 会 递归执行这些子查询, 把结果放在临时表里 |
UNCACHEABLE SUBQUERY | 结果集不能被缓存的子查询,必须从新为外 层查询的每一行进行评估 |
UNCACHEABLE UNION | UNION 中的第二个或随后的 select 查询,属 于不可缓存的子查询 |
字段 | 说明 |
---|---|
table | 输出行所引用的表 |
很是重要的项, 显示链接使用的类型, 按最优到最差的类型排序
type : 链接类型 | 说明 |
---|---|
system | 表仅有一行(=系统表)。这是 const 链接类型的一个特例 |
const | const 用于用常数值比较 PRIMARY KEY 时。当 查询的表仅有一行时,使用 System |
eq_ref | const 用于用常数值比较 PRIMARY KEY 时。当 查询的表仅有一行时,使用 System |
ref | 链接不能基于关键字选择单个行,可能查找 到多个符合条件的行。 叫作 ref 是由于索引要 跟某个参考值相比较。这个参考值或者是一 个常数,或者是来自一个表里的多表查询的 结果值 |
ref_or_null | 如同 ref, 可是 MySQL 必须在初次查找的结果 里找出 null 条目,而后进行二次查找。 |
index_merge | 说明索引合并优化被使用了 |
unique_subquery | 在某些 IN 查询中使用此种类型,而不是常规的 ref:value IN (SELECT primary_key FROM single_table WHERE some_expr) |
index_subquery | 在 某 些 IN 查 询 中 使 用 此 种 类 型 , 与 unique_subquery 相似,可是查询的是非惟一 性索引: value IN (SELECT key_column FROM single_table WHERE some_expr) |
range | 只检索给定范围的行,使用一个索引来选择 行。key 列显示使用了哪一个索引。当使用=、 <>、>、>=、<、<=、IS NULL、<=>、BETWEEN 或者 IN 操做符,用常量比较关键字列时,可 以使用 range |
index | 全表扫描,只是扫描表的时候按照索引次序 进行而不是行。主要优势就是避免了排序, 可是开销仍然很是大 |
all | 最坏的状况,从头至尾全表扫描 |
字段 | 说明 |
---|---|
possible_keys | 指出 MySQL 能在该表中使用哪些索引有助于 查询。若是为空,说明没有可用的索引 |
字段 | 说明 |
---|---|
key | MySQL 实际从 possible_key 选择使用的索引。 若是为 NULL,则没有使用索引。不多的状况 下,MYSQL 会选择优化不足的索引。这种情 况下,能够在 SELECT 语句中使用 USE INDEX (indexname)来强制使用一个索引或者用 IGNORE INDEX(indexname)来强制 MYSQL 忽略索引 |
字段 | 说明 |
---|---|
key_len | 使用的索引的长度。在不损失精确性的状况 下,长度越短越好。 |
字段 | 说明 |
---|---|
ref | 显示索引的哪一列被使用了 |
字段 | 说明 |
---|---|
rows | MYSQL 认为必须检查的用来返回请求数据的行数 |
extra 项 | 说明 |
---|---|
Using filesort | 表示 MySQL 会对结果使用一个外部索引排序,而不是从表里按索引次序读到相关内容。可能在内存或者磁盘上进行排序。MySQL 中没法利用索引完成的排序操做称为“文件排序” |
Using temporary | 表示 MySQL 在对查询结果排序时使用临时表。常见于排序 order by 和分组查询 group by |
explain select * from t01 where id=100000; explain select * from t01 where id>10000 and id<20000; explain select * from t01 where id>20000;