本节内容:
1、介绍
2、索引的原理(目的:加速查询)
3、索引的数据结构
4、聚集索引与辅助索引
5、MySQL索引管理
6、测试索引
7、正确使用索引
8、联合索引与覆盖索引
9、查询优化神器--explain
10、慢查询优化的基本步骤
11、慢日志管理
一、介绍
1、什么是索引
索引在MySQL中也叫做“键”或者"key"(primary key,unique key,还有一个index key),
是存储引擎用于快速找到记录的一种数据结构。
2、为什么要有索引
一般的应用系统,读写比例在10:1左右,
我们更多的是需要去查询,还有一些复杂的查询操作,
因此对查询语句的优化显然是重中之重。
说起加速查询,就不得不提到索引了。
3、索引的作用(加速查询)
索引对于良好的性能非常关键,尤其是当表中的数据量越来越大时,
索引对于性能的影响愈发重要,减少io次数,加速查询。
4、正确使用索引
1、创建索引的量要合理,
若索引太多,应用程序的性能可能会受到影响。
而索引太少,对查询性能又会产生影响,
要找到一个平衡点,这对应用程序的性能至关重要。
不要建立太多索引,有些索引是没有必要。
2、最好一开始就应该创建索引
如果知道数据的使用,从一开始就应该在需要处添加索引。
在创建的时候,你就要构思对数据的查询,建立好良好的索引,
以应对后面的大数据,复杂数据查询;
这就要求你对业务的数据流的了解。
二、索引的原理
1、索引的原理
索引的目的:提高查询效率,加快查询;
缺点:1.会降低写入的效率,
(因为你但凡加入一些新的数据,都需要把索引或者说书的目录重新做一个。)
2.在表中有大量数据的前提下,创建索引速度会很慢
2、磁盘IO与预读
1、磁盘IO
磁盘读取数据靠的是机械运动
每次读取数据的时间组成:寻道时间、旋转时间、传输时间
寻道时间:指的是磁臂移动到指定磁道所需要的时间,主流磁盘一般在5ms以下
旋转延迟:就是我们经常听说的磁盘转速,4.17m/s
传输时间:指的是从磁盘读出或将数据写入磁盘的时间,一般在零点几毫秒,相对于前两个时间可以忽略不计。
那么访问一次磁盘的时间,即一次磁盘IO的时间约等于5+4.17 = 9ms左右
但要知道一台500 -MIPS(Million Instructions Per Second)的机器每秒可以执行5亿条指令
这里已经是对cpu的严重浪费了。
数据库动辄十万百万乃至千万级数据,每次9毫秒的时间,显然是个灾难,
2、预读
考虑到磁盘IO是非常高昂的操作,计算机操作系统做了一些优化,
当一次IO时,不光把当前磁盘地址的数据,而是把相邻的数据也都读取到内存缓冲区内,
因为局部预读性原理告诉我们,当计算机访问一个地址的数据的时候,与其相邻的数据也会很快被访问到。
三、索引的数据结构
前面讲了索引的基本原理,数据库的复杂性,又讲了操作系统的相关知识,
目的就是让大家了解,现在我们来看看索引怎么做到减少IO,加速查询的。
任何一种数据结构都不是凭空产生的,一定会有它的背景和使用场景,
知道的具体的使用场景;我们就根据使用需求,来控制查找数据的IO
那就是:每次查找数据时把磁盘IO次数控制在一个很小的数量级,最好是常数数量级。
b+树应运而生
B+树是通过二叉查找树,再由平衡二叉树,B树演化而来,等到后面讲算法的时候再深入,
1、b+树,相关介绍
如上图,是一颗b+树,最上层是树根,中间的是树枝,最下面是叶子节点,
浅蓝色的块我们称之为一个磁盘块或者叫做一个block块,
这是操作系统一次IO往内存中读的内容,一个块对应四个扇区,
可以看到每个磁盘块包含几个数据项
(深蓝色所示,一个磁盘块里面包含多少数据,一个深蓝色的块表示一个数据,其实不是数据,后面有解释)
和指针(黄色所示,看最上面一个,
p1表示比上面深蓝色的那个17小的数据的位置在哪,
看它指针指向的左边那个块,里面的数据都比17小,
p2指向的是比17大比35小的磁盘块),
真实的数据存在于叶子节点即3、5、9、10、13、15、28、29、36、60、75、79、90、99。
非叶子节点只不存储真实的数据,只存储指引搜索方向的数据项,如17、35并不真实存在于数据表中。
2、b+树的查找过程
如图所示,如果要查找数据项29,那么首先会把磁盘块1由磁盘加载到内存,
此时发生一次IO,
在内存中用二分查找确定29在17和35之间,锁定磁盘块1的P2指针,
内存时间因为非常短(相比磁盘的IO)可以忽略不计,
通过磁盘块1的P2指针的磁盘地址把磁盘块3由磁盘加载到内存,
发生第二次IO,
29在26和30之间,锁定磁盘块3的P2指针,通过指针加载磁盘块8到内存,
发生第三次IO,
同时内存中做二分查找找到29,结束查询,总计三次IO。
3、b+树的性质
1.索引字段要尽量的小:
好处:
可以多存数据,比如int占4字节,要比bigint8字节少一半。
防止树增高,太大还可能导致退化成线性表。
2.索引的最左匹配特性:
从数据块的左边开始匹配,再匹配右边的,
当b+树的数据项是复合的数据结构,比如(name,age,sex)的时候,
b+树是按照从左到右的顺序来建立搜索树的,
四、聚集索引和辅助索引
聚集索引是什么呢,其实就是我们说的那个主键,
之前我们说Innodb存储引擎的表,必须有一个主键,
数据库中的B+树索引可以分为聚集索引(clustered index)和辅助索引(secondary index),
在数据库中,B+树的高度一般都在2~4层,
这也就是说查找某一个键值的行记录时最多只需要2到4次IO,这倒不错。
因为当前一般的机械硬盘每秒至少可以做100次IO,
2~4次的IO意味着查询时间只需要0.02~0.04秒。
聚集索引与辅助索引不同的是:叶子结点存放的是否是一整行的信息
1、聚集索引(叶子节点存放着,行记录数据)
InnoDB存储引擎表示索引组织表,即表中数据按照主键顺序存放。
而聚集索引(clustered index)就是按照每张表的主键构造一棵B+树,
同时叶子结点存放的即为整张表的行记录数据,也将聚集索引的叶子结点称为数据页。
1、聚集索引的优点
1.它对主键的排序查找和范围查找速度非常快,
叶子节点的数据就是用户所要查询的数据。
如用户需要查找一张表,查询最后的10位用户信息,由于B+树索引是双向链表,
所以用户可以快速找到最后一个数据页,并取出10条记录
2.范围查询(range query),
即如果要查找主键某一范围内的数据,通过叶子节点的上层中间节点就可以得到页的范围,
之后直接读取数据页即可
2、辅助索引(通过条件来查询)
表中除了聚集索引外其他索引都是辅助索引(Secondary Index,也称为非聚集索引)
(unique key啊、index key啊),
与聚集索引的区别是:辅助索引的叶子节点不包含行记录的全部数据。
就是我们在查询的时候,where后面需要写id之外的其他字段名称来进行查询,
叶子节点存放的是对应的那条数据的主键字段的值,
除了包含键值以外,每个叶子节点中的索引行中还包含一个书签(bookmark),
其实这个书签你可以理解为是一个{'name字段',name的值,主键id值}的这么一个数据。
如果我们select 后面要的是name,我们直接就可以在辅助索引的叶子节点找到对应的name值,
这种我们也可以称为覆盖索引。
回表操作,
需要通过辅助索引的叶子节点中保存的主键id的值再去通过聚集索引来找到完整的一条记录,
然后从这个记录里面拿出age的值,
五、MySQL索引管理
1、功能
1. 索引的功能就是加速查找
2. mysql中的primary key,unique,联合唯一也都是索引,
这些索引除了加速查找以外,还有约束的功能
2、MySQL常用的索引
普通索引INDEX:加速查找
唯一索引:
-主键索引PRIMARY KEY:加速查找+约束(不为空、不能重复)
-唯一索引UNIQUE:加速查找+约束(不能重复)
联合索引:
-PRIMARY KEY(id,name):联合主键索引
-UNIQUE(id,name):联合唯一索引
-INDEX(id,name):联合普通索引
fe:各种索引的应用场景
各种索引的应用场景3、索引的两大类型hash与btree
1、两大索引类型
我们可以在创建上述索引的时候,为其指定索引类型,分两类
hash类型的索引:查询单条快,范围查询慢
btree类型的索引:b+树,层数越多,数据量指数级增长(我们就用它,因为innodb默认支持它)
2、不同的存储引擎支持的索引类型也不一样
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 等索引;
4、创建/删除索引的语法
#方法一:创建表时
CREATE TABLE 表名 (
字段名1 数据类型 [完整性约束条件…],
字段名2 数据类型 [完整性约束条件…],
[UNIQUE | FULLTEXT | SPATIAL ] INDEX | KEY
[索引名] (字段名[(长度)] [ASC |DESC])
);
#方法二:CREATE在已存在的表上创建索引
CREATE [UNIQUE | FULLTEXT | SPATIAL ] INDEX 索引名
ON 表名 (字段名[(长度)] [ASC |DESC]) ;
#方法三:ALTER TABLE在已存在的表上创建索引
ALTER TABLE 表名 ADD [UNIQUE | FULLTEXT | SPATIAL ] INDEX
索引名 (字段名[(长度)] [ASC |DESC]) ;
#删除索引:DROP INDEX 索引名 ON 表名字;
fe:创建/删除索引的具体示例
创建/删除索引的具体示例六、测试索引
1、准备,创建一个300万的数据库
准备表2、在没有索引的前提下测试查询速度
#无索引:mysql根本就不知道到底是否存在id等于333333333的记录,
也不知道存在几条id=333333333的记录,只能把数据表从头到尾扫描一遍,
此时有多少个磁盘块就需要进行多少IO操作,所以查询速度很慢
mysql> select * from s1 where id=333333333;
Empty set (0.33 sec)
3、在表中已经存在大量数据的前提下,为某个字段段建立索引,建立速度会很慢
在表中已经存在大量数据的前提下,
为某个字段段建立索引,建立速度会很慢
或者用alter table s1 add primary key(id);加主键,建索引很慢的。
4、在索引建立完毕后,以该字段为查询条件时,查询速度提升明显
1. mysql先去索引表里根据b+树的搜索原理很快搜索到id等于333333333的记录不存在,
IO大大降低,因而速度明显提升
2. 我们可以去mysql的data目录下找到该表,可以看到占用的硬盘空间多了
3. 需要注意,用没有加索引的字段去查询,速度依然很慢
5、总结
1. 一定是为搜索条件的字段创建索引,
比如select * from s1 where id = 333;就需要为id加上索引
2. 在表中已经有大量数据的情况下,建索引会很慢,且占用硬盘空间,
建完后查询速度加快
3. 需要注意的是:innodb表的索引会存放于s1.ibd文件中,
而myisam表的索引则会有单独的索引文件table1.MYI
七、正确使用索引
并不是说我们创建了索引就一定会加快查询速度,
若想利用索引达到预想的提高查询速度的效果,
我们在添加索引时,必须遵循以下问题
1、索引未命中
1、范围问题,或者说条件不明确,条件中出现这些符号或关键字:
>、>=、<、<=、!= 、between...and...、like、
2、 尽量选择区分度高的列作为索引,区分度的公式是count(distinct col)/count(*),
表示字段不重复的比例,比例越大我们扫描的记录数越少,唯一键的区分度是1,
3、 =和in可以乱序,比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,
mysql的查询优化器会帮你优化成索引可以识别的形式
4、 索引列不能参与计算,保持列“干净”
所以语句应该写成create_time = unix_timestamp(’2014-05-29’)
5、and的工作原理:对于连续多个and:mysql会按照联合索引,找一个区分度高的索引字段
(这样便可以快速锁定很小的范围),加速查询,
6、or的工作原理:mysql会按照条件的顺序,从左到右依次判断,
7、最左前缀匹配原则(详见第八小节),非常重要的原则,
1、其他情况
其他情况2、其他注意事项
- 避免使用select *
- count(1)或count(列) 代替 count(*)
- 创建表时尽量时 char 代替 varchar
- 表的字段顺序固定长度的字段优先
- 组合索引代替多个单列索引(经常使用多个条件查询时)
- 尽量使用短索引
- 使用连接(JOIN)来代替子查询(Sub-Queries)
- 连表时注意条件类型需一致
- 索引散列值(重复少)不适合建索引,例:性别不适合
八、联合索引与覆盖索引
1、联合索引
联合索引时指对表上的多个列合起来做一个索引,
联合索引的创建方法与单个索引的创建方法一样,不同之处在仅在于有多个索引列,
1、注意建立联合索引的一个原则:
索引是有个最左匹配的原则的,所以建联合索引的时候,将区分度高的放在最左边,
依次排下来,范围查询的条件尽可能的往后边放。
2、联合索引的第二个好处
是在第一个键相同的情况下,已经对第二个键进行了排序处理,
fe:联合索引使用示例
示例2、覆盖索引
InnoDB存储引擎支持覆盖索引(covering index,或称索引覆盖),
即从辅助索引中就可以得到查询记录,而不需要查询聚集索引中的记录
使用覆盖索引的一个好处是:辅助索引不包含整行记录的所有信息,
故其大小要远小于聚集索引,因此可以减少大量的IO操作
fe:覆盖索引查询
名字2、覆盖索引的另外一个好处
是对某些统计问题而言的。基于上一小结创建的表buy_log,查询计划如下
名字
九、查询优化神器–explain
关于explain命令相信大家并不陌生,具体用法和字段含义可以参考官网explain-output,
这里需要强调rows是核心指标,绝大部分rows小的语句执行一定很快(有例外,下面会讲到)。所以优化语句基本上都是在优化rows。
关于explain,如果大家有兴趣,可以看看这篇博客,他总结的挺好的:
关于explain
官网的explain
MySQL 执行计划explain详解
十、 慢查询优化的基本步骤
0.先运行看看是否真的很慢,注意设置SQL_NO_CACHE
1.where条件单表查,锁定最小返回记录表。这句话的意思是把查询语句的where都应用到表中返回的记录数最小的表开始查起,单表每个字段分别查询,看哪个字段的区分度最高
2.explain查看执行计划,是否与1预期一致(从锁定记录较少的表开始查询)
3.order by limit 形式的sql语句让排序的表优先查
4.了解业务方使用场景
5.加索引时参照建索引的几大原则
6.观察结果,不符合预期继续从0分析
十一、慢日志管理
慢日志
- 执行时间 > 10
- 未命中索引
- 日志文件路径
配置:
- 内存
show variables like '%query%';
show variables like '%queries%';
set global 变量名 = 值
- 配置文件
mysqld --defaults-file='E:wupeiqimysql-5.7.16-winx64mysql-5.7.16-winx64my-default.ini'
my.conf内容:
slow_query_log = ON
slow_query_log_file = D:/....
注意:修改配置文件之后,需要重启服务