mysql索引机制及分类
- 1、索引简介
- 2、索引机制
- 2.1 红黑树(平衡二叉树)
- 2.2 哈希
- 2.3 平衡多路查找树(B-Tree)
- 2.4 B+tree
- 2.5 不同数据结构的索引对比
- 3、索引分类
- 3.2 存储分类-聚簇、二级(辅助)索引
- 3.2.1 聚簇(集)索引
- 3.2.1.1 定义
- 3.2.1.2 聚集索引的适用情形
- 3.2.1.3 聚集索引的好处
- 3.2.1.4 聚集索引的创建
- 3.2.3 非聚簇(集)索引、二级(辅助)索引
- 3.2.3.1 定义
- 3.2.3.2 聚集索引与非聚集索引的适用情况
- 3.2.2 回表查询 - 二次查询问题
- 3.2.3 索引覆盖
- 3.2.3.1 定义
- 3.2.3.2 覆盖索引的优点
- 3.2.3.3 覆盖索引的限制
- 3.2.3.4 覆盖索引的查询优化
- 3.2.4 注意事项
- 3.3 字段特性分类-主键、普通、前缀索引
- 3.4 字段个数分类-单列、联合(复合\组合)索引
- 3.4.1 单列索引**
- 3.4.2 联合索引(复合索引、组合索引)**
- 3.4.3 联合索引的最左匹配原则
1、索引简介
索引是关系型数据库中给数据库表中一列或多列的值排序后的存储结构,SQL的主流索引结构有B+树以及Hash结构,聚集索引以及非聚集索引用的是B+树索引
SQL Sever 索引类型有:唯一索引,主键索引,聚集索引,非聚集索引。
MySQL 索引类型有:唯一索引,主键(聚集)索引,非聚集索引,全文索引。
建立索引后,索引列会在磁盘中单独的存储起来,索引的本质是一种数据结构,通过这种空间换时间的方式可以快速找到需要查询的数据。
2、索引机制
MySQL索引按数据结构分类可分为:B+tree索引、Hash索引、Full-text索引。
注:InnoDB实际上也支持Hash索引,但是InnoDB中Hash索引的创建由存储引擎自动优化创建,不能人为干预是否创建Hash索引。
B+tree 是MySQL中被存储引擎采用最多的索引类型。B+tree 中的 B 代表平衡(balance),而不是二叉(binary),因为 B+tree 是从最早的平衡二叉树演化而来的。下面展示B+tree数据结构与其他数据结构的对比。
2.1 红黑树(平衡二叉树)
特点:
(1)红黑树是一种弱平衡二叉查找树。节点子节点个数为两个,意味着其搜索复杂度为 O(logN)。
(2)通过对任何一条从根到叶子的路径上各个节点着色的方式的限制,红黑树确保没有一条路径会比其他路径长出两倍。
(3)二叉树具有以下性质:左子树的键值小于根的键值,右子树的键值大于根的键值。
2.2 哈希
特点:键值Key通过Hash映射找到桶bucket。桶指的是一个能存储一条或多条记录的存储单位。一个桶的结构包含了一个内存指针数组,桶中的每行数据都会指向下一行,形成链表结构,当遇到Hash冲突时,会在桶中进行键值的查找。
Hash冲突:
如果桶的空间小于输入的空间,不同的输入可能会映射到同一个桶中,这时就会产生Hash冲突,如果Hash冲突的量很大,就会影响读取性能。
2.3 平衡多路查找树(B-Tree)
B-Tree是为磁盘等外存储设备设计的一种平衡查找树。
系统从磁盘读取数据到内存时是以磁盘块(block)为基本单位的,位于同一个磁盘块中的数据会被一次性读取出来,而不是需要什么取什么。InnoDB存储引擎中有页(Page)的概念,页是其磁盘管理的最小单位。
InnoDB在把磁盘数据读入到磁盘时会以页为基本单位,在查询数据时如果一个页中的每条数据都能有助于定位数据记录的位置,这将会减少磁盘I/O次数,提高查询效率。B-Tree结构的数据可以让系统高效的找到数据所在的磁盘块。
为了描述B-Tree,首先定义一条记录为一个二元组[key, data] ,key为记录的键值,对应表中的主键值,data为一行记录中除主键外的数据。对于不同的记录,key值互不相同。
一棵m阶的B-Tree有如下特性:
- 每个节点最多有m个孩子。
- 除了根节点和叶子节点外,其它每个节点至少有Ceil(m/2)个孩子。
- 若根节点不是叶子节点,则至少有2个孩子
- 所有叶子节点都在同一层,且不包含其它关键字信息
- 每个非终端节点包含n个关键字信息(P0,P1,…Pn, k1,…kn)
- 关键字的个数n满足:ceil(m/2)-1 <= n <= m-1
- ki(i=1,…n)为关键字,且关键字升序排序。
- Pi(i=1,…n)为指向子树根节点的指针。P(i-1)指向的子树的所有节点关键字均小于ki,但都大于k(i-1)
B-Tree中的每个节点根据实际情况可以包含大量的关键字信息和分支,如下图所示为一个3阶的B-Tree:
每个节点占用一个盘块的磁盘空间,一个节点上有两个升序排序的关键字和三个指向子树根节点的指针,指针存储的是子节点所在磁盘块的地址。两个关键词划分成的三个范围域对应三个指针指向的子树的数据的范围域。
B-Tree相对于AVLTree缩减了节点个数,使每次磁盘I/O取到内存的数据都发挥了作用,从而提高了查询效率。
2.4 B+tree
详情可查看: MYSQL 索引机制-B+TREE.
B+Tree是在B-Tree基础上的一种优化,使其更适合实现外存储索引结构,InnoDB存储引擎就是用B+Tree实现其索引结构。
从上一节中的B-Tree结构图中可以看到每个节点中不仅包含数据的key值,还有data值。而每一个页的存储空间是有限的,如果data数据较大时将会导致每个节点(即一个页)能存储的key的数量很小,当存储的数据量很大时同样会导致B-Tree的深度较大,增大查询时的磁盘I/O次数,进而影响查询效率。在B+Tree中,所有数据记录节点都是按照键值大小顺序存放在同一层的叶子节点上,而非叶子节点上只存储key值信息,这样可以大大加大每个节点存储的key值数量,降低B+Tree的高度。
B+Tree相对于B-Tree有几点不同:
- 非叶子节点只存储键值信息。
- 所有叶子节点之间都有一个链指针。
- 数据记录都存放在叶子节点中。
将上一节中的B-Tree优化,由于B+Tree的非叶子节点只存储键值信息,假设每个磁盘块能存储4个键值及指针信息,则变成B+Tree后其结构如下图所示:
B+Tree有如下特性:
(1)有K个孩子的节点就有K个关键字。也就是孩子数量=关键字数。
(2)非叶子节点的关键字也会同时存在在子节点中,并且是在子节点中所有关键字的最大或最小。
(3)非叶子节点仅用于索引,不保存数据记录,跟记录有关的信息都放在叶子节点中。
(4)所有关键字都在叶子节点出现,叶子节点构成一个有序链表,而且叶子节点本身按照关键字的大小从小到大顺序链接。
2.5 不同数据结构的索引对比
3、索引分类
MySQL索引都有哪些分类:
- 按数据结构分类可分为:B+tree索引、Hash索引、Full-text索引。
- 按物理存储分类可分为:聚簇索引、二级索引(辅助索引)。
- 按字段特性分类可分为:主键索引、普通索引、前缀索引。
- 按字段个数分类可分为:单列索引、联合索引(复合索引、组合索引)。
3.2 存储分类-聚簇、二级(辅助)索引
按物理存储分类,MySQL索引按叶子节点存储的是否为完整表数据分为:聚簇索引、二级索引(辅助索引)。全表数据存储在聚簇索引中,聚簇索引以外的其他索引叫做二级索引,也叫辅助索引。
3.2.1 聚簇(集)索引
3.2.1.1 定义
MySQL底层使用B+树来存储索引,数据均存在叶子节点上。对于InnoDB而言,主键索引和行记录是存储在一起的,因此叫做聚集索引(clustered index)。除了聚集索引,其他所有都叫做非聚集索引(secondary index),包括普通索引、唯一索引等。
聚集索引中键值的逻辑顺序决定了表中相应行的物理存储位置,因此一个表中只能有一个聚集索引。索引的逻辑顺序与相应行的物理位置一致。
简单的说,聚集索引就是按照每张表的主键构造一颗B+树,同时叶子节点中存放的即为整张表的记录数据。 聚集索引的叶子节点称为数据页,聚集索引的这个特性决定了索引组织表中的数据也是索引的一部分。
聚簇索引的每个叶子节点存储了一行完整的表数据,叶子节点间按id列递增连接,可以方便地进行顺序检索。
以MyISAM为存储引擎的表不存在聚簇索引。
在InnoDB中,要求必须有聚簇索引,且只存在一个聚集索引:
- 若表存在主键,默认主键索引就是聚集索引;
- 若表不存在主键,则会把第一个非空的唯一索引作为聚集索引;
- 否则,会隐式定义一个rowid作为聚集索引。
3.2.1.2 聚集索引的适用情形
- 经常对某些列进行范围搜索:
例如查询一段日期范围。当找到包含第一个数据的行之后,便可以确保包含后续索引值的行在物理位置上相邻。 - 经常对查出的数据按照某一列进行排序,也可以再该列上建立聚集索引,避免在每次查询数据是都进行排序,从而节约时间成本。
- 当索引值唯一时,使用聚集索引查找特定的行也很有效率,例如建立主键。
3.2.1.3 聚集索引的好处
索引的叶子节点就是对应的数据节点(MySQL的MyISAM除外,此存储引擎的聚集索引和非聚集索引只多了个唯一约束,其他没什么区别),可以直接获取到对应的全部列的数据,而非聚集索引在索引没有覆盖到对应的列的时候需要进行二次查询,后面会详细讲。因此在查询方面,聚集索引的速度往往会更占优势。
3.2.1.4 聚集索引的创建
- 创建表的时候指定主键:
SQL Sever默认主键为聚集索引,也可以指定为非聚集索引,而MySQL里主键就是聚集索引:
(1). SQL Sever默认主键为聚集索引,也可以指定为非聚集索引,但是varbinary类型的字段是不可以建立索引的。
(2). MySQL里主键就是聚集索引,MySQL的InnoDB存储引擎按照主键进行聚集,当没有设置主键时,InnoDB会以唯一的非空索引来代替,如果既没有主键,也没有唯一的非空索引,InnoDB则会生成一个隐藏的主键然后在上面进行聚集。
如下示例:
create table t1(
id int primary key,
name nvarchar(255)
)
- 创建表后添加聚集索引:
sql Server:
create clustered index clustered_index on table_name(colum_name)
mysql:
alter table table_name add primary key(colum_name)
值得注意的是,最好还是在创建表的时候添加聚集索引,由于聚集索引的物理顺序上的特殊性,因此如果再在上面创建索引的时候会根据索引列的排序移动全部数据行上面的顺序,会非常地耗费时间以及性能。
3.2.3 非聚簇(集)索引、二级(辅助)索引
3.2.3.1 定义
该索引逻辑顺序与磁盘上行的物理存储顺序不同,一个表中可以拥有多个非聚集索引。
按照定义,除了聚集索引以外的索引都是非聚集索引,只是人们想细分一下非聚集索引,分成普通索引,唯一索引,全文索引。
非聚集索引的叶子节点=键值+书签(Innodb存储引擎的书签就是相应行数据的主键索引值。)。非聚集索引其实叶子节点除了会存储索引覆盖列的数据,也会存放聚集索引所覆盖的列数据。
非聚集索引的叶子节点并不存储一行完整的表数据,而是存储了聚簇索引所在列的值。
3.2.3.2 聚集索引与非聚集索引的适用情况
3.2.2 回表查询 - 二次查询问题
我们以下图为例,假设现在有一个表,存在id、name、age三个字段,其中id为主键,因此id为聚集索引,name建立索引为非聚集索引。关于id和name的索引,有如下的B+树,可以看到,聚集索引的叶子节点存储的是主键和行记录,非聚集索引的叶子节点存储的是主键。
从上面的索引存储结构来看,我们可以看到,在主键索引树上,通过主键就可以一次性查出我们所需要的数据,速度很快。这很直观,因为主键就和行记录存储在一起,定位到了主键就定位到了所要找的包含所有字段的记录。
但是对于非聚集索引,如上面的右图,我们可以看到,需要先根据name所在的索引树找到对应主键,然后通过主键索引树查询到所要的记录,这个过程叫做回表查询。
回表查询 需要额外的 B+tree 搜索过程,必然增大查询耗时。
3.2.3 索引覆盖
如何解决非聚集索引的二次查询问题?
需要注意的是,通过二级索引查询时,回表不是必须的过程,当SELECT的所有字段在单个二级索引中都能够找到时,就不需要回表,MySQL称此时的二级索引为覆盖索引或触发了索引覆盖。
3.2.3.1 定义
- 索引是高效找到行的一个方法,当能通过检索索引就可以读取想要的数据,那就不需要再到数据表中读取行了。如果一个索引包含了(或覆盖了)满足查询语句中字段与条件的数据就叫 做覆盖索引。
- select的数据列只用从索引中就能够取得,不必从数据表中读取,换句话说查询列要被所使用的索引覆盖。
- 覆盖索引是非聚集组合索引的一种形式,它包括在查询里的Select、Join和Where子句用到的所有列(即建立索引的字段正好是覆盖查询语句[select子句]与查询条件[Where子句]中所涉及的字段,也即,索引包含了查询正在查找的所有数据)。
注意:
- 不是所有类型的索引都可以成为覆盖索引。覆盖索引必须要存储索引的列,而哈希索引、空间索引>和全文索引等都不存储索引列的值,所以MySQL只能使用B+Tree索引做覆盖索引
- 以用Explain命令查看SQL语句的执行计划,执行计划的Extra字段中若出现Using index,表示查询触发了索引覆盖。
3.2.3.2 覆盖索引的优点
覆盖索引是一种非常强大的工具,能大大提高查询性能,只需要读取索引而不需要读取数据,有以下优点:
1、索引项通常比记录要小,所以MySQL访问更少的数据。
2、索引都按值的大小存储,相对于随机访问记录,需要更少的I/O。
3、数据引擎能更好的缓存索引,比如MyISAM只缓存索引。
4、覆盖索引对InnoDB尤其有用,因为InnoDB使用聚集索引组织数据,如果二级索引包含查询所需的数据,就不再需要在聚集索引中查找了。
3.2.3.3 覆盖索引的限制
1、覆盖索引也并不适用于任意的索引类型,索引必须存储列的值。
2、Hash和full-text索引不存储值,因此MySQL只能使用BTree。
3、不同的存储引擎实现覆盖索引都是不同的,并不是所有的存储引擎都支持覆盖索引。
4、如果要使用覆盖索引,一定要注意SELECT列表值取出需要的列,不可以SELECT * ,因为如果将所有字段一起做索引会导致索引文件过大,查询性能下降。
3.2.3.4 覆盖索引的查询优化
参考: 覆盖索引的查询优化.
3.2.4 注意事项
- 1、在SQL Server里面会对查询自动优化,选择适合的索引,因此如果在数据量不大的情况下,SQL Server很有可能不会使用非聚集索引进行查询,而是使用聚集索引进行查询,即便需要扫描整个聚集索引,效率也比使用非聚集索引效率要高。
- 2、在MySQL里面就算表里数据量少且查询了非键列,也不会使用聚集索引去全索引扫描,但如果强制使用聚集索引去查询,性能反而比非聚集索引查询要差,这就是两种SQL的不同之处。
3.3 字段特性分类-主键、普通、前缀索引
按字段特性分类,MySQL索引按字段特性分类可分为:主键索引、普通索引、前缀索引。
- 主键索引
建立在主键上的索引被称为主键索引,一张数据表只能有一个主键索引,索引列值不允许有空值,通常在创建表时一起创建。 - 唯一索引
建立在UNIQUE字段上的索引被称为唯一索引,一张表可以有多个唯一索引,索引列值允许为空,列值中出现多个空值不会发生重复冲突。 - 普通索引
建立在普通字段上的索引被称为普通索引。 - 前缀索引
前缀索引是指对字符类型字段的前几个字符或对二进制类型字段的前几个bytes建立的索引,而不是在整个字段上建索引。前缀索引可以建立在类型为char、varchar、binary、varbinary的列上,可以大大减少索引占用的存储空间,也能提升索引的查询效率。
3.4 字段个数分类-单列、联合(复合\组合)索引
按索引字段个数分类,MySQL索引按字段个数分类可分为:单列索引、联合索引(复合索引、组合索引)。
3.4.1 单列索引**
建立在单个列上的索引被称为单列索引。
3.4.2 联合索引(复合索引、组合索引)**
建立在多个列上的索引被称为联合索引,又叫复合索引、组合索引。
3.4.3 联合索引的最左匹配原则
在联合索引中,从最左边的字段开始匹配,若条件中字段在联合索引中符合从左到右的顺序则走索引,否则不走。
为了方便后续说明,首先建立一个如下的表(MySQL5.7),表中共有5个字段(a、b、c、d、e),其中a为主键,有一个由b,c,d组成的联合索引,存储引擎为InnoDB,插入三条测试数据。
CREATE TABLE `test` (
`a` int NOT NULL AUTO_INCREMENT,
`b` int DEFAULT NULL,
`c` int DEFAULT NULL,
`d` int DEFAULT NULL,
`e` int DEFAULT NULL,
PRIMARY KEY(`a`),
KEY `idx_abc` (`b`,`c`,`d`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
INSERT INTO test(`a`, `b`, `c`, `d`, `e`) VALUES (1, 2, 3, 4, 5);
INSERT INTO test(`a`, `b`, `c`, `d`, `e`) VALUES (2, 2, 3, 4, 5);
INSERT INTO test(`a`, `b`, `c`, `d`, `e`) VALUES (3, 2, 3, 4, 5);
如 index(b,c,d) 联合索引,则相当于创建了 b单列索引,(b,c)联合索引,和(b,c,d)联合索引。
我们可以执行下面的几条语句验证一下这个原则。
EXPLAIN SELECT * FROM test WHERE b = 1;
结果如下:
EXPLAIN SELECT * FROM test WHERE b = 1 and c = 2;
结果如下:
EXPLAIN SELECT * FROM test WHERE b = 1 and c = 2 and d = 3;
结果如下:
这时候,我们如果执行下面这个SQL语句,你觉得会走索引吗?
SELECT b, c, d FROM test WHERE d = 2;
上条不符合最左原则的查询,它也如图预期一样,走了全表扫描。