mysql索引之一:索引基础(B-Tree索引、哈希索引、聚簇索引、全文(Full-text)索引区别)(惟一索引、最左前缀索引、前缀索引、多列索引)

没有索引时mysql是如何查询到数据的

  索引对查询的速度有着相当重要的影响,理解索引也是进行数据库性能调优的起点。考虑以下状况,假设数据库中一个表有10^6条记录,DBMS的页面大小为4K,并存储100条记录。若是没有索引,查询将对整个表进行扫描,最坏的状况下,若是全部数据页都不在内存,须要读取10^4个页面,若是这10^4个页面在磁盘上随机分布,须要进行10^4次I/O,假设磁盘每次I/O时间为10ms(忽略数据传输时间),则总共须要100s(但实际上要好不少不少)。若是对之创建B-Tree索引,则只须要进行log100(10^6)=3次页面读取,最坏状况下耗时30ms。这就是索引带来的效果,不少时候,当你的应用程序进行SQL查询速度很慢时,应该想一想是否能够建索引。html

进入正题:mysql

1、什么是索引?   

  索引(在MYSQL中也叫作键<key>),是存储引擎用于快速找到记录的一种数据结构。索引用来快速地寻找那些具备特定值的记录,全部MySQL索引都以B-树的形式保存。若是没有索引,执行查询时MySQL必须从第一个记录开始扫描整个表的全部记录,直至找到符合要求的记录。表里面的记录数量越多,这个操做的代价就越高。若是做为搜索条件的列上已经建立了索引,MySQL无需扫描任何记录便可迅速获得目标记录所在的位置。若是表有1000个记录,经过索引查找记录至少要比顺序扫描记录快100倍。 算法

  索引对查询的速度有着相当重要的影响,理解索引也是进行数据库性能调优的起点。考虑以下状况,假设数据库中一个表有10^6条记录,DBMS的页面大小为4K,并存储100条记录。若是没有索引,查询将对整个表进行扫描,最坏的状况下,若是全部数据页都不在内存,须要读取10^4个页面,若是这10^4个页面在磁盘上随机分布,须要进行10^4次I/O,假设磁盘每次I/O时间为10ms(忽略数据传输时间),则总共须要100s(但实际上要好不少不少)。若是对之创建B-Tree索引,则只须要进行log100(10^6)=3次页面读取,最坏状况下耗时30ms。这就是索引带来的效果,不少时候,当你的应用程序进行SQL查询速度很慢时,应该想一想是否能够建索引。sql

索引优化应该是对查询性能优化最有效的手段了,索引可以轻易将查询性能提升几个数量级,”最优“的索引有时比一个”好的“索引性能要好两个数量级。建立一个真正”最优“的索引常常要重写查询。数据库

2、索引的类型

 

从数据结构角度缓存

一、B+树索引(O(log(n))):关于B+树索引,能够参考 MySQL索引背后的数据结构及算法原理性能优化

二、hash索引:
a 仅仅能知足"=","IN"和"<=>"查询,不能使用范围查询
b 其检索效率很是高,索引的检索能够一次定位,不像B-Tree 索引须要从根节点到枝节点,最后才能访问到页节点这样屡次的IO访问,因此 Hash 索引的查询效率要远高于 B-Tree 索引
c 只有Memory存储引擎显示支持hash索引服务器

三、FULLTEXT索引(如今MyISAM和InnoDB引擎都支持了)数据结构

四、R-Tree索引(用于对GIS数据类型建立SPATIAL索引)并发

从物理存储角度

一、汇集索引(clustered index)

二、非汇集索引(non-clustered index)

从逻辑角度

一、主键索引:主键索引是一种特殊的惟一索引,不容许有空值

二、普通索引或者单列索引

三、多列索引(复合索引):复合索引指多个字段上建立的索引,只有在查询条件中使用了建立索引时的第一个字段,索引才会被使用。使用复合索引时遵循最左前缀集合

四、惟一索引或者非惟一索引

五、空间索引:空间索引是对空间数据类型的字段创建的索引,MYSQL中的空间数据类型有4种,分别是GEOMETRY、POINT、LINESTRING、POLYGON。MYSQL使用SPATIAL关键字进行扩展,使得可以用于建立正规索引类型的语法建立空间索引。建立空间索引的列,必须将其声明为NOT NULL,空间索引只能在存储引擎为MYISAM的表中建立

 

3、索引的类型的详解

3.1MySQL提供多种索引类型(按照逻辑角度分)供选择: 

3.1.1普通索引
这是最基本的索引类型,并且它没有惟一性之类的限制。普通索引能够经过如下几种方式建立: 
建立索引,例如CREATE INDEX <索引的名字> ON tablename (列的列表); 
修改表,例如ALTER TABLE tablename ADD INDEX [索引的名字] (列的列表); 
建立表的时候指定索引,例如CREATE TABLE tablename ( [...], INDEX [索引的名字] (列的列表) ); 

3.1.2惟一性索引 
这种索引和前面的“普通索引”基本相同,但有一个区别:索引列的全部值都只能出现一次,即必须惟一。惟一性索引能够用如下几种方式建立: 
建立索引,例如CREATE UNIQUE INDEX <索引的名字> ON tablename (列的列表); 
修改表,例如ALTER TABLE tablename ADD UNIQUE [索引的名字] (列的列表); 
建立表的时候指定索引,例如CREATE TABLE tablename ( [...], UNIQUE [索引的名字] (列的列表) ); 

