【十一】MySQL数据库之索引
索引
【一】索引概述
- 数据库索引是一个数据结构,其改善了数据检索操作的速度数据库表以额外写入和存储空间的费用来维持索引数据结构。
- 索引用于快速定位数据,而不必每次访问数据库表时都在数据库表中搜索每一行。
- 可以使用数据库表的一列或多列来创建索引,这为快速随机查找和有效访问有序记录提供了基础。
【1】什么是索引
- 我们为数据建立索引,在一定程度上和为书籍建立目录类似.
- 大家可以联想一下,为书建目录有什么好处?
- 当你没有为书建立目录之前,如果你需要找到书具体的某一页内容,你只能通过一页一页地去翻,这种操作我们可以称之为遍历,但这么做其实相当不可靠,可能在前几页你就能找到你所需要的内容,但也可能在最后几页才找到,这样查询次数就会变得非常不可控,效率很低.
- 可是一旦你为书建立了目录,你的查找方式就改变了,你就可以通过目录来查询到你所需要的内容,
- 那么到底什么是索引?
- 索引是一种数据数据结构,用通俗的话来说,就是一种组织数据的方式。
- 为数据库表中的一行行数据创建索引就好比是为书的一页页内容创建目录。
【2】为什么要用索引?
- 显而易见地
- 我们为数据创建索引是为了提高查询数据的速度。
- 由于在增加,删除或修改数据时,如果建立了索引,需要额外建立键值对的对应关系
- 所以创建完索引后增、删、改的效率反而会降低。
- 然而在我们日常的工作中,对数据库进行的读写比例大约在10:1
- 而且增删改的操作几乎都是在一张表中进行,很少会遇到效率问题
- 而查询有时候很复杂,会横跨多张表
- 所以使用索引总体上能大幅提升我们的工作效率。
【3】我们应该如何正确看待索引?
- 很多人会有一个误区,在一个软件开发完之后,等到发现这个软件运行地较慢时,才去添加索引,这就是一个错误的观点。
- 因为如果当一个软件上线之后,发现运行速度很慢,这种“慢”可能并不是绝对意义上的很长时间,但即使某一条SQL语句只卡顿2~3秒,也会严重影响用户的体验。
- 一旦这个问题出现以后,如果你再去“亡羊补牢”,就会发现并不一定是数据库结构带来的问题,当你将其他问题一一排除,并确定是由于SQL语句引起的,可能已经过去了很长时间。
- 此外很多负责运维的人并不擅长分析SQL语句。
- 那么,究竟应该如何正确看待索引呢?
(1)最好是在程序准备上线时,就提前考虑好索引的问题
- 分析有哪些功能是用户可能频繁使用到的,给这部分加上索引
(2)索引并不是加的越多越好
- 诚然如果你给所有字段都加上索引,那么查询所有字段的速度都会相应提升,可是如果你真的这么做了
- 即使你进行一个非常简单的“写”操作,都意味着硬盘上的索引结构要重新变化,可能带来非常多的IO行为。
【二】索引的数据结构
- 尽管为数据建立索引与为书籍建立目录十分类似
- 但两者也略有不同
- 不同的地方在于索引的创建分为两步
- 第一步
- 以索引字段作为键,与数据进行对应,
- 第二步
- 以键为基础,构造B+树
- 我们之前提到过,MySQL使用的默认存储引擎是InnoDB引擎,而InnoDB存储引擎中创建的是B+树结构的索引
- 那么问题来了,究竟什么是B+树结构的索引?
- 在介绍B+树索引结构之前,我们再引入一个例子来帮助更好地理解索引
- 比如你过年回家要买火车票,全国的火车票就相当于表中的一行行记录,如果要你直接查询具体回家的那一趟列车,由于全国有那么多趟列车,很明显无异于大海捞针,那你会怎么去查询?
- 你可以先搜索出发地,目的地,这样就可以筛选出一部分记录,范围就缩小了,然后再搜索具体的时间段,范围又进一步缩小了。
- 于是你可以不断地缩小范围,这样就可以大大提高查询效率。
- 而火车站规定筛选的这些条件其实就相当于索引。
- 在介绍B+树索引结构之前,我们再引入一个例子来帮助更好地理解索引
- 现在我们来看下B+树的发展历程。
- B+树是由二叉查找树,平衡二叉树,B树一步步发展而来
- 下面我们就介绍一下B+树的发展历史
二叉查找树
- 在上图的user表中
- 最初数据是杂乱无章地进行排列的
- 现在,我要将这张表变为二叉树结构,那如何操作呢?
第一步,我们首先要给它加上索引
- 假设我们以ID字段的值作为基础给它创建索引
- 比如第一条记录的id是10,我们就将它作为key取出,和后面的名字作为value一一对应
- 当我们依次将所有的记录都进行一遍类似的提取操作后,提炼完以后的表就相当于每一个key都对应一条记录。
第二步,我们可以以这个key值的大小作为依据
- 对整张表进行排序,放入类似于上图中右边的二叉树结构。
- 在这张图里,每一个圆圈我们称之为一个节点
- 其中没有子节点的我们称之为叶子节点
- 处于最上层的节点我们称为根节点
- 而位于中间的都称之为树枝。
- 很容易发现,二叉查找树任何节点的左子节点的键值都小于当前节点的键值,右子节点的键值都大于当前节点的键值。
上图中,如果我们想查询id为8的数据
- 如果不使用索引
- 我们要按顺序遍历
- 第五次能查询到数据
- 但如果使用二叉树索引
- 因为8比10小
- 我们直接可以定位到左边的树枝
- 而到了第二层,由于8比7大
- 我们又可以定位到右边的分支
- 最后在第三层找到了我们需要的数据
- 一共需要查找3次。
- 更重要的是
- 在这张表中
- 无论你要查询什么数据
- 由于树的结构一共只有3层
- 所以最多只需要3次查询就能找到。
- 这里的3层我们称之为二叉树的高度
- 很明显,二叉树的高度就是所需要查到的数据的最大IO次数
平衡二叉树
- 然而根据二叉查找树的特点
- 上图也可以称之为二叉查找树
- 可是这种二叉查找树的高度很高,根本无法达到减少IO次数的目的
- 于是我们引进了一个新的概念——平衡二叉树。
- 平衡二叉树的特点是
- 每个节点的左右子树的高度差不能超过1。
- 可是平衡二叉树的数据结构也会有一定的问题
- 以上图中的模型为例
- 我们创建索引时要将索引的键值对写入硬盘
- 然后每次IO读取一个节点的数据,放入内存,这样固然能提速
- 但就好比开卡车运货,为什么我们每辆卡车只放一个很小的货物呢?
B树
- 很容易想到,我们从硬盘往内存读取数据
- 读取的是一个磁盘块
- 或者叫数据库中的一页数据。
- 于是我们又过渡到了下一个阶段,B树。
- B树是在平衡二叉树的基础上
- 引入了磁盘块的概念
- 在一个磁盘块中放入较多的节点。
- 在B树的每一页中
- 引入了一个指针的概念
- 比如在上图中,根页根据不同的范围划分
- 用指针指向一个分枝,进一步提升了查询的效率。
- 然而B树仍存在一些问题
- B树每一页中的节点既存放key
- 又存放对应的记录
- 这意味着每个节点都要占用较大的空间
- 也就是说每页只能存放较少的节点
- 这样在存放同等数据量的情况下,树的高度会偏高
- 有没有办法进一步降低树的高度呢?
B+树
- B+树可以看作是在B树的基础上
- 非叶子节点不存储数据而只存放key
- 只有叶子节点才存储key和记录的值
- 这样,每个节点就能存储更多的键值
- 进一步降低树的高度。
- 此外,B+树的叶子节点之间也有指针指向,是有序排列的
- 这意味着B+树在排序操作中和有天然的优势
- 此外在范围查询中,一旦匹配到某个叶子节点,就可以直接按顺序去找其它的叶子节点,不必再一次从树根查起
- 这些优点使查找的效率得到大幅提升。
【三】索引的分类与区别
【1】hash 索引
索引可分为hash索引以及B+树索引
- 在查询速度上,如果是等值查询
- 那么hash索引明显有绝对优势
- 因为只需要经过一次 Hash 算法即可找到相应的键值
- 当然了,这个前提是键值都是唯一的。
- 如果键值不是唯一
- 就需要先找到该键所在位置
- 然后再根据链表往后扫描
- 直到找到相应的数据
- 会大大降低Hash索引的查找效率
- Hash 索引是无序的,如果是范围查询检索
- 这时候 Hash 索引就无法起到作用
- 即使原先是有序的键值
- 经过 Hash 算法后
- 也会变成不连续的了
- 所以hash算法不支持范围查询和排序运算
- 且无法支持多列联合索引和部分模糊查询
- 大多数场景下,都会有组合查询,范围查询、排序、分组、模糊查询等查询特征
- Hash 索引无法满足要求
- 建议数据库使用B+树索引。
- 但在一些离散型高
- 数据基数大,且等值查询时候,Hash索引更有优势。
【2】B+树索引的分类与区别
B+树的索引可分为聚簇索引和辅助索引
- 聚簇索引 :
- 又名聚集索引,是以主键字段的值(通常是id)作为key创建的索引(一张表中只有一个)
- 如果没有指定主键字段,会自动使用不为空且唯一的字段作为主键字段
- 如果没有符合条件的,Innodb存储引擎会自动创建一个隐藏的字段作为主键字段
- 聚簇索引叶子节点中存放的是id对应的一整条数据记录。
- 辅助索引:
- 针对非主键字段创建的索引(一张表中可以包含多个)
- 辅助索引叶子节点中存放的是对应的主键字段
【3】总结:
(1)聚集索引与辅助索引的相同点:
- 它们都是B+树结构,非叶子节点只存放key值,而叶子节点存放key和value
(2)聚集索引与辅助索引的不同点:
-
聚集索引key对应的value是一整行完整的记录
-
辅助索引key对应的value是其对应的主键字段的值
(3)此处还涉及到两个概念:回表查询和覆盖索引
- 接着,我们来举个例子详细说明一下覆盖索引和回表查询
- 首先,建立一张表,插入数据
create table t3(
id int primary key auto_increment,
name varchar(16),
age int
)engine=innodb;
insert into t3(name,age) values
("egon",18),
("tom",18),
("lili",18);
Id | name | age |
---|---|---|
1 | ly | 18 |
2 | tom | 18 |
3 | lili | 18 |
覆盖索引:
-
在当前命中的索引结构中,就能拿到所有你需要的数据
-
比如以下两条SQL语句查询这个表就能覆盖索引
select * from t3 where id = 3; -- 覆盖了索引
select name,id from t3 where name="ly"; -- 覆盖了索引
回表查询:
-
通过辅助索引拿到主键值
- 然后再回到聚集索引从根出发再查一下
- 获取其他字段的值
-
比如这条SQL语句
select name,id,age from t3 where name="ly";
- 因为无法直接查询到年龄
- 我们就要通过辅助索引拿到主键id
- 再从聚集索引中找到年龄的值
- 这就是回表操作
【四】联合索引与最左前缀匹配原则
- 联合索引是指对表上各个列(多个字段)合起来做一个索引
- 最左前缀匹配:
- 从联合索引的最左边开始匹配
- 一定要带着最左边的那个字段
- 最左前缀举例:
- 表结构
create index idx_id_name_gender on s1(id,name,gender);
- 当查询条件中出现以下字段
- 可以命中联合索引
- 因为符合最左前缀原则
id
id name
id gender
id name gender
- 例如下面的查询语句可以命中
where age = 18 and name = "egon" and gender="male"
- 而如果查询条件中只出现一个gender字段,不能命中
where gender="male"
【五】索引下推技术
- 索引下推(index condition pushdown )简称ICP
- 在Mysql5.的版本上推出,用于优化查询。
- 在不使用索引下推技术的情况下
- 在使用辅助索引进行查询时
- 存储引擎通过索引检索到数据
- 然后返回给MySQL服务器
- 服务器然后判断数据是否符合条件 。
- 在使用索引下推技术的情况下
- 如果存在某些列符合索引的的判断条件时
- MySQL服务器会将这一部分判断条件传递给存储引擎
- 然后由存储引擎通过判断索引是否符合MySQL服务器传递的条件
- 只有当索引符合条件时才会将数据检索出来返回给MySQL服务器 。
- 索引下推技术可以减少存储引擎查询基础表的次数
- 也可以减少MySQL服务器从存储引擎接收数据的次数。
【六】如何正确使用索引?
- 看到这里,相信不少读者一定会有个疑问
- 命中索引是否一定会明显加速?
- 答案应该是不一定
- 当查询的过程中出现以下情况时
- 比如查询范围过大,字段占用空间过多或区分度低
- 或是索引被放入函数或参与运算
- 即使索引命中了,也不会显著提高查询的速度
如何正确使用索引。
1 对区分度高并且占用空间小的字段建立索引
2 针对范围查询命中了索引,如果范围很大,查询效率依然很低
- 解决方法:
- 把查询范围缩小
- 或分段/分页取值
- 一段一段取最终把大范围给取完
- 也可以搭配使用缓存
3 使用索引下推技术(默认开启)
4 不要把查询字段放到函数或者参与运算
举例:
- 不推荐的sql语句(把查询字段放入函数)
select count(*) from where id*12 = 3;
- 推荐的sql语句(查询字典不放入函数)
select count(*) from where id = 3/12;
5 索引覆盖:在命中索引的前提下,select查找值存在于本索引树中
create index idx_name on s1(name);
select name,id from s1 where name="egon"; -- 覆盖了索引
select name,id,age from s1 where name="egon"; -- 没有覆盖索引,需要回表操作
注意:
- 如果查询条件是用的主键字段
- 那么无论select查什么都会覆盖索引
- 都不需要回表。
- 所以,在查询语句中,能用主键字段作为查询依据就尽量用它
6 创建联合索引,并遵循最左前缀匹配原则
create index idx_xxx on s1(name,age,gender);