索引是一种数据结构,主要功能是提高数据库的查询效率。
目前最常用的索引数据结构就是B+树,我们先从介绍它开始。
B+树是一种多叉、平衡的排序查找树状结构,多用于外部查找。(平衡二叉树、红黑树多用于数据集不大的内部查找。)其节点分为三类:根节点、内部节点和叶子节点。
定义:
(1)每个节点最多存放
m - 1
个数值;(2)每个内部节点的出度为
(m + 1)/2 <= k <= m
;(3)所有叶子节点都处于同一层;
(4)所有叶子节点都是按键值的大小顺序存放在同一层的叶节点中,各叶节点指针进行连接。
如图所示,在B+Tree的每个叶子结点增加一个指向相邻叶子结点的指针,就形成了带有顺序访问指针的B+Tree,做这个优化的目的是为了提高区间访问的性能。查询key为从15到55的所有数据记录,当找到15后,只需顺着结点和指针顺序遍历就可以一次性访问到所有数据结点。
一般来说,索引本身也很大,不可能全部存储在内存中,因此索引往往以索引文件的形式存储的磁盘上。这样的话,索引查找过程中就要产生磁盘I/O消耗,相对于内存存取,I/O存取的消耗要高几个数量级,所以评价一个数据结构作为索引的优劣最重要的指标就是在查找过程中磁盘I/O操作次数的渐进复杂度。换句话说,索引的结构组织要尽量减少查找过程中磁盘I/O的次数。
索引本身也很大,往往以索引文件的形式存储的磁盘上,所以在索引查找过程中应当尽量减少磁盘I/O的次数。
页(Page)是计算机管理存储器的逻辑块,硬件及操作系统往往将主存和磁盘存储区分割为连续的大小相等的块,每个存储块称为一页(在许多操作系统中,页得大小通常为4k),主存和磁盘以页为单位交换数据。当程序要读取的数据不在主存中时,会触发一个缺页异常,此时系统会向磁盘发出读盘信号,磁盘会找到数据的起始位置并向后连续读取一页或几页载入内存中,然后异常返回,程序继续运行。
为了减少磁盘I/O,在实际实现B+Tree还需要使用如下技巧:
每次新建结点时,直接申请一个页面的空间,这样可以保证一个结点的大小等于一个页面,加之计算机存储分配都是按页对齐的,就实现了一个node只需一次I/O。
根节点常驻内存,B+Tree中一次检索最多需要 h - 1
次I/O,渐进复杂度为O(h)=O(LogdN)。
一般实际应用中,出度d是非常大的数字,通常超过100,因此h非常小。实际上可以想象,内部节点4k,只保存关键字可以保存多少个。而在一个页内进行顺序查找的效率其实很高,所以最后利用B+树的查找效率非常高。
聚集索引和非聚集索引都是基于B+树的,其不同点如下:
(1)叶子节点即存储了真实数据行的页,不再需要单独的数据页。
(2)一张表上只能创建一个聚集索引,因为真实的物理顺序只有一个。
(3)如果一张表没有聚集索引,则这样的表称之为“堆集”,其中的数据行没有特定的顺序,所以新曾的数据行都添加到表的末尾。
(1)叶结点的data域存放的是数据记录的地址。
(2)数据表的物理顺序与索引顺序无关。
(3)每张表可以存在多个非聚簇索引。
定义:
主键索引:B+树的关键字是主键的索引;
辅助索引:B+树的关键字不是主键的索引;
MyISAM的主键索引是非聚簇索引。所以MyISAM索引文件和数据文件是分离的。
InnoDB的辅助索引的叶子节点的data域保存的是主键值,所以辅助索引搜索需要检索两遍索引:首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录。
哈希索引就是采用一定的哈希算法,把键值换算成新的哈希值,检索时不需要类似B+树那样从根节点到叶子节点逐级查找,只需一次哈希算法即可立刻定位到相应的位置,速度非常快。
B+树索引和哈希索引的区别:
InnoDB中有一个优化策略,称之为自适应哈希(AHI:Adaptive Hash Index)。首先,如果数据项都在内存当中,而且链表并不长,则从一个很短的链表中查找一个数据项的耗时是非常小的,甚至可以忽略不计。因此,我们常常说哈希是一种非常快的查找方法,时间复杂度是O(1)。而B+树的查找次数或者说磁盘IO次数取决于B+树的高度。在生产环境中,B+树的高度往往在3~4层。
(1)AHI只能用来搜索等值的查询,比如 WHERE a=xxx AND b=yyy;
(2)以相同的模式连续访问了100次,则InnoDB自动建立一条AHI,无需DBA干预;
全文索引,只有在MyISAM引擎上才能使用,只能在CHAR,VARCHAR,TEXT类型字段上使用全文索引。
对于B+树索引,通过索引字段的前缀(xxx%)进行查找是效率很高的,但是在实际应用中,我们往往需要查找的是一个文本中是否包含关键字(%xxx%),比如博文、存在数据库的一本书等。这种查找就不是B+树所擅长的了。全文检索(Full Text Index)就是将存在数据库中整本书或整篇文章中的任意信息查找出来的技术,而全文检索一般是通过倒排索引(inverted index)实现的。
InnoDB支持空间索引,通过R树来实现,使得空间搜索变得高效。 InnoDB空间索引也支持MyISAM引擎现有的空间索引的语法,此外,InnoDB空间索引支持完整的事务特性以及隔离级别。
目前,InnoDB空间索引只支持两个维度的数据,MySQL开发团队表示有计划支持多维。此外,开发团队正在做更多关于性能方面的工作,以使其更加高效。
主要原因:
联合索引又叫多列索引、复合索引。
如果是单列,就按这列数据进行排序;
如果是多列,就按多列数据排序,例如有(1,1) (2,2) (2,1) (1,2)
那在索引中的叶子节点的数据顺序就是(1,1)(1,2)(2,1)(2,2)
这也是为什么查询复合索引的前缀是可以用到索引的原因。
最左前缀:建立联合索引 (a, b, c) ,就相当于同时建立了索引 (a)、(a, b)、(a, b, c)。
建立联合索引 (a, c, b) ,就相当于同时建立了索引 (a)、(a, c)、(a, c, b)。
注意,建立联合索引的顺序很重要。
还是以联合索引(a, b, c)为例,看看联合索引的影响:
(1)索引在三列上都生效了,索引的效率最高:
where a=3 and b=45 and c=5 --全部发挥作用
where b=45 and a=3 and c=5 --mysql没有那么笨,跟select语句写的顺序无关
(2)只用到了最左第一列a的索引,产生的结果集依然很大,需要扫描的数据集还是很大:
where a=3 and c=5 --只用到了最左边一列,因为不存在(a, c)的索引
(3)一列都没有用到的SELECT语句:
where b=3 and c=4 --一列都没有用到,等于全表扫描,先扫面b列,再扫描结果集中的c列
我们还可以总结出这样的诀窍: 离散度大的列放到联合索引的前面 。
优点:在mysql中建立前缀索引的意义在于相对于整列建立索引,前缀索引仅仅是选择该列的部分字符作为索引,减少索引的字符可以节约索引空间,从而提高索引效率。
缺点:降低索引的离散性。
alter table music add index music_index(name(2)); --使用name的前两个字符做索引的key
如果索引的叶子节点包含了要查询的数据,那么就不用回表查询了,也就是说这种索引包含(亦称覆盖)所有需要查询的字段的值,我们称这种索引为覆盖索引。
还有一种解释方式。数据库的索引保存在索引文件中,如果一次查询只需要查询索引文件,那么这个索引就覆盖了这条查询语句。
举个例子:
create table test_index(
id int primary key auto_increment,
name char(10) not null default ``,
email char(10) not null default ``,
index c (`id`,`name`) -- 注意这里,建立了(id, name)的联合索引
) engine = Innodb charset utf8;
insert into test_index (`name`,`email`) values ('datou','[email protected]'); ,('datou','[email protected]');
(1)被索引覆盖的查询:
select id ,name from test_index ;
(2)没有被覆盖的查询:
select id ,name ,email from test_index ;
(1)查询频繁的SQL语句中,给where 从句,group by从句,on从句中出现的列添加索引;
(2)区分度高:建索引的列要有区分度或者说离散性。
(3)长度小:减少I/O次数和辅助索引的大小。
这是最基本的索引,它没有任何限制。它有以下几种创建方式:
CREATE INDEX indexName ON mytable(username(length));
如果是CHAR,VARCHAR类型,length可以小于字段实际长度;如果是BLOB和TEXT类型,必须指定 length。
ALTER table tableName ADD INDEX indexName(columnName)
CREATE TABLE mytable(
ID INT NOT NULL,
username VARCHAR(16) NOT NULL,
INDEX [indexName] (username(length))
);
DROP INDEX [indexName] ON mytable;
它与前面的普通索引类似,不同的就是:索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。它有以下几种创建方式:
CREATE UNIQUE INDEX indexName ON mytable(username(length))
ALTER table mytable ADD UNIQUE [indexName] (username(length))
CREATE TABLE mytable(
ID INT NOT NULL,
username VARCHAR(16) NOT NULL,
UNIQUE [indexName] (username(length))
);
有四种方式来添加数据表的索引:
ALTER TABLE tbl_name ADD PRIMARY KEY (column_list):
该语句添加一个主键,这意味着索引值必须是唯一的,且不能为NULL。
ALTER TABLE tbl_name ADD UNIQUE index_name (column_list): 这条语句创建索引的值必须是唯一的(除了NULL外,NULL可能会出现多次)。
ALTER TABLE tbl_name ADD INDEX index_name (column_list): 添加普通索引,索引值可出现多次。
ALTER TABLE tbl_name ADD FULLTEXT index_name (column_list):该语句指定了索引为 FULLTEXT ,用于全文索引。
以下实例为在表中添加索引。
mysql> ALTER TABLE testalter_tbl ADD INDEX (c);
你还可以在 ALTER 命令中使用 DROP 子句来删除索引。尝试以下实例删除索引:
mysql> ALTER TABLE testalter_tbl DROP INDEX c;
主键只能作用于一个列上,添加主键索引时,你需要确保该主键默认不为空(NOT NULL)。实例如下:
mysql> ALTER TABLE testalter_tbl MODIFY i INT NOT NULL;
mysql> ALTER TABLE testalter_tbl ADD PRIMARY KEY (i);
你也可以使用 ALTER 命令删除主键:
mysql> ALTER TABLE testalter_tbl DROP PRIMARY KEY;
删除主键时只需指定PRIMARY KEY,但在删除索引时,你必须知道索引名。
你可以使用 SHOW INDEX 命令来列出表中的相关的索引信息。可以通过添加 \G 来格式化输出信息。
尝试以下实例:
mysql> SHOW INDEX FROM table_name; \G
........