该博客主要讲解一下MySQL中的索引。
一、B树和B+树
在讲解索引之前,先简要说明一下索引的底层数据结构。MySQL中常用的存储引擎,如InnoDB和MyISAM的索引都是B+树索引,也就是底层数据结构就是B+树。
1、B树
B+树是由B树发展而来的,因此我们先讲一下B树。B树的定义(按照阶数来定义的)如下:
一组关键字建立的B树:
2、B+树
一棵m阶的B+树和m阶的B树的异同点在于:
1、有n棵子树的结点中含有n-1个关键字(与B树相同,MySQL中的B+树索引采用这种); (此处有争议,另一种说法是,B+树具有n棵子树的结点中含有n个关键字)
2、所有的叶子结点中包含了全部关键字的信息,即所有的非叶子节点仅仅起到索引作用,非叶结点中仅含有其子树根结点中最大关键字及指向该子树的指针。(而B树的叶子节点并没有包括全部需要查找的信息)
3、在B+树上,有一个指针指向关键字最小的叶子节点,所有叶子节点链接成一个线性链表。(B树不存在指针将叶子节点连成一个链表)
一组关键字建立的B+树:
二、B树索引(B+树索引)
MySQL中的存储引擎通常实现的都是B+树索引。
下图为B+树索引示例:
B+树索引是用于全键值、键值范围和键前缀查找。其中键前缀查找只适用于最左前缀查找。
假设有如下数据表:
CREAT TABLE(
last_name varchar(50) not null,
first_name varchar(50) not null,
dob date not null,
gender enum('m', 'f') not null,
key(last_name, first_name, dob)
);
B+树索对下面类型的查询有效:
全值匹配:
指和索引中的所有列进行匹配,例如在前面的索引中查找姓名为Cuba Allen、出生于1960-01-01年的人。
匹配最左前缀
在前面的索引中查找姓为Allen的所有人,即只是用了第一列。
匹配列前缀
也可以只匹配某一列的值的开头部分,例如查找所有以A开头的姓的人,这也只是用了第一列。
匹配范围值
例如查找姓在Allen和Barrymore之间的人,这也是只用到了第一列。
精确匹配某一列并范围匹配另一列
前面的索引也可以查询所有姓为Allen,且名字以K开头的人。即第一列全匹配,第二列范围匹配。
只访问索引的查询
支持覆盖索引,即查询只需要访问索引无须访问数据行。这是一种优化技巧。
B+树索引的限制:
由此,可以看出,索隐列的顺序是很重要的,所以在建立索引时,既要考虑选取哪些列,也要考虑这些列的顺序。
MySQL中还有一些其他类型的索引,比如哈希索引、全文索引、空间索引等,这里就不再叙述了。
索引的优点:
1、索引大大减少了服务器需要扫描的数据量。
2、索引可以帮助服务器避免排序和临时表。
3、索引可以将随机I/O变为顺序I/O。
索引的缺点:
1、索引需要占用额外的存储空间,数据量越大占用的额外空间也就越大。
2、进行更新操作时需要花费额外的时间维护索引,造成更新操作效率降低。
三、高性能的索引策略
1、独立的列
独立的列是指索引列不能是表达式的一部分,也不能是函数的参数。
例如:SELECT id FROM actor WHERE id+1=5;
这个例子就不能使用id的索引,可以改成id=4,这就可以使用索引了。
例如另外一种常见错误:SELECT ... WHERE TO_DAYS(CURRENT_DATE)-TO_DAYS(date_col)<=10; 这是不能使用date_col列的索引的。
2、前缀索引和索引选择性
有时候需要索引很长的的字符列,这会让索引变得大且慢。这时就可以采用前缀索引,即只索引列的前面一部分字符,这样可以大大节约索引空间,从而提高索引效率。但是这样会降低索引的选择性。索引的选择性是指,不重复的索引值和数据表的记录总数(#T)的比值,范围是在1/#T到1之间。唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。
在选择索引的时候,既要选择足够长的前缀以保证较高的选择性,同时又不能太长,以便节约空间。
3、多列索引及索引列顺序
一个常见的错误就是,为每个列创建独立的索引,或者按照错误的顺序创建多列索引。在多个列上建立独立的单列索引大部分情况下并不能提高MySQL的查询性能。当出现服务器对多个索引做相交操作时(通常有多个AND条件),通常意味着需要一个包含所有列的多列索引,而不是多个独立的单列索引。
索引顺序的选择通常是一个令人困惑的问题,正确的顺序依赖于使用该索引的查询,并且同时需要考虑如何更好地满足排序和分组的需求。在一个多列B+树索引中,索引列的顺序意味着索引首先按照最左列进行排序,其次是第二列,等等。选择索引列顺序有一个经验法则:将选择性最高的列放到索引最前列(在不需要考虑排序和分组的时候,效果很好)。
4、聚簇索引
聚簇索引并不是一种单独的索引类型,而是一种索引存储结构。具体的实现细节依赖于存储引擎,InnoDB的聚簇索引实际上在同一个结构中B+树索引和数据行,而MyISAM(不支持聚簇索引)是将索引和数据分为两个单独的文件存储的。
当表有聚集索引时,它的数据行实际上存放在索引的叶子页中。因为无法同时把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引。MySQL中不是所有的存储引擎都支持聚簇索引,InnoDB支持,而MyISAM不支持,下面主要讲解InnoDB的聚簇索引。
InnoDB是通过主键聚集数据的,不支持选择其他索引作为聚集索引。
聚簇索引的优点:
聚簇索引的缺点:
InnoDB和MyISAM的数据分布对比
对于如下表格:
CREATE TABLE layout_test(
col1 int NOT NULL,
col2 int NOT NULL,
PRIMARY KEY(col1),
KEY(col2)
);
写下来看一下InnoDB和MyISAM是如何存储的。
MyISAM的数据分布
MyISAM按照数据插入的顺序存储在磁盘上,如下图:
这种数据存储方式,很容易创建索引:
col2列上的索引与主键索引没有什么大的区别:
MyISAM中的主键索引和其它索引在结构上没有什么不同。主键索引就是一个名为PRIMARY的唯一非空索引。MyISAM通过索引查询到行号之后,可以直接通过行号(或者说是物理地址)去读取相应的数据行。
InnoDB的数据分布
因为InnoDB支持聚簇索引,所以,使用的存储方式与MyISAM有很大的不同。其存储方式如下图:
从这里可以看出,InnoDB把主键索引和整张表结合在了一起。在InnoDB中,聚簇索引“就是”表,所以不会像MyISAM那样需要独立的行存储数据。
聚簇索引的每一个叶子节点都包含了主键值、事务ID、用于事务和MVCC的回滚指针以及所有的剩余列(在该例子中就是col2)。如果主键是一个列前缀索引,InnoDB也会包含完整的主键列和剩下的其他列。
InnoDB的二级索引(不是聚簇索引,一张表只能有一个聚簇索引)和聚簇索引很不相同,如下图:
InnoDB的二级索引的叶子节点存储的不是“行指针”,而是主键值,并以此作为指向行的“指针”。一个查找如果使用的是二级索引,则需要先在二级索引中查找到主键值,再根据主键值去聚簇索引中查找相应的数据(需要两次B+树查找)。这样的策略减少了当出现行移动或者数据页分裂时二级索引的维护工作。,但是使用主键值会使得二级索引占用更多的存储空间。
下面给出InnoDB和MyISAM存储表的抽象对比图:
5、覆盖索引
如果一个索引包含(或者说覆盖)所有需要查询的字段的值,我们就称之为“覆盖索引”。覆盖索引能够极大地提高性能。
覆盖索引有许多好处:
覆盖索引必须要存储索引列的值,所以,MySQL只能使用B+树做覆盖索引。
参考:
《高性能MySQL》