3.1.3主键
主键是一种惟一性索引,但它必须指定为“PRIMARY KEY”。若是你曾经用过AUTO_INCREMENT类型的列,你可能已经熟悉主键之类的概念了。主键通常在建立表的时候指定,例如“CREATE TABLE tablename ( [...], PRIMARY KEY (列的列表) ); ”。可是,咱们也能够经过修改表的方式加入主键,例如“ALTER TABLE tablename ADD PRIMARY KEY (列的列表); ”。每一个表只能有一个主键。 


3.1.4全文索引 
MySQL从3.23.23版开始支持全文索引和全文检索。在MySQL中,全文索引的索引类型为FULLTEXT。全文索引能够在VARCHAR或者 TEXT类型的列上建立。它能够经过CREATE TABLE命令建立,也能够经过ALTER TABLE或CREATE INDEX命令建立。对于大规模的数据集,经过ALTER TABLE(或者CREATE INDEX)命令建立全文索引要比把记录插入带有全文索引的空表更快。本文下面的讨论再也不涉及全文索引,要了解更多信息,请参见MySQL documentation。 详细见:
mysql全文索引


3.1.5单列索引与多列索引 
索引能够是单列索引,也能够是多列索引。下面咱们经过具体的例子来讲明这两种索引的区别。

示例:假设有这样一个people表: 

CREATE TABLE people ( peopleid SMALLINT NOT NULL AUTO_INCREMENT, firstname CHAR(50) NOT NULL, lastname CHAR(50) NOT NULL, age SMALLINT NOT NULL, townid SMALLINT NOT NULL, PRIMARY KEY (peopleid) ); 

下面是咱们插入到这个people表的数据: 
这个数据片断中有四个名字为“Mikes”的人(其中两个姓Sullivans,两个姓McConnells),有两个年龄为17岁的人,还有一个名字不同凡响的Joe Smith。 

这个表的主要用途是根据指定的用户姓、名以及年龄返回相应的peopleid。例如,咱们可能须要查找姓名为Mike Sullivan、年龄17岁用户的peopleid(SQL命令为SELECT peopleid FROM people WHERE firstname='Mike' AND lastname='Sullivan' AND age=17;)。因为咱们不想让MySQL每次执行查询就去扫描整个表,这里须要考虑运用索引。 

首先,咱们能够考虑在单个列上建立索引,好比firstname、lastname或者age列。若是咱们建立firstname列的索引(ALTER TABLE people ADD INDEX firstname (firstname);),MySQL将经过这个索引迅速把搜索范围限制到那些firstname='Mike'的记录,而后再在这个“中间结果集”上 进行其余条件的搜索:它首先排除那些lastname不等于“Sullivan”的记录,而后排除那些age不等于17的记录。当记录知足全部搜索条件之 后,MySQL就返回最终的搜索结果。 

因为创建了firstname列的索引,与执行表的彻底扫描相比,MySQL的效率提升了不少,但咱们要求MySQL扫描的记录数量仍旧远远超过了实际所 须要的。虽然咱们能够删除firstname列上的索引,再建立lastname或者age列的索引,但总地看来,不论在哪一个列上建立索引搜索效率仍旧相 似。 

为了提升搜索效率,咱们须要考虑运用多列索引。若是为firstname、lastname和age这三个列建立一个多列索引,一个多列索引能够由最多15个列组成。MySQL只需一次检索就可以找出正确的结果!下面是建立这个多列索引的SQL命令: 

ALTER TABLE people ADD INDEX fname_lname_age (firstname,lastname,age); 

因为索引文件以B-树格式保存,MySQL可以当即转到合适的firstname,而后再转到合适的lastname,最后转到合适的age。在没有扫描数据文件任何一个记录的状况下,MySQL就正确地找出了搜索的目标记录! 

那么,若是在firstname、lastname、age这三个列上分别建立单列索引,效果是否和建立一个firstname、lastname、 age的多列索引同样呢?答案是否认的,二者彻底不一样。当咱们执行查询的时候,MySQL只能使用一个索引。若是你有三个单列的索引,MySQL会试图选择一个限制最严格的索引。可是,即便是限制最严格的单列索引,它的限制能力也确定远远低于firstname、lastname、age这三个列上的多列索引。 

3.1.6最左前缀 
多列索引还有另一个优势,它经过称为最左前缀(Leftmost Prefixing)的概念体现出来。继续考虑前面的例子,如今咱们有一个firstname、lastname、age列上的多列索引,咱们称这个索引 为fname_lname_age。当搜索条件是如下各类列的组合时,MySQL将使用fname_lname_age索引: 
firstname,lastname,age 
firstname,lastname 
firstname 
从另外一方面理解,它至关于咱们建立了(firstname,lastname,age)、(firstname,lastname)以及(firstname)这些列组合上的索引。下面这些查询都可以使用这个fname_lname_age索引: 

SELECT peopleid FROM people WHERE firstname='Mike' AND lastname='Sullivan' AND age='17'; 
SELECT peopleid FROM people WHERE firstname='Mike' AND lastname='Sullivan'; 
SELECT peopleid FROM people WHERE firstname='Mike'; 

#The following queries cannot use the index at all: 
SELECT peopleid FROM people WHERE lastname='Sullivan'; 
SELECT peopleid FROM people WHERE age='17'; 
SELECT peopleid FROM people WHERE lastname='Sullivan' AND age='17'; 

