MySQL 索引详解
什么是索引?MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效检索数据的数据结构。简单的来说,索引就相当于字典的目录,帮助我们更快地找到需要查找的文字。索引的优缺点优点:使用索引可以大大加快数据的检索速度(大大减少检索的数据量),这也是创建索引的主要原因如果是唯一索引,可以确保数据表中的每一行记录的唯一性缺点:索引需要占用额外的存储空间创建和维护索引需要耗费时间。MySQL执行增
什么是索引?
MySQL官方对索引的定义为:索引(Index)是帮助MySQL高效检索数据的数据结构。简单的来说,索引就相当于字典的目录,帮助我们更快地找到需要查找的文字。
索引的优缺点
优点:
- 使用索引可以大大加快数据的检索速度(大大减少检索的数据量),这也是创建索引的主要原因
- 如果是唯一索引,可以确保数据表中的每一行记录的唯一性
缺点:
- 索引需要占用额外的存储空间
- 创建和维护索引需要耗费时间。MySQL执行增删改操作时,也需要对索引进行修改,会降低SQL的执行效率
常见的索引结构
按数据结构分,常用的有hash索引、B树索引和B+树索引,InnoDB使用的是B+树索引,下面将介绍这些索引并分析为何使用B+树索引。
hash:底层就是hash表。hash表是键值对(key-value)的集合,通过哈希算法(也叫散列算法)对key进行计算得到value的index,实现在接近 O(1)下快速检索到数据 。
虽然hash索引查找数据的速度很快,但存在两个问题:
-
hash冲突,指的是多个key通过哈希算法得到的index相同。通常的解决方法是链地址法,将hash冲突的数据存放在链表中,比如Java中的HashMap在一定条件下就是使用链地址法来解决hash冲突的。
-
hash索引不支持顺序查询和范围查询,这个是hash索引最大的缺点。假如我们要对表中的数据进行排序或者进行范围查询,那 Hash 索引可就不行了。
InnoDB实际上会使用一种名为
自适应哈希索引(adaptive hash index)
来提高查询效率。具体的,InnoDB会监控对表上索引的查找,如果某些索引被频繁访问,则会在其上建立哈希索引来提高检索效率,所以称之为自适应(adaptive)的。自适应哈希索引通过缓冲池的 B+ 树构造而来,因此建立的速度很快。而且不需要将整个表都建哈希索引,InnoDB 会自动根据访问的频率和模式来为某些页建立哈希索引。
B树和B+树:B树是一种多路平衡查找树,而B+树是B树的一种变体。B 树和 B+树中的 B 是 Balanced
(平衡)的意思。
两者的主要区别:
- B树的所有节点都存放的key(键)和data(数据),而B+树只有叶子节点才存放key+data,非叶子节点只存放key。
- B树的叶子节点都是没有关联的,B+树的叶子节点之间通过指针来连接,构成一个有序链表。
上面的两点也是MySQL使用B+树的原因,有如下优点:
- B+树的树高要比B树低得多(B+树树高为3时就可以存储千万级数据),树高低意味着查询时磁盘IO操作少,效率就更快了。
- B 树的检索的过程相当于对范围内的每个节点的关键字做二分查找,可能还没有到达叶子节点,检索就结束了。而 B+树的检索效率就很稳定了,任何查找都是从根节点到叶子节点的过程(不考虑缓存)。
- 进行范围查询时,B树需要做局部的中序遍历,可能要跨层访问,跨层访问代表着要进行额外的磁盘I/O操作,B+树可以通过叶子节点之间的指针进行范围查询。
B+树中一个节点的大小多少合适?
1页或页的倍数最为合适。因为如果一个节点的大小小于1页,那么读取这个节点的时候其实也会读出1页,造成资源的浪费。所以为了不造成浪费,所以最后把一个节点的大小控制在1页、2页、3页等倍数页大小最为合适。
这里说的“页”是 MySQL 自定义的单位(和操作系统类似),MySQL 的 Innodb 引擎中1页的默认大小是16k,可以使用命令SHOW GLOBAL STATUS LIKE ‘Innodb_page_size’ 查看。
MySQL默认一个节点大小为1页,即16K。
为什么B+树高度为3就能存储千万级数据?
我们知道,在InnoDB中,非叶子节点存的是key+指针,叶子节点存的是key+data(通常也就是整一行数据)。
对于叶子节点:我们假设一行数据大小为1K(对于一般业务绝对足够了),那么一页就是16条数据。
对于非叶子节点:假设key使用bigint为8字节,指针在Mysql中为6字节,一共是14字节,则16K能存放16 * 1024 / 14=1170个
。那么一棵高度为3的B+树能存储的数据为1170 * 1170 * 16 = 21902400 (千万级)
所以,在节点为一页16K时,树高3的B+树就能存储千万级的数据,千万级数据下仅需要3次IO即可找到数据(一般再多就要考虑拆分了)。
索引类型
主键索引
数据表的主键列使用的就是主键索引。
一张表有且只能有一个主键。在InndoDB,当没有显式指定表的主键时,如果有不为null的唯一索引,那么会选择该索引字段作为主键;如果没有,InnoDB会自动创建一个 6Byte 的隐藏自增主键字段。
二级索引(辅助索引)
二级索引叶子节点data域存储的是主键。
二级索引又可以分为:
- 唯一索引(Unique Key):顾名思义,字段的值不能重复,唯一索引值可以为NULL。建立唯一索引的目的大部分时候都是为了该属性列的数据的唯一性,而不是为了查询效率。
- 普通索引(Index):普通索引的唯一作用是为了提高查询效率,允许数据重复和NULL。
- 前缀索引(Prefix):前缀索引只适用于字符串类型的数据。前缀索引是截取文本中具有区分度的前缀创建索引,索引数据更小,同时也保证了查询的效率。
- 全文索引(Full Text):全文索引主要是为了检索大文本数据中的关键字的信息,是目前搜索引擎数据库使用的一种技术。Mysql5.6 之前只有 MYISAM 引擎支持全文索引,5.6 之后 InnoDB 也支持了全文索引。 实际中很少用到。
聚簇索引和非聚簇索引(InnoDB引擎)
聚簇索引
聚簇索引即索引结构和数据一起存放的索引。聚簇索引只在InnoDB中有,主键索引属于聚簇索引,聚簇索引文件即数据文件。
-
聚簇索引的优点
聚簇索引的查询速度非常快,定位到索引的节点,就相当于找到了数据。
-
聚簇索引的缺点
- 依赖于有序的数据 :因为 B+树是多路平衡树,如果索引的数据不是有序的,那么就需要在插入时排序,如果数据是整型还好,否则类似于字符串或 UUID 这种又长又难比较的数据,插入或查找的速度肯定比较慢。
- 更新代价大 : 如果对索引列的数据被修改时,那么对应的索引也将会被修改, 而且况聚集索引的叶子节点还存放着数据,修改代价肯定是较大的, 所以对于主键索引来说,主键一般都是不可被修改的。
非聚簇索引
非聚簇索引即索引结构和数据分开存放的索引。二级索引属于非聚簇索引。
-
非聚簇索引的优点
更新代价比聚簇索引小。非聚簇索引的data域不存放数据本身,只有一个主键,索引节点更小。
-
非聚簇索引的缺点
- 和聚簇索引一样,依赖于有序的数据。
- 可能会二次查询(回表):当找到索引节点后,可能还需要根据主键再到主键索引中查询。
什么是回表查询?
在InnoDB中,对于主键索引,只需要走一遍主键索引的查询就能在叶子节点拿到数据。
而对于二级索引,叶子节点存放的是key+主键值,因此,当查询语句中需要的列在索引中不存在时,需要根据主键值去到主键索引拿取列数据,称为回表。
联合索引
联合索引底层使用的还是B+树,并且还是只有一棵树,只是此时的排序会:首先按照第一个索引排序,在第一个索引相同的情况下,再按第二个索引排序,依次类推。
ICP
ICP,全称Index Condition Pushdown(索引下推)。
下面我们以一个例子来说明
建表语句
CREATE TABLE `test` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`a` int(11) NOT NULL,
`b` int(11) NOT NULL,
`c` varchar(64) NOT NULL,
`d` int(11) NOT NULL,
PRIMARY KEY (`id`),
KEY `idx_a_b_c` (`a`,`b`,`c`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
test表有联合索引idx_a_b_c
(a
,b
,c
)
-
SELECT * FROM test WHERE a = 1 AND b = 2 AND c LIKE 'name%';
在5.6之前不使用ICP的情况下,最用最左匹配原则。只能使用联合索引的
a
,然后根据索引data域的主键回表查询,对b
和c
条件进行过滤。使用ICP之后,除了匹配
a
之外,还额外匹配联合索引的b
和c
,这样一来,使用联合索引就可以尽可能排除符合where
条件的记录,减少回表查询的数据量。 -
SELECT * FROM test WHERE a = 1 AND c LIKE 'name%';
这条语句也会使用ICP,ICP的目的是尽可能的过滤不符合条件的记录,哪怕不符合最左匹配的原则,因为这样能降低执行的成本。
综上,ICP的实质就是通过二级索引尽可能的过滤不符合条件的记录,哪怕不符合最左匹配原则,减少回表,降低执行成本。
扩展-EXPLAIN
explain的字段有:
-
id:标识符。id如果相同,可以认为是一组,从上往下顺序执行;在所有组中,id值越大,优先级越高,越先执行 。
-
select_type
:查询类型。
- SIMPLE(简单SELECT,不使用UNION或子查询等)
- PRIMARY(子查询中最外层查询,查询中若包含任何复杂的子部分,最外层的select被标记为PRIMARY)
- UNION(UNION中的第二个或后面的SELECT语句)
- DEPENDENT UNION(UNION中的第二个或后面的SELECT语句,取决于外面的查询)
- UNION RESULT(UNION的结果,union语句中第二个select开始后面所有select)
- SUBQUERY(子查询中的第一个SELECT,结果不依赖于外部查询)
- DEPENDENT SUBQUERY(子查询中的第一个SELECT,依赖于外部查询)
- DERIVED(派生表的SELECT, FROM子句的子查询)
- UNCACHEABLE SUBQUERY(一个子查询的结果不能被缓存,必须重新评估外链接的第一行)
-
table:显示查询的表名称,有时不是真实的表名字 。
-
type:对表访问方式。常用的类型有:ALL、index、range、 ref、eq_ref、const、system、NULL(从左到右,性能从差到好)
- ALL:全表查询
- index: Full Index Scan,index与ALL区别为index类型只遍历索引树
- range:只检索给定范围的行,使用一个索引来选择行
- ref: 表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值
- eq_ref: 类似ref,区别就在使用的索引是唯一索引,对于每个索引键值,表中只有一条记录匹配,简单来说,就是多表连接中使用primary key或者 unique key作为关联条件
- const、system: 当MySQL对查询某部分进行优化,并转换为一个常量时,使用这些类型访问。如将主键置于where列表中,MySQL就能将该查询转换为一个常量,system是const类型的特例,当查询的表只有一行的情况下,使用system
- NULL: MySQL在优化过程中分解语句,执行时甚至不用访问表或索引,例如从一个索引列里选取最小值可以通过单独索引查找完成。
-
possible_keys:指出MySQL能使用哪个索引在表中找到记录,查询涉及到的字段上若存在索引,则该索引将被列出,但不一定被查询使用(该查询可以利用的索引,如果没有任何索引显示 null)
-
Key:key列显示MySQL实际决定使用的键(索引),必然包含在possible_keys中
-
key_len:表示索引中使用的字节数,可通过该列计算查询中使用的索引的长度(key_len显示的值为索引字段的最大可能长度,并非实际使用长度,即key_len是根据表定义计算而得,不是通过表内检索出的)。不损失精确性的情况下,长度越短越好
-
ref:列与索引的比较,表示上述表的连接匹配条件,即哪些列或常量被用于查找索引列上的值
-
row:查询所需要扫描的行数,是一个估算值
-
Extra:该列包含MySQL解决查询的详细信息
- Using where:使用覆盖索引时会出现,不用读取表中所有信息,仅通过索引就可以获取所需数据。
- Using temporary:表示MySQL需要使用临时表来存储结果集,常见于排序和分组查询,常见 group by ; order by
- Using filesort:当Query中包含 order by 操作,而且无法利用索引完成的排序操作称为“文件排序”
- Using join buffer:该值强调了在获取连接条件时没有使用索引,并且需要连接缓冲区来存储中间结果。如果出现了这个值,那应该注意,根据查询的具体情况可能需要添加索引来改进能
- Impossible where:这个值强调了where语句会导致没有符合条件的行(通过收集统计信息不可能存在结果)
- Select tables optimized away:这个值意味着仅通过使用索引,优化器可能仅从聚合函数结果中返回一行
- No tables used:Query语句中使用from dual 或不含任何from子句
如何做慢SQL优化
首先要搞明白SQL慢的原因是什么?是查询了不需要的列或行?还是数据量太大?或是查询条件没有命中索引?大致步骤如下:
- 首先使用explain分析语句的执行计划,查看索引的使用情况,是不是没有走查询索引,是的话可以考虑加索引解决。
- 分析语句,看是否存在一些导致索引失效的用法(如在where条件中进行运算、隐式转换等),是否加载了不需要的列和行,是的话对语句进行重写。
- 如果对语句的优化已无法进行,考虑表中的数据量是否太大,如果是的话可以进行垂直或者水平拆分。
创建索引的一些建议
- 选择合适的字段创建索引:
- 不为NULL的字段:数据库对于为NULL的字段,需要更多的存储空间,并且在查询时索引统计和值比较都更复杂,优化更复杂。
- 频繁作为条件查询、排序的字段:可以利用索引,加快检索和排序效率。
- 更新频率较低的字段:索引需要维护,频繁更新会降低SQL效率。
- 字段的值应该具有区分度:作为索引的字段应尽量做到重复值较少,如性别字段只有两个值就不适合作为索引。
- 尽可能的考虑建立联合索引而不是单列索引,避免冗余索引:索引是需要占用磁盘空间的,如果一个联合索引能覆盖列a、b,那就不需要单独建一个列a的索引了。
- 字符串类型的索引考虑使用前缀索引:在保证索引区分度的情况下,截取字符串的某段前缀,是空间和时间上的平衡取舍。
更多推荐
所有评论(0)