索引可以让服务器快速定位到表的指定位置。
总结下来,索引有如下三个优点:
使用B-Tree数据结构来存储数据,大多MySQL引擎都支持该索引。B-Tree索引可以加快访问数据的速度,因为B-Tree对索引列顺序组织存储,范围查找快。
* 唯一索引 *
主键索引
a. 全值匹配:全值匹配指的是和索引中的所有列进行匹配。
b. 匹配最左前缀:即只使用索引的第一列。
c. 匹配列前缀:也可以值匹配某一列的值的开头部分。
d. 匹配范围值
e. 精确匹配某一列并范围匹配另外一列
f. 只访问索引的查询:覆盖索引
a. 如果不是按索引的最左列开始查找,则无法使用索引。
b. 不能跳过索引中的列。
c. 如果查询中有某个列的范围查询,则最右边的所有列都无法使用索引优化查询。如果范围查询列值的数量有限,那么可以通过使用多个等于条件来代替范围查找。
哈希索引基本哈希表实现,只有精确匹配索引所有列的查询才有效。结构十分紧凑,查询速度非常快。
对于每一行数据,存储引擎都会对所有的索引列计算一个哈希码,哈希码值较小。哈希索引将所有的哈希码存储在索引中,同时在哈斯表中保存指向每个数据行的指针。MySQL中只有Memory引擎显示支持哈希索引。哈希表只能在内存实现,没法再磁盘实现
索引列会被存储在匹配到的hash bucket里面的表里,这个表里会有实际的数据行指针,再根据实际的数据行指针查找对应的数据行。
概括来说,要查找一行数据或者处理一个where子句
1、根据where条件里面的参数生成合适的哈希函数
2、索引列进行匹配,匹配到对应hash bucket,找到对应hash bucket意味着也找到了对应的数据行指针(row pointer)
3、读取数据
https://www.cnblogs.com/Jtianlin/p/5150104.html
哈希索引比起B树索引简单,因为它不需要遍历B树,所以访问速度会更快
1、因为Hash索引比较的是经过Hash计算的值,所以只能进行等式比较,不能用于范围查询
2、由于哈希值是按照顺序排列的,但是哈希值映射的真正数据在哈希表中就不一定按照顺序排列,所以无法利用Hash索引来加速任何排序操作
3、不能用部分索引键来搜索,因为组合索引在计算哈希值的时候是一起计算的。
4、当哈希值大量重复且数据量非常大时,其检索效率并没有Btree索引高的。
应用场景
文本字段上的普通索引只能加快对出现在字段内最前面的字符串进行的检索操作,如果字段里存放的是由几个或者多个单词构成的大段文字,普通索引就不行了,这种场合用全文索引比较合适
查询效率
创建全文索引
- 创建article表
CREATE TABLE article (
id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
title VARCHAR(200),
content TEXT,
FULLTEXT (title, content) —在title和content列上创建全文索引
);
ALTER TABLE article
ADD FULLTEXT INDEX fulltext_article (title, content)
SELECT * FROM article WHERE MATCH(title, content) AGAINST(‘查询字符串’)
SELECT * FROM article WHERE content LIKE ‘%查询字符串%’
强烈注意:MySQL自带的全文索引只能用于数据库引擎为MyISAM的数据表,如果是其他数据引擎,则全文索引不会生效。此外,MySQL自带的全文索引只能对英文进行全文检索,目前无法对中文进行全文检索。如果需要对包含中文在内的文本数据进行全文检索,我们需要采用Sphinx(斯芬克斯)/Core
备注1:目前,使用MySQL自带的全文索引时,如果查询字符串的长度过短将无法得到期望的搜索结果。MySQL全文索引所能找到的词的默认最小长度为4个字符。另外,如果查询的字符串包含停止词,那么该停止词将会被忽略。
备注2:如果可能,请尽量先创建表并插入所有数据后再创建全文索引,而不要在创建表时就直接创建全文索引,因为前者比后者的全文索引效率要高。
有时候需要索引很长的字符列,这会让索引变得很大且慢。通常可以索引开始部分的字符,这样可以大大节约索引空间,从而提高索引效率。但这样会降低索引的选择性。
a. 索引的选择性是指,不重复的索引值(也称为基数)和数据表的记录总数(#T)的比值
范围从1/#T到1之间。索引的选择性越高则查询效率越高,因为选择性高的索引可以让MySQL在查找时过滤掉更多的行。
唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的。
b. 一般情况下某个列前缀的选择性也是足够高的,足以满足查询性能。
对于BLOB、TEXT或者很长的VARCHAR类型的列,必须使用前缀索引,因为MySQL不允许索引这些列的完整长度。
c. 诀窍在于要选择足够长的前缀以保证较高的选择性,同时又不能太长(以便节省空间)。
计算合适的前缀长度的一个方法是计算完整列的选择性,并使前缀的选择性接近于完整列的选择性。
下面是如何计算完整列的选择性
SELECT COUNT(DISTINCT city)/COUNT(*) FROM sakila.city_demo; 查询结构值为:0.031
通常来说,这个例子中如果前缀的选择性能够接近于0.031,基本上就可以用了。
可以在一个查询中针对不同前缀长度进行计算,这对于大表非常有用。
下面给出了如何在同一个查询中计算不同前缀长度的选择性:
SELECT COUNT(DISTINCT LEFT(city,3)) AS sel3,
COUNT(DISTINCT LEFT(city,4)) AS sel4,COUNT(DISTINCT LEFT(city,5)) AS sel5,
COUNT(DISTINCT LEFT(city,6)) AS sel6,COUNT(DISTINCT LEFT(city,7)) AS sel7
FROM sakila.city_demo;
查询结果值,按顺序为:0.0238 , 0.0293, 0.0305,0.0309,0.0310
查询显示当前前缀长度到达7的时候,再增加前缀长度,选择性提升的幅度已经很小了。
d. 只看平均选择性是不够的,也有例外的情况,需要考虑最坏情况下的选择性。
e. 前缀索引是一种能使索引更小、更快的有效办法
-但另一方面也有其缺点:MySQL无法使用前缀索引做ORDER BY和GROUP BY , 也无法使用前缀索引做覆盖扫描。
f. 有时候后缀索引也有用途(例如,找到某个域名的所有电子邮件地址)。
-MySQL原生并不支持反向索引,但是可以把字符串反转后存储,并基于此建立前缀索引。可以通过触发器来维护这种索引。
在MySQL或更新的版本中,会使用”索引合并”策略,查询能同时使用两个单列索引进行扫描,并将结果进行合并。这种算法有三个变种:
索引合并策略有时候是一种优化的结果,但实际上更多的时候说明表上的索引建的很糟糕:
a. 当出现服务器对多个索引做相交操作时(通常多个AND条件),通常意味着需要一个包含所有相关列的多列索引,而不是多个独立的单列索引。
b. 当服务器需要对多个索引做联合操作时(通常由多个OR条件),通常需要耗费大量CPU和内存资源在算法的缓存、排序和合并操作上。
特别是当其中有些索引的选择性不高,需要合并扫描返回的大量数据时。
c. 优化器不会把这些计算到”查询成本”(cost)中,优化器值关系随机页面读取。
这样不仅消耗更多的CPU和内存资源,还会影响查询的并发性。
d. 如果在EXPLAIN中看到有索引合并,应该好好检查一下查询和表的结构,看是不是已经最优的
。也可以哦太难过参数optimizer_switch来关闭索引合并功能。也可以使用IGNORE INDEX提示让优化器
忽略掉某些索引。
正确的索引顺序依赖于使用该索引的查询,并同时满足需要考虑如何更好地满足排序和分组的需要。
a. 在一个多列的B-Tree索引中,索引列的顺序意味着索引首先按照最左列进行排序,其次是第二列,等等。
所以,索引可以按照升序或者降序进行全表扫描,以满足符合列顺序的ORDER BY,GROUP BY和DISTINCT等字句的查询需求。
b. 当不需要考虑排序和分组时,将选择性最高的列放在前面通常是很好的。这时候索引的作用只是用于优化WHERE条件查询。
c. 更具运行频率最高的查询来调整索引的顺序,让这种情况下索引的选择性最高。
d. 如果是从诸如pt-query-digest这样的工具的报告中提起”最差”查询,那么再按上面办法选定索引顺序往往是非常高效的。
如果没有类似的具体查询来运行,那么最好还是按经验法则来做,因为全局法则考滤的是全局基数和选择性,而不是某个具体查询。
SELECT COUNT(DISTINCT staff_id)/COUNT(*) AS staff_id_selectivity,
COUNT(DISTINCT customer_id)/COUNT(*) AS customer_id_selectivity,
count(*) from payment
查询结果值为:
staff_id_selectivity 0.0001,customer_id_selectivity 0.0373, count(*) 16049
customer_id 的选择性更高,所以答案是将其作为索引列的第一列
非聚簇索引
但是记录的物理和索引不一定一致,两种索引都采用B+树结构,非聚集索引的叶子层并不和实际数据页相重叠,而采用叶子层包含一个指向表中的记录在数据页中的指针方式。非聚集索引层次多,不会造成数据重排。
非聚集索引叶节点仍然是索引节点,只是有一个指针指向对应的数据块,此如果使用非聚集索引查询,而查询列中包含了其他该索引没有覆盖的列,那么他还要进行第二次的查询,查询节点上对应的数据行的数据。
* 聚簇索引与非聚簇索引区别*
* InnoDB将通过主键聚集数据。*
如果没有定义主键,InnoDB会选择一个唯一的非空索引代替。如果没有这样的索引,InnoDB会隐式定义一个主键作为聚簇索引。InnoDB只聚集在同一个页面中的记录。包含相邻键值的页面可能会相距甚远。
* 聚簇索引的优点:*
a. 可以把相关的数据保存在一起。
b. 数据访问更快。
c. 使用覆盖索引扫描的查询可以直接使用叶节点中的主键值。
* 聚簇索引的缺点:*
a. 更新聚簇索引列的代价很高,因为会强制将每个被更新的行移动到新的位置。
b. 可能会导致页分裂。
c. 导致全表扫描变慢,尤其是行比较稀疏。
e. 二级索引(非聚簇索引)可能比想象的要更大,因为在二级索引的叶子节点包含了引用行的主键列。
f. 二级索引访问需要两次索引查询,而不是一次。
这是因为二级索引叶子节点保存的不是指向行的物理位置的指针,而是行的主键值。这意味着通过二级索引查找行,存储引擎需要找到二级索引的叶子节点获得对应的主键值,然后根据这个值去聚簇索引中查找到对应的行。这里做了重复工作:两次B-Tree查找而不是一次。
* 最好避免随机的(不连续且值的分布范围非常大)聚簇索引,特别是对于IO密集型的应用。*
例如:从性能角度考虑,使用UUID作为聚簇索引会很糟糕:它使得聚簇索引的插入变得完全随机,这是最坏的情况,是的数据没有任何聚集。向UUID主键插入行不仅花费更长的时间,而且索引占用的空间也更大。这一方面是由于主键字段更长,另一方面毫无疑问是由于页分裂和碎片导致的。
* 使用InnoDB时应该尽可能地按主键顺序插入数据,并且尽可能地使用单调增加的聚簇键的值插入新行。*
3.6.1 覆盖索引的好处:
-b. 因为索引是按照值顺序存储的(至少在单个页内如此),所以对于IO密集型的范围查询会比随机从磁盘读取每一行数据的IO要少得多。
-c. 由于InnoDB的聚簇索引,覆盖索引对InnoDB表特别有用。
InnoDB的二级索引在叶子节点中保存了行的主键值,所以如果二级主键能够覆盖查询,则可以避免对主键索引的二次查询。
3.6.2 不是所有类型的索引都可以作为覆盖索引
覆盖索引必须要存储索引列的值,而哈希索引、空间索引和全文索引等都不存储索引列的值,所以MySQL只能使用B-Tree索引做覆盖索引。不是所有的索引都支持覆盖索引。
3.6.3 当发起一个索引覆盖的查询时,在EXPLAIN的Extra列可以看到”Using index”的信息。
6.4 MySQL不能在索引中执行LIKE操作。
因为该操作可能转换为简单的比较操作,但是如果是通配符开头的LIKE查询,存储引擎就无法比较匹配。MySQL服务器只能提取数据行的值而不是索引值来作比较。
6.5 延迟关联
延迟对列的访问。在查询第一阶段MySQL可以做覆盖索引,在FROM子句的子查询中使用索引。
MySQL有两种方式可以生成有序的结果
通过排序操作;或者按索引顺序扫描;如果EXPLAIN出来的type列的值为”index”,则说明MySQL使用了索引来做排序(不要和Extra列的”Using index”搞混淆了)。
a. 索引本身是很快的,因为只需要从一条索引记录移动到紧接着的下一条记录。
如果索引不能覆盖查询所需的全部列,那就不得不每扫描一条索引记录就回表查询一次对应的行。这基本上都是随机IO,因此按索引顺序读取数据的速度通常要比顺序地全表扫描慢,尤其是在IO密集型的工作负载时。
b. 只有当索引的列顺序和ORDER BY子句顺序完全一致,并且所有列的顺序方向(倒序或正序)都一样时
MySQL才能够使用索引来对结果做排序。如果查询需要关联多张表,则只有ORDER BY子句引用的字段全部为第一个表时,才能使用索引做排序。
c. 有一种情况下ORDER BY子句可以不满足索引的最左前缀的要求,就是前导列为常量的时候。
MyISAM使用前缀索引压缩来减少索引的大小,从而让更多的索引可以放入内存中,这在某些情况下能极大地提高性能。默认值压缩字符串,到哪通过参数设置可以对整数压缩。
MySQL允许在相同列上创建多个索引,无论是有意还是无意的。MySQL需要单独维护重复的索引,并且优化器在优化查询的是时候也需要逐个地进行考虑,这会影响性能。
a. 重复索引:
例如如下代码:
CREATE TABLE test (ID INT NOT NULL PRIMARY KEY, A INT NOT NULL,B INT NOT NULL,UNIQUE(ID),INDEX(ID)) ENGINE = InnoDB
一个经验不足的用户可能是想创建一个主键,先加上唯一限制,然后再加上索引以供查询使用。事实上,MySQL的唯一限制和主键限制都是通过索引实现的,因此,上面的写法实际上在相同的列上创建了三个重复的索引。通常并没有理由这样做,除非是在同一列上创建不同类型的索引来满足不同的查询需求。
b. 冗余索引:
如果创建了索引(A,B),在创建索引(A)就是冗余索引,因为这只是前一个索引的前缀索引。
冗余索引通常发生在为表添加新索引的时候。例如,有人可能会增加一个新的索引(A,B)而不是扩展已有的索引(A)。还有一种情况是将一个索引扩展为(A,ID),其中ID是主键,对于InnoDB来说主键已经包含在二级索引中了,所以这也是冗余的。
c. 解决冗余索引和重复索引的方法很简单,删除这些索引就可以了,但首先要找出这样的索引。
有张表名为table的表,由三列组成,分别是姓名、性别和婚姻状况,其中性别只有男和女两项,婚姻状况由已婚、未婚、离婚这三项,该表共有100w个记录。现在有这样的查询: select * from table where Gender=‘男’ and Marital=“未婚”;
不使用索引
不使用索引时,数据库只能一行行扫描所有记录,然后判断该记录是否满足查询条件。
B树索引
对于性别,可取值的范围只有’男’,’女’,并且男和女可能各站该表的50%的数据,这时添加B树索引还是需要取出一半的数据, 因此完全没有必要。相反,如果某个字段的取值范围很广,几乎没有重复,比如身份证号,此时使用B树索引较为合适。事实上,当取出的行数据占用表中大部分的数据时,即使添加了B树索引,数据库如oracle、MySQL也不会使用B树索引,很有可能还是一行行全部扫描。
如果用户查询的列的基数非常的小, 即只有的几个固定值,如性别、婚姻状况、行政区等等。要为这些基数值比较小的列建索引,就需要建立位图索引。
对于性别这个列,位图索引形成两个向量,男向量为10100…,向量的每一位表示该行是否是男,如果是则位1,否为0,同理,女向量位01011。
对于婚姻状况这一列,位图索引生成三个向量,已婚为11000…,未婚为00100…,离婚为00010…。
当我们使用查询语句“select * from table where Gender=‘男’ andMarital=“未婚”;”的时候 首先取出男向量10100…,然后取出未婚向量00100…,将两个向量做and操作,这时生成新向量00100…,可以发现第三位为1,表示该表的第三行数据就是我们需要查询的结果。
上面讲了,位图索引适合只有几个固定值的列,如性别、婚姻状况、行政区等等,而身份证号这种类型不适合用位图索引。
此外,位图索引适合静态数据,而不适合索引频繁更新的列。举个例子,有这样一个字段busy,记录各个机器的繁忙与否,当机器忙碌时,busy为1,当机器不忙碌时,busy为0。
这个时候有人会说使用位图索引,因为busy只有两个值。好,我们使用位图索引索引busy字段!假设用户A使用update更新某个机器的busy值,比如update table set table.busy=1 where rowid=100;,但还没有commit,而用户B也使用update更新另一个机器的busy值,update table set table.busy=1 where rowid=12; 这个时候用户B怎么也更新不了,需要等待用户A commit。
原因
用户A更新了某个机器的busy值为1,会导致所有busy为1的机器的位图向量发生改变,因此数据库会将busy=1的所有行锁定,只有commit之后才解锁。