3.1.7空间索引

使用SPATIAL参数能够设置索引为空间索引。空间索引只能创建在空间数据类型上,这样能够提升系统获取空间数据的效率。MySQL中的空间数据类型包括GEOMETRY和POINT、LINESTRING和POLYGON等。目前只有MyISAM存储引擎支持空间检索,并且索引的字段不能为空值。对于初学者来讲,这类索引不多会用到。

 

3.二、索引的类型(从数据结构角度)

索引是在存储引擎中实现的,而不是在服务器层中实现的。因此,每种存储引擎的索引都不必定彻底相同,并非全部的存储引擎都支持全部的索引类型。


3.2.一、B-Tree索引

B-Tree:每个叶子节点都包含指向下一个叶子节点的指针,从而方便叶子节点的范围遍历。B-Tree一般意味着全部的值都是按顺序存储的,而且每个叶子页到根的距离相同,很适合查找范围数据。

B+树是一个平衡的多叉树,从根节点到每一个叶子节点的高度差值不超过1,并且同层级的节点间有指针相互连接。

在B+树上的常规检索,从根节点到叶子节点的搜索效率基本至关,不会出现大幅波动,并且基于索引的顺序扫描时,也能够利用双向指针快速左右移动,效率很是高。

所以,B+树索引被普遍应用于数据库、文件系统等场景。顺便说一下,xfs文件系统比ext3/ext4效率高不少的缘由之一就是,它的文件及目录索引结构所有采用B+树索引,而ext3/ext4的文件目录结构则采用Linked list, hashed B-tree、Extents/Bitmap等索引数据结构,所以在高I/O压力下,其IOPS能力不如xfs。


假设有以下一个表:

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-Tree索引进行全关键字、关键字范围和关键字前缀查询,固然,若是想使用索引,你必须保证按索引的最左边前缀(leftmost prefix of the index)来进行查询
(1)匹配全值(Match the full value):对索引中的全部列都指定具体的值。例如,上图中索引能够帮助你查找出生于1960-01-01的Cuba Allen。
(2)匹配最左前缀(Match a leftmost prefix):你能够利用索引查找last name为Allen的人,仅仅使用索引中的第1列。
(3)匹配列前缀(Match a column prefix):例如,你能够利用索引查找last name以J开始的人,这仅仅使用索引中的第1列。
(4)匹配值的范围查询(Match a range of values):能够利用索引查找last name在Allen和Barrymore之间的人,仅仅使用索引中第1列。
(5)匹配部分精确而其它部分进行范围匹配(Match one part exactly and match a range on another part):能够利用索引查找last name为Allen,而first name以字母K开始的人。
(6)仅对索引进行查询(Index-only queries):若是查询的列都位于索引中,则不须要读取元组的值。(覆盖索引)

因为B-树中的节点都是顺序存储的,因此能够利用索引进行查找(找某些值),也能够对查询结果进行ORDER BY。

固然,使用B-tree索引有如下一些限制:
(1) 查询必须从索引的最左边的列开始,不然没法使用索引。关于这点已经提了不少遍了。例如你不能利用索引查找在某一天出生的人。
(2) 不能跳过某一索引列。例如,你不能利用索引查找last name为Smith且出生于某一天的人。
(3) 存储引擎不能使用索引中范围条件右边的列。例如,若是你的查询语句为WHERE last_name="Smith" AND first_name LIKE 'J%' AND dob='1976-12-23',则该查询只会使用索引中的前两列,由于LIKE是范围查询。


3.2.二、Hash索引

哈希索引基于哈希表实现,只有精确索引全部列的查询才有效。对于每一行数据,存储引擎都会对全部的索引列计算一个哈希码,哈希码是一个较小的值,而且不一样键值的行计算出来的哈希码也不同。哈希索引将全部的哈希存储在索引中,同时在哈希表中保存指向每一个数据的指针。

MySQL中,只有Memory存储引擎显示支持hash索引,是Memory表的默认索引类型,尽管Memory表也可使用B-Tree索引。Memory存储引擎支持非惟一hash索引,这在数据库领域是罕见的,若是多个值有相同的hash code,索引把它们的行指针用链表保存到同一个hash表项中。
假设建立以下一个表:

CREATE TABLE testhash (
   fname VARCHAR(50) NOT NULL,
   lname VARCHAR(50) NOT NULL,
   KEY USING HASH(fname)
) ENGINE=MEMORY;

包含的数据以下:

假设索引使用hash函数f( ),以下:

f('Arjen') = 2323
f('Baron') = 7437
f('Peter') = 8784
f('Vadim') = 2458

 

此时,索引的结构大概以下:

哈希索引中存储的是:哈希值+数据行指针 

Slots是有序的,可是记录不是有序的。当你执行
mysql> SELECT lname FROM testhash WHERE fname='Peter';
MySQL会计算’Peter’的hash值,而后经过它来查询索引的行指针。由于f('Peter') = 8784,MySQL会在索引中查找8784,获得指向记录3的指针。
由于索引本身仅仅存储很短的值,因此,索引很是紧凑。Hash值不取决于列的数据类型,一个TINYINT列的索引与一个长字符串列的索引同样大。
 
