索引(在MySQL中也叫做键(key))是存储引擎用于快速找到记录的一种数据结构。索引对于良好的性能非常关键。尤其是当表中的数据量越来越大时,索引对性能的影响愈发重要。在数据量较小且负载较低时,不恰当的索引对性能的影响可能还不明显,但是当数据量逐渐增大时,性能则会急剧下降。
索引有很多种类型,可以为不同的场景提供更好的性能。在MySQL中,索引是在引擎层而不是服务器层实现的。所以,并没有统一的索引标准:不同的存储引擎的索引的工作方式不一样,也不是所有的存储引擎都支持所有类型的索引。即使多个存储引擎支持同一种类型的索引,其底层实现也可能不同。
当人们谈论索引的时候,如果没有特别指明类型,那多半说的就是B树索引,它使用B树数据结构或者及其变种来存储数据(例如,Innodb使用的是B+树)。我们使用术语“B树”,是因为MySQL在create table和其他语句中也是用该关键字。存储引擎以不同的方式使用B树索引,性能也各有不同,各有优劣。
假设有如下数据表:
create table People (
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)
);
对于表中的每一行数据,索引中包含了last_name、first_name和dob列的值,下图显示了该索引是如何组织数据的存储的(该图只展示了索引部分的结构,B+树索引中的叶子结点关联的数据行并未画出):
从上图可见,索引对多个值进行排序的依据是create table语句中定义索引时列的顺序。可以看到最后两个条目,两个人的姓名一模一样,则根据他们的出生日期来排列顺序。
能使用B树索引查询的类型:
B树索引的限制:
看到这里大家应该明白,索引列的顺序是多么的重要。这些限制都和索引的列有关系。在优化性能的时候,可能需要使用相同的列但顺序不同的索引来满足不同类型的查询需求。
哈希索引(hash index)基于哈希表实现,只有精确匹配索引所有列的查询才有效。对于每一行数据,存储引擎都会对所有的索引列计算一个哈希码(hash code),哈希码是一个较小的值,并且不同键值的行计算出来的哈希码也不一样。哈希索引将所有的哈希码存储在索引中,同时在哈希表中保存指向每一个数据行的指针。
哈希索引自身只需要存储对应的哈希值,所以索引的结构十分紧凑,这也让哈希索引查找的速度非常快。
哈希索引的限制:
另外由于目前Innodb存储引擎大量的使用,因此需要针对Innodb存储引擎说明一下,Innodb存储引擎支持B+树索引、全文索引,但是Innodb并不支持哈希索引,Innodb中有一个特殊的功能叫做自适应哈希索引,是指Innodb引擎注意到某些索引值被使用得非常频繁时,它会在内存中基于B树索引智商再创建一个哈希索引,这样就让B树索引也具有哈希索引的一些优点。但是这是一个完全自动的、内部的行为,用户无法控制或者配置,不过如果有必要,可以关闭该功能。
最常见的B树索引,按照顺序存储数据,所以MySQL可以用来做order by和group by操作。因为数据是有序的,所以B树也就会将相关列值都存储在一起。最后,因为索引中存储了实际的列值,所以某些查询只是用索引就能够完成全部查询。根据这些特点,总结下来索引有如下三个优点:
评价一个索引的好坏可以使用三星准则:
索引并不总是会提升性能,只有当索引帮助存储引擎快速查找到记录带来的好处大于其带来的额外工作时,索引才是有效的。对于非常小的表,大部分情况下简单的全表扫描更高效,对于中到大型的表,索引的就非常有效。但是对于特大型的表,建立和使用索引的代价将随之增加。
独立的列是指索引列不能是表达式的一部分,也不能是函数的参数。
例如,下面这个查询无法使用actor_id列的索引:
select actor_id from sakila.actor where actor_id + 1 = 5;
索引的选择性是指,不重复的索引值(也称为基数,cardinality)和数据表的记录总数(#T)的比值,范围从1/#T到1之间。索引的选择性越高,则查询的效率越高,因为选择性高的索引可以让MySQL在查找时过滤掉更多的行。唯一索引的选择性时1,这是最好的索引选择性,性能也是最好的。
有些时候需要索引很长的字符列,这会让索引变得大且慢。这种情况下通常可以索引索引列的开始部分字符,这样可以大大节约索引空间,从而提高索引效率,但是这样会降低索引的选择性。
如下是一个使用前缀索引的例子:
alter table sakila.city add key (city(7));
前缀索引是可以让索引更小、更快的有效办法。但是无法使用前缀索引做order by和group by操作。
在多个列上建立独立的单列索引大部分情况下并不能提高MySQL的查询性能。MySQL5.0和更新版本引入了一种叫做索引合并(index merge)的策略(需要注意的是,索引合并如果使用不当会很容易造成慢查询),一定程序上可以使用表上的多个单列索引来定位指定的行。更早版本的MySQL只能使用其中某一个单列索引,然而很多情况下没有哪一个独立的单列索引是非常有效的。
使用explain语句分析sql时,可以在Extra列中看到是否使用了index merge。索引合并有时候是一种优化的结果,但是实际上更多时候说明了表中索引建的很糟糕:
如果在explain中看到有索引合并,应该好好检查一下查询和表的结构,看是不是已经是最优的。也可以通过参数optimization switch来关闭索引合并功能。也可以使用ignore index提示让优化器忽略掉某些索引。
在如何选择索引的列顺序有一个经验法则:将选择性最高的列放在索引的最前面。这个经验法则在某些时候有用,但通常不如避免随机I/O和排序那么重要。场景不同则选择不同,没有一个放之四海而皆准的法则。但是当不需要考虑排序和分组的情况下,将选择性最高的列放在前面通常是很好的。
聚集索引并不是一种单独的索引类型,而是一种数据存储方式。在Oracle中也叫做索引组织表(index organized table)。和聚集索引相对于的叫非聚集索引,在Oracle中也叫堆组织表(heap organized table)。当表有聚集索引时,他的数据行实际上存放在索引的叶子页。《高性能MySQL》中术语“聚集”,就是表示数据行和相邻的键值紧凑地存储在一起。说的通俗一点,就是数据行在磁盘上是按照索引顺序存放在一起,当然这只是定义,实际中例如Innodb存储引擎,只聚集在同一个磁盘页面中的记录,包含相邻键值的页面可能相距很远。也就是说Innodb中,同一个磁盘页面中的记录是因为在同一个页面中,所以是存储在一起的,但是逻辑相邻的磁盘页面由链表连接,实际上可能物理上相隔很远。因为无法同时把数据行存放在两个不同的地方,所以一个表只能有一个聚集索引。
以下以Innodb存储引擎做说明。到目前为止,Innodb存储引擎并不支持选择哪个索引作为聚集索引,在定义了主键的情况下,在Innodb中将通过主键聚集数据,如果没有定义主键,Innodb会选择一个唯一的非空索引代替。如果没有这样的索引,Innodb会隐式定义一个主键来作为聚集索引。
聚集索引的优点:
聚集索引缺点:
InnoDB和MyISAM的数据分布对比
下面让我们来看看InnoDB和MyISAM是如何存储下面这个表的:
create table t (
id int not null primary key auto_increment,
name varchar(50) not null,
sex enum('m', 'f') not null,
key(name)
);
表中有4条记录:
1, zhangsan, m
3, lisi, m
5, wangwu, f
9, zhaoliu, m
MyISAM的数据分布比较简单,我们先介绍MyISAM的情况。MyISAM中的数据行和索引是分开存储的,也就是说MyISAM的索引都是非聚集索引。非聚集索引的主键索引和普通索引没有本质区别:
也就是说MyISAM的表是可以没有主键的。主键索引和普通索引是两棵独立的索引B+树,通过索引列查找时,先定位到B+树的叶子结点,再通过指针定位到行记录(也就是对应数据行移动时,需要维护索引)。MyISAM存储示意图如下所示:
InnoDB存储引擎支持聚集索引,所以使用非常不同的方式存储同样的数据。InnoDB存储数据的示意图如下所示:
如上图所示,InnoDB的主键索引与行记录是存储在一起的,并没有独立区域存储数据行。主键索引的叶子结点存储的是主键和对应的数据行而不是指针,因此InnoDB的PK查询是非常快的。另外需要注意的是InnoDB默认是在主键上使用聚集索引,如果没有定义主键会使用唯一的非空索引代替,如果没有这样的索引,会隐式定义一个主键来做聚集索引。也就是说InnoDB的主键是一定会存在的。
还有一个值得注意的问题是,顺序主键在高并发的情况下可能会造成明显的锁争用。
所谓的覆盖索引是指一个索引中包含了所有需要查询的字段的值。
覆盖索引是非常有用的工具,能够极大的提高性能。覆盖索引带来的好处如下:
但是需要注意的是,并不是所有的存储引擎都支持覆盖索引。
索引是一个复杂的问题,理解索引最好的办法就是结合示例,多做练习。高效的索引策略当然不止上面总结的这些示例,还比如有索引引发的锁、冗余和重复索引、使用索引扫描来做排序(B树索引是已经排好序的)等等策略。
参考:
《高性能MySQL》
1分钟了解MyISAM与InnoDB的索引差异