Hash索引有如下一些限制:
(1)因为索引仅包含hash code和记录指针,因此,MySQL不能经过使用索引避免读取记录。可是访问内存中的记录是很是迅速的,不会对性形成太大的影响。
(2)哈希索引数据并非按照索引值顺序存储的,因此不能使用hash索引排序。
(3)Hash索引不支持键的部分匹配,由于是经过整个索引值来计算hash值的。例如,在数据列(A,B)上创建哈希索引,若是查询只有数据列A,则没法使用该索引。
(4)Hash索引只支持等值比较
,例如使用=,IN( )和<=>。对于WHERE price>100并不能加速查询。
(5)访问Hash索引的速度很是快,除非有不少哈希冲突(不一样的索引列值却有相同的哈希值)。当出现哈希冲突的时候,存储引擎必须遍历链表中全部的行指针,逐行进行比较,直到找到全部符合条件的行。
(6)若是哈希冲突不少的话,一些索引维护操做的代价也会很高。当从表中删除一行时,存储引擎要遍历对应哈希值的链表中的每一行,找到并删除对应行的引用,冲突越多,代价越大。

 

InnoDB引擎有一个特殊的功能叫作“自适应哈希索引”。当InnoDB注意到某些索引值被使用得很是频繁时,它会在内存中基于B-Tree索引上再建立一个哈希索引,这样就像B-Tree索引也具备哈希索引的一些优势,好比快速的哈希查找。

建立哈希索引:若是存储引擎不支持哈希索引,则能够模拟像InnoDB同样建立哈希索引,这能够享受一些哈希索引的便利,例如只须要很小的索引就能够为超长的键建立索引。

思路很简单:在B-Tree基础上建立一个伪哈希索引。这和真正的哈希索引不是一回事,由于仍是使用B-Tree进行查找,可是它使用哈希值而不是键自己进行索引查找。你须要作的就是在查询的where子句中手动指定使用哈希函数。这样实现的缺陷是须要维护哈希值。能够手动维护,也可使用触发器实现。

若是采用这种方式,记住不要使用SHA1和MD5做为哈希函数。由于这两个函数计算出来的哈希值是很是长的字符串,会浪费大量空间,比较时也会更慢。SHA1和MD5是强加密函数,设计目标是最大限度消除冲突,但这里并不须要这样高的要求。简单哈希函数的冲突在一个能够接受的范围,同时又可以提供更好的性能。

若是数据表很是大,CRC32会出现大量的哈希冲突,CRC32返回的是32位的整数,当索引有93000条记录时出现冲突的几率是1%。

处理哈希冲突:当使用哈希索引进行查询时,必须在where子句中包含常量值。


3.2.三、空间(R-Tree)索引

MyISAM支持空间索引,主要用于地理空间数据类型,例如GEOMETRY。和B-TREE索引不一样,这类索引无须前缀查询。空间索引会从全部到维度来索引数据。查询时,能够有效地使用任意维度来组合查询。必须使用MySQL的GIS相关函数如MBRCONTAINS()等来维护数据。MySQL的GIS支持并不完善,因此大部分人都不会使用这个特性。开源关系数据库系统中对GIS的解决方案作得比较好的是PostgreSQL的PostGIS。


3.2.四、全文(Full-text)索引
全文索引是MyISAM的一个特殊索引类型,innodb的5.6以上版本也支持,它查找的是文本中的关键词主要用于全文检索。

全文索引是一种特殊类型的索引,它查找都是文本中的关键词,而不是直接比较索引中的值。全文搜索和其余几类索引匹配方式彻底不同。它有许多须要注意的细节,如停用词、词干和复数、布尔搜索等。全文索引更相似于搜索引擎作的事情,而不是简单的WHERE条件匹配。

在相同的列上同时建立全文索引和基于值对B-Tree索引不会有冲突,全文索引适用于MATCH AGAINST操做,而不是普通的WHERE条件操做。

3.三、从物理存储角度索引分类

3.3.一、聚簇索引(Clustered Indexes)

聚簇索引保证关键字的值相近的元组存储的物理位置也相同(因此字符串类型不宜创建聚簇索引,特别是随机字符串,会使得系统进行大量的移动操做),且一个表只能有一个聚簇索引。由于由存储引擎实现索引,因此,并非全部的引擎都支持聚簇索引。目前,只有solidDB和InnoDB支持。

3.3.二、非聚簇索引

3.3.三、聚簇索引的结构大体以下:

叶子页包含了行的所有数据,可是节点页只包含了索引列。

二级索引叶子节点保存的不是指行的物理位置的指针,而是行的主键值。这意味着经过二级索引查找行,存储引擎须要找到二级索引的叶子节点获取对应的主键值,而后根据这个值去聚簇索引中查找到对应的行。这里作了重复的工做:两次B-TREE查找而不是一次。

 

 注:叶子页面包含完整的元组,而内节点页面仅包含索引的列(索引的列为整型)。一些DBMS容许用户指定聚簇索引,可是MySQL的存储引擎到目前为止都不支持。InnoDB对主键创建聚簇索引。若是你不指定主键,InnoDB会用一个具备惟一且非空值的索引来代替若是不存在这样的索引,InnoDB会定义一个隐藏的主键,而后对其创建聚簇索引通常来讲,DBMS都会以聚簇索引的形式来存储实际的数据,它是其它二级索引的基础。

3.3.四、InnoDB和MyISAM的数据布局的比较
为了更加理解聚簇索引和非聚簇索引,或者primary索引和second索引(MyISAM不支持聚簇索引),来比较一下InnoDB和MyISAM的数据布局,对于以下表:

CREATE TABLE layout_test (
   col1 int NOT NULL,
   col2 int NOT NULL,
   PRIMARY KEY(col1),
   KEY(col2)
);

假设主键的值位于1---10,000之间,且按随机顺序插入,而后用OPTIMIZE TABLE进行优化。col2随机赋予1---100之间的值,因此会存在许多重复的值。

(1)    MyISAM的数据布局
其布局十分简单,MyISAM按照插入的顺序在磁盘上存储数据,以下:

 注:左边为行号(row number),从0开始。由于元组的大小固定,因此MyISAM能够很容易的从表的开始位置找到某一字节的位置。
MyISAM创建的primary key的索引结构大体以下:

 注:MyISAM不支持聚簇索引,索引中每个叶子节点仅仅包含行号(row number),且叶子节点按照col1的顺序存储
来看看col2的索引结构:

 实际上,在MyISAM中,primary key和其它索引没有什么区别。Primary key仅仅只是一个叫作PRIMARY的惟一,非空的索引而已,叶子节点按照col2的顺序存储

(2)    InnoDB的数据布局
InnoDB按聚簇索引的形式存储数据,因此它的数据布局有着很大的不一样。它存储表的结构大体以下:

 注:聚簇索引中的每一个叶子节点包含primary key的值,事务ID和回滚指针(rollback pointer)——用于事务和MVCC,和余下的列(如col2)。

相对于MyISAM,InnoDB的二级索引与聚簇索引有很大的不一样。InnoDB的二级索引的叶子包含primary key的值,而不是行指针(row pointers),这样的策略减少了移动数据或者数据页面分裂时维护二级索引的开销,由于InnoDB不须要更新索引的行指针。其结构大体以下:

 聚簇索引和非聚簇索引表的对比:

 

 

3.3.四、按primary key的顺序插入行(InnoDB)

若是你用InnoDB,并且不须要特殊的聚簇索引,一个好的作法就是使用代理主键(surrogate key)——独立于你的应用中的数据。最简单的作法就是使用一个AUTO_INCREMENT的列,这会保证记录按照顺序插入,并且能提升使用primary key进行链接的查询的性能。应该尽可能避免随机的聚簇主键,例如,字符串主键就是一个很差的选择,它使得插入操做变得随机。

 

4、覆盖索引(Covering Indexes)

mysql索引之六:mysql高效索引之覆盖索引

  覆盖索引是一种很是强大的工具,能大大提升查询性能。设计优秀的索引应该考虑到整个查询,而不仅仅的where条件部分。索引确实是一种查找数据的高效方式,可是MYSQL也可使用索引来直接获取列的数据,这样就再也不须要读取数据行。索引的叶子节点中已经包含要查询的数据,那么就没有必要再回表查询了,若是索引包含知足查询的全部数据,就称为覆盖索引。

  解释一: 就是select的数据列只用从索引中就可以取得,没必要从数据表中读取,换句话说查询列要被所使用的索引覆盖。
  解释二: 索引是高效找到行的一个方法,当能经过检索索引就能够读取想要的数据,那就不须要再到数据表中读取行了。若是一个索引包含了(或覆盖了)知足查询语句中字段与条件的数据就叫作覆盖索引。
  解释三: 是非汇集组合索引的一种形式,它包括在查询里的Select、Join和Where子句用到的全部列(即创建索引的字段正好是覆盖查询语句[select子句]与查询条件[Where子句]中所涉及的字段,也即,索引包含了查询正在查找的全部数据)。

只须要读取索引而不用读取数据有如下一些优势:
(1)索引项一般比记录要小,因此MySQL访问更少的数据;
(2)索引都按值的大小顺序存储,相对于随机访问记录,须要更少的I/O;
(3)大多数据引擎能更好的缓存索引。好比MyISAM只缓存索引。
(4)覆盖索引对于InnoDB表尤为有用,由于InnoDB使用汇集索引组织数据,若是二级索引中包含查询所需的数据,就再也不须要在汇集索引中查找了。
覆盖索引不能是任何索引,只有B-TREE索引存储相应的值。并且不一样的存储引擎实现覆盖索引的方式都不一样,并非全部存储引擎都支持覆盖索引(Memory和Falcon就不支持)。

对于索引覆盖查询(index-covered query),使用EXPLAIN时,能够在Extra一列中看到“Using index”。例如,在sakila的inventory表中,有一个组合索引(store_id,film_id),对于只须要访问这两列的查询,MySQL就可使用索引,以下:

mysql> EXPLAIN SELECT store_id, film_id FROM sakila.inventory\G

*************************** 1. row ***************************
           id: 1
 select_type: SIMPLE
        table: inventory
         type: index
possible_keys: NULL
          key: idx_store_id_film_id
      key_len: 3
          ref: NULL
         rows: 5007
        Extra: Using index
1 row in set (0.17 sec)

(同时查询actor_id[主键]与last_name[索引字段])在大多数引擎中,只有当查询语句所访问的列是索引的一部分时,索引才会覆盖。可是,InnoDB不限于此,InnoDB的二级索引在叶子节点中存储了primary key的值。所以,sakila.actor表使用InnoDB,并且对因而last_name上有索引,因此,索引能覆盖那些访问actor_id的查询,如:

mysql> EXPLAIN SELECT actor_id, last_name
    -> FROM sakila.actor WHERE last_name = 'HOPPER'\G
*************************** 1. row ***************************
           id: 1
 select_type: SIMPLE
        table: actor
         type: ref
possible_keys: idx_actor_last_name
          key: idx_actor_last_name
      key_len: 137
          ref: const
         rows: 2
        Extra: Using where; Using index

5、利用索引进行排序

MySQL中,有两种方式生成有序结果集:

  • 一是使用filesort
  • 二是按索引顺序扫描

若是explain出来的type列的值为“index”,则说明MYSQL使用了索引扫描来作排序。利用索引进行排序操做是很是快的,由于只须要从一条索引记录移动到紧接着的下一条记录。但若是索引不能覆盖查询所需的所有列,那就不得不每扫描一条索引记录就回表查询一次对应的行,这基本上都是随机IO,所以按索引顺序读取的速度一般要比顺序地全表扫描慢,尤为是在IO密集型的工做负载时

并且能够利用同一索引同时进行查找和排序操做。当索引的顺序与ORDER BY中的列顺序相同且全部的列是同一方向(所有升序或者所有降序)时,可使用索引来排序。若是查询是链接多个表,仅当ORDER BY中的全部列都是第一个表的列时才会使用索引。其它状况都会使用filesort文件排序。

create table actor(
actor_id int unsigned NOT NULL AUTO_INCREMENT,
name      varchar(16) NOT NULL DEFAULT '',
password        varchar(16) NOT NULL DEFAULT '',
PRIMARY KEY(actor_id),
KEY     (name)
) ENGINE=InnoDB;
insert into actor(name,password) values('cat01','1234567');
insert into actor(name,password) values('cat02','1234567');
insert into actor(name,password) values('ddddd','1234567');
insert into actor(name,password) values('aaaaa','1234567');

explain结果:

mysql> explain select actor_id from actor order by actor_id \G
*************************** 1. row ***************************
           id: 1
 select_type: SIMPLE
        table: actor
         type: index
possible_keys: NULL
          key: PRIMARY
      key_len: 4
          ref: NULL
         rows: 4
        Extra: Using index
1 row in set (0.00 sec)


mysql> explain select actor_id from actor order by password \G
*************************** 1. row ***************************
           id: 1
 select_type: SIMPLE
        table: actor
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 4
        Extra: Using filesort
1 row in set (0.00 sec)

mysql> explain select actor_id from actor order by name \G
*************************** 1. row ***************************
           id: 1
 select_type: SIMPLE
        table: actor
         type: index
possible_keys: NULL
          key: name
      key_len: 18
          ref: NULL
         rows: 4
        Extra: Using index
1 row in set (0.00 sec)

对于filesort,MySQL有两种排序算法。 当MySQL不能使用索引进行排序时,就会利用本身的排序算法(快速排序算法)在内存(sort buffer)中对数据进行排序,若是内存装载不下,它会将磁盘上的数据进行分块,再对各个数据块进行排序,而后将各个块合并成有序的结果集(实际上就是外排序,使用临时表)

(1)两次扫描算法(Two passes)
实现方式是先将须要排序的字段和能够直接定位到相关行数据的指针信息取出,而后在设定的内存(经过参数sort_buffer_size设定)中进行排序,完成排序以后再次经过行指针信息取出所需的Columns。
注:该算法是4.1以前采用的算法,它须要两次访问数据,尤为是第二次读取操做会致使大量的随机I/O操做。另外一方面,内存开销较小。

(2)一次扫描算法(single pass)
该算法一次性将所需的Columns所有取出,在内存中排序后直接将结果输出。
注:从 MySQL 4.1 版本开始使用该算法。它减小了I/O的次数,效率较高,可是内存开销也较大。若是咱们将并不须要的Columns也取出来,就会极大地浪费排序过程所须要的内存。在 MySQL 4.1 以后的版本中,能够经过设置 max_length_for_sort_data 参数来控制 MySQL 选择第一种排序算法仍是第二种。当取出的全部大字段总大小大于 max_length_for_sort_data 的设置时,MySQL 就会选择使用第一种排序算法,反之,则会选择第二种。为了尽量地提升排序性能,咱们天然更但愿使用第二种排序算法,因此在 Query 中仅仅取出须要的 Columns 是很是有必要的。

当对链接操做进行排序时,若是ORDER BY仅仅引用第一个表的列,MySQL对该表进行filesort操做,而后进行链接处理,此时,EXPLAIN输出“Using filesort”;不然,MySQL必须将查询的结果集生成一个临时表,在链接完成以后进行filesort操做,此时,EXPLAIN输出“Using temporary;Using filesort”。

 

6、索引与加锁

索引对于InnoDB很是重要,由于它可让查询锁更少的元组。这点十分重要,由于MySQL 5.0中,InnoDB直到事务提交时才会解锁。有两个方面的缘由:首先,即便InnoDB行级锁的开销很是高效,内存开销也较小,但无论怎么样,仍是存在开销。其次,对不须要的元组的加锁,会增长锁的开销,下降并发性。
InnoDB仅对须要访问的元组加锁,而索引可以减小InnoDB访问的元组数。可是,只有在存储引擎层过滤掉那些不须要的数据才能达到这种目的。一旦索引不容许InnoDB那样作(即达不到过滤的目的),MySQL服务器只能对InnoDB返回的数据进行WHERE操做,此时,已经没法避免对那些元组加锁了:InnoDB已经锁住那些元组,服务器没法解锁了。
来看个例子:

create table actor(
actor_id int unsigned NOT NULL AUTO_INCREMENT,
name      varchar(16) NOT NULL DEFAULT '',
password        varchar(16) NOT NULL DEFAULT '',
PRIMARY KEY(actor_id),
KEY     (name)
) ENGINE=InnoDB;
insert into actor(name,password) values('cat01','1234567');
insert into actor(name,password) values('cat02','1234567');
insert into actor(name,password) values('ddddd','1234567');
insert into actor(name,password) values('aaaaa','1234567');
SET AUTOCOMMIT=0;
BEGIN;
SELECT actor_id FROM actor WHERE actor_id < 4
AND actor_id <> 1 FOR UPDATE;
mysql> EXPLAIN SELECT actor_id FROM test.actor
    -> WHERE actor_id < 4 AND actor_id <> 1 FOR UPDATE \G
*************************** 1. row ***************************
           id: 1
 select_type: SIMPLE
        table: actor
         type: index
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: NULL
         rows: 4
        Extra: Using where; Using index
1 row in set (0.00 sec)
mysql>

 该查询仅仅返回2---3的数据,实际已经对1---3的数据加上排它锁了。InnoDB锁住元组1是由于MySQL的查询计划仅使用索引进行范围查询(而没有进行过滤操做,WHERE中第二个条件已经没法使用索引了)

 

代表存储引擎从索引的起始处开始,获取全部的行,直到actor_id<4为假,服务器没法告诉InnoDB去掉元组1。
为了证实row 1已经被锁住,咱们另外建一个链接,执行以下操做:

SET AUTOCOMMIT=0;
BEGIN;
SELECT actor_id FROM actor WHERE actor_id = 1 FOR UPDATE;

 该查询会被挂起,直到第一个链接的事务提交释放锁时,才会执行(这种行为对于基于语句的复制(statement-based replication)是必要的)。
如上所示,当使用索引时,InnoDB会锁住它不须要的元组。更糟糕的是,若是查询不能使用索引,MySQL会进行全表扫描,并锁住每个元组,无论是否真正须要。

 

 

 

7、索引的优势:

最多见的B-Tree索引,按照顺序存储数据,因此MYSQL能够用来作order by和group by操做。由于数据是有序的,因此B-Tree也就会将相关的列值存储在一块儿。最后,由于索引中存储了实际的列值,因此某些查询只使用索引就可以完成所有查询。总结下来索引有以下三个优势:

1,索引大大减少了服务器须要扫描的数据量

2,索引能够帮助服务器避免排序和临时表

3,索引能够将随机IO变成顺序IO

索引三星系统:

一星:索引将相关的记录放到一块儿

二星:索引中的数据顺序和查找中的排列顺序一致

三星:索引中的列包含了查询中须要的所有列

 

索引是最好的解决方案吗?

索引并不老是最好的工具。总的来讲只有索引帮助存储引擎快速查找到记录的好处大于其带来的额外工做时,索引才是有效的。

对于很是小的表,大部分状况下简单的全表扫描更高效;

对于中到大型的表,索引就很是有效。

但对于特大型的表,创建和使用索引的代价将随之增加。这种状况下须要一种技术能够直接区分出查询须要的一组数据,而不是一条记录一条记录地匹配。例如使用分区技术。

若是表的数量特别多,能够创建一个元数据信息表,用来查询须要用到的某些特性。例如执行那些须要聚合多个应用分布在多个表的数据的查询,则须要记录“哪一个用户的信息存储在哪一个表中”的元数据,这样在查询时就能够直接忽略那些不包含指定用户信息的表。

 

8、高性能的索引策略

 

5.1独立的列

独立的列是指索引列不能是表达式的一部分,也不是是函数的参数。例如如下两个查询没法使用索引:

1)表达式:  select actor_id from sakila.actor where actor_id+1=5;

2)函数参数:select ... where TO_DAYS(CURRENT_DATE) - TO_DAYS(date_col)<=10;

 

5.2前缀索引和索引选择性

  有时须要索引很长的字符列,它会使索引变大并且变慢。一个策略就是模拟哈希索引。可是有时这也不够好,那?

一般能够索引开始的几个字符,而不是所有值,以节约空间并获得好的性能。这使索引须要的空间变小,但这样也会下降索引的选择性。索引的选择性是指,不重复的索引值(基数)和数据表中的记录总数(#T)的比值,范围从1/#T之间。索引的选择性越高则查询效率越高,由于选择性高的索引可让MYSQL在查找时过滤掉更多的行。

惟一索引的选择性是1,这是最好的索引选择性,性能也是最好的。

通常状况下某个前缀的选择性也是足够高的,足以知足查询性能。对于BLOB、TEXT或者很长的VARCHAR类型的列,必须使用前缀索引,由于mysql不容许索引它们的全文化。

能够在同一个查询中针对许多不一样的前缀长度进行计算,选择选择性好的。

(用LEFT函数,left(city,4))

添加前缀索引

mysql>alter table test.test add key (city(7));

前缀索引能很好地减小索引的大小及提升速度,可是mysql不能在order by 和group by查询中使用前缀索引,也不能把它们用来覆盖索引。

有时后缀索引也挺有用,例如查找某个域名的全部电子邮件地址。mysql不支持反向索引,可是能够把反向字符串保存起来,而且索引它的前缀。能够用触发器维护这种索引。

决窍:在于要选择足够长的前缀以保证较高的选择性,同时又不能太长(以便节约空间)。前缀应该足够长,以使得前缀索引的选择性接近于索引整个列。换句话说,前缀的“基数”应该接近于完整列的“基数”。为了决定前缀的合适长度,须要找到最多见的值的列表,而后和最多见的前缀列表进行比较。例如如下查询:

select count(*) as cnt,city from sakila.city_demo group by city order by cnt desc limit 10;

select count(*) as cnt,left(city,7) as perf  from sakila.city_demo group by city order by cnt desc limit 10;

直到这个前缀的选择性接近完整列的选择性。

计算合适的前缀长度的另外一个方法就是计算完整列的选择性,并使前缀的选择性接近于完整列的选择性,以下:

select  count(distinct city)/count(*) from sakila.city_demo;

select  count(distinct left(city,7))/count(*) from sakila.city_demo;

前缀索引是一种能使索引更小、更快的有效办法,但另外一方面也有其缺点:MYSQL没法使用前缀索引作order by和group by,也没法使用前缀索引作覆盖扫描。

5.3多列索引

一个多列索引与多个列索引MYSQL在解析执行上是不同的,若是在explain中看到有索引合并,应该好好检查一下查询的表和结构是否是已经最优。

 

5.4选择合适的索引列顺序

对于如何选择索引的顺序有一个经验法则:将选择性最高的列放在索引最前列。

当不须要考虑排序和分组时,将选择性最高的列放在前面一般是最好的。而后,性能不仅是依赖于全部索引列的选择性(总体基数),也和查询条件的具体值有关,也就是和值的分布有关。这和前面介绍的选择前缀的长度须要考虑的地方同样。可能须要根据那些运行频率最高的查询来调整索引列的顺序,让这种状况下索引的选择性最高。

使用经验法则要注意不要假设平均状况下的性能也能表明特殊状况下的性能,特殊状况可能会摧毁整个应用的性能(当使用前缀索引时,在某些条件值的基数比正常值高的时候)。

 5.5单列索引、多列索引以及最左前缀 原则及使用上的特色

结合实例说明以下:如今咱们想查出知足如下条件的用户id:
mysql>SELECT `uid` FROM people WHERE lname`='Liu'  AND `fname`='Zhiqun' AND `age`=26
由于咱们不想扫描整表,故考虑用索引。

单列索引:
ALTER TABLE people ADD INDEX lname (lname);
将lname列建索引,这样就把范围限制在lname='Liu'的结果集1上,以后扫描结果集1,产生知足fname='Zhiqun'的结果集2,再扫描结果集2,找到 age=26的结果集3,即最终结果。

由 于创建了lname列的索引,与执行表的彻底扫描相比,效率提升了不少,但咱们要求扫描的记录数量仍旧远远超过了实际所需 要的。虽然咱们能够删除lname列上的索引,再建立fname或者age 列的索引,可是,不论在哪一个列上建立索引搜索效率仍旧类似。

2.多列索引:
ALTER TABLE people ADD INDEX lname_fname_age (lame,fname,age);
为了提升搜索效率,咱们须要考虑运用多列索引,因为索引文件以B-Tree格式保存,因此咱们不用扫描任何记录,便可获得最终结果。

注:在mysql中执行查询时,只能使用一个索引,若是咱们在lname,fname,age上分别建索引,执行查询时,只能使用一个索引,mysql会选择一个最严格(得到结果集记录数最少)的索引。

3.最左前缀:顾名思义,就是最左优先,上例中咱们建立了lname_fname_age多列索引,至关于建立了(lname)单列索引,(lname,fname)组合索引以及(lname,fname,age)组合索引

注:在建立多列索引时,要根据业务需求,where子句中使用最频繁的一列放在最左边。

 

 

9、分析索引效率 

如今咱们已经知道了一些如何选择索引列的知识,但还没法判断哪个最有效。MySQL提供了一个内建的SQL命令帮助咱们完成这个任务,这就是 EXPLAIN命令。EXPLAIN命令的通常语法是:EXPLAIN 。你能够在MySQL文档找到有关该命令的更多说明。下面是一个例子: 

EXPLAIN SELECT peopleid FROM people WHERE firstname='Mike' AND lastname='Sullivan' AND age='17'; 

这个命令将返回下面这种分析结果: 

下面咱们就来看看这个EXPLAIN分析结果的含义。 
见explain章节


10、索引的缺点 

到目前为止,咱们讨论的都是索引的优势。事实上,索引也是有缺点的。 首先,索引要占用磁盘空间。一般状况下,这个问题不是很突出。可是,若是你建立每一种可能列组合的索引,索引文件体积的增加速度将远远超过数据文件。若是你有一个很大的表,索引文件的大小可能达到操做系统容许的最大文件限制。 第二,对于须要写入数据的操做,好比DELETE、UPDATE以及INSERT操做,索引会下降它们的速度。这是由于MySQL不只要把改动数据写入数据文件,并且它还要把这些改动写入索引文件。 【结束语】在大型数据库中,索引是提升速度的一个关键因素。无论表的结构是多么简单,一次500000行的表扫描操做不管如何不会快。若是你的网站上也有 这种大规模的表,那么你确实应该花些时间去分析能够采用哪些索引,并考虑是否能够改写查询以优化应用。要了解更多信息,请参见MySQL manual。另外注意,本文假定你所使用的MySQL是3.23版,部分查询不能在3.22版MySQL上执行。

相关文章
相关标签/搜索