一、MySQL如何使用索引(index)
1.1 索引概述
索引用于快速查找具有特定列值的行。
如果不使用索引,MySQL必须从表的第一行开始,然后扫描整个表来寻找符合条件的行。这种情况下,表越大,扫描整表的代价就越大,就越低效。
索引是提高SELECT操作性能的最佳方式,MySQL的所有data type也都能建立索引。
但是,过多地创建索引会浪费存储空间,也会浪费时间,因为MySQL会花费一定的时间决定使用哪些索引。
而且,索引还会增加插入、更新和删除的成本,因为在执行这些操作时必须更新每个索引。
由此可知,在设计索引时,我们必须找到正确的平衡,才能使用最优索引集实现快速查询。
大多数MySQL索引(PRIMARY KEY,UNIQUE,INDEX和FULLTEXT)都存储在B树中。 例外:空间数据类型的索引使用R树; MEMORY表也支持哈希索引; InnoDB使用FULLTEXT索引的反转列表。
1.2 MySQL通常会为以下操作使用索引(特别地,MEMORY表中的hash索引的使用会在后面讨论):
- 为了快速查找匹配where子句的行;
- 删除满足条件的行。如果有多个index可供选择,MySQL通常会使用那个找到的结果集最小的index。
- 对于多列索引(Mutil-column index),只有对索引列进行最左的连续组合,才能出发对这个多列索引的使用。
- 在执行join操作时从其它表中取回行时,如果被使用的不同表中的两个列的data type和size都相同,那么MySQL可以更加高效得使用它们的索引。这种情况下,VARCHAR和CHAR如果size一样,则它们被视作同一类型。如varchar(10)和char(10)。
- 在非二进制字符串的字段的比较中,两个列字段应该使用同样的字符集(character set)。例如,比较utf-8的列与Latin1的列会排除掉索引的使用。
- 不同类型的列比较时,例如一个字符串的列与一个时间或者数值列进行比较,如果他们的值在没有经过转换而不能比较,可能会阻止索引的使用。例如,数字1与字符串“1”,“ 1”,“00001” or “01.e1”。这种情况下,不会对String列使用任何索引。
- 查找特定索引列key_col中的MIN()和MAX()。这个拆线呢会由预处理器进行优化,它会检查是否在Where条件子句中使用了索引中key_col之前出现的列索引。
SELECT MIN(key_part2),MAX(key_part2) FROM tbl_name WHERE key_part1=10;
- sort或者group一个表,如果进行排序或者分组的表是某个可用索引的最左前缀。
- 在某些情况下,查询可以被优化为以在不咨询数据行的情况下检索值。
索引对于较小的表没有太多用处,而一个大型表,如果查询操作总要访问大多数的行,那么索引也效果甚微。
事实上,当查询需要访问大多数行时,按序依次访问比通过索引来访问会更快。顺序读取可以最大限度地减少磁盘搜索,即使查询不需要所有行也是如此。
二、主键的优化
主键列通常是在重要查询中会使用的列。它具有关联的索引,以实现快速查询性能。 查询性能受益于NOT NULL优化,因为它不能包含任何NULL值。
使用InnoDB存储引擎,表格数据在物理上进行组织,以根据主键或列进行超快速查找和排序。
如果你的数据表很大而且十分重要,却没有主键或者主键列,你可以创建一个单独的自增数值列,将其作为主键(ID)。这个id也可以在join查询中作为其它表的外键。
三、外键的优化
若一个表有很多列,你需要查询不同列的组合,那么将频率较低的数据拆分为每个都有几列的单表可能会很有效,并通过复制数字ID将它们与主表关联起来。 主表中的列。这样,每个小表都可以有一个主键来快速查找其数据,您可以使用连接操作查询所需的列集。根据数据的分布方式,查询可能执行较少的I/O和占用更少的缓存内存,因为相关列被打包在磁盘上。(为了最大化性能,查询尝试从磁盘读取尽可能少的数据块;只有几列的表可以在每个数据块中容纳更多的行。)
四、Column Indexs
最常见的索引类型涉及单个列,可以在某个数据结构中存储该列的值的副本,允许快速查找具有相应列值的行。
这个给数据结构就是B-tree,它允许索引快速查找到一个特定的值,或者一个值集合,或者某些范围的 值,对应where条件子句中相关的操作符,=,>,<=,between,in等。
不同的存储引擎下,每个表中最大的索引数以及最大的索引长度是不同的。所有的存储引擎每表支持至少16个索引,以及支持的索引总长度至少为256字节。当然多数存储引擎有更大的支持。
更多关于列索引的知识参考下面章节:
13.1.14 “create index syntax”
4.1 指定索引前缀长度(区分前缀索引)
CREATE TABLE test (blob_col BLOB, INDEX(blob_col(10)));
这个语句在建表时对一个String列创建了一个只使用该列的前10个字符的索引。
这种方式可以让索引文件更小。当对BLOB或者TEXT列创建索引时,必须指定前缀长度,这个长度最大可达到1000字节,InnoDB一般是767字节。
4.2 全文索引(FullText index)
FULLTEXT索引用于全文搜索。 只有InnoDB和MyISAM存储引擎支持FULLTEXT索引,并且仅支持CHAR,VARCHAR和TEXT列。
索引始终发生在整个列上,并且不支持指定列索引前缀。 有关详细信息,请参见第12.9节“全文搜索功能”。
优化适用于针对单个InnoDB表的某些类型的FULLTEXT查询。当查询具有以下特征时,特别有效:
- FULLTEXT查询仅返回文档ID,文档ID和搜索排名。
- FULLTEXT查询按分数的降序对匹配的行进行排序,并应用LIMIT子句来获取前N个匹配的行。 要应用此优化,必须没有WHERE子句和只有一个ORDER BY子句按降序排列。
- FULLTEXT查询仅检索与搜索项匹配的行的COUNT(*)值,而不包含其他WHERE子句。 将WHERE子句编码为WHERE MATCH(text)AGAINST('other_text'),不带任何> 0比较运算符。
对于包含全文表达式的查询,MySQL在查询执行的优化阶段评估这些表达式。 优化器不只是查看全文表达式并进行估计,它实际上是在开发执行计划的过程中对它们进行评估。
这种行为的含义是,对于全文查询,EXPLAIN通常比在优化阶段没有进行表达式评估的非全文查询慢。
对于全文查询的EXPLAIN可能会显示由于在优化期间发生匹配而在Extra列中优化的Select表; 在这种情况下,在以后的执行期间不需要访问表。
您可以在空间数据类型上创建索引。 MyISAM和InnoDB支持空间类型的R树索引。 其他存储引擎使用B树来索引空间类型(ARCHIVE除外,它不支持空间类型索引)
4.3 MEMORY存储引擎上的索引
MEMORY存储引擎默认使用HASH索引,但也支持BTREE索引。
五、多列索引
MySQL可以创建复合索引(即多列索引)。 索引最多可包含16列。 对于某些数据类型,您可以指定只使用索引列的前缀长度。
当查询涉及到了多列索引中的全部的列,或者涉及到这些列的最左前n列,MySQL就会使用多列索引。
如果在索引定义中以正确的顺序指定列,则单个复合索引可以加速同一表上的多种查询。
多列索引可以被看成为一个排序数组,它的一行由一个相关索引列的所有值连接起来。
作为复合索引的替代方法,您可以根据其他列的信息引入“散列”列。 如果此列很短,相当独特且已编制索引,则它可能比许多列上的“宽”索引更快。 在MySQL中,使用这个额外的列很容易:
SELECT * FROM tbl_name WHERE hash_col=MD5(CONCAT(val1,val2)) AND col1=val1 AND col2=val2;
多列索引的问题
假设一个表如下:
CREATE TABLE test ( id INT NOT NULL, last_name CHAR(30) NOT NULL, first_name CHAR(30) NOT NULL, PRIMARY KEY (id), INDEX name (last_name,first_name) );
name索引会在以下情况下被使用:
SELECT * FROM test WHERE last_name='Widenius'; SELECT * FROM test WHERE last_name='Widenius' AND first_name='Michael'; SELECT * FROM test WHERE last_name='Widenius' AND (first_name='Michael' OR first_name='Monty'); SELECT * FROM test WHERE last_name='Widenius' AND first_name >='M' AND first_name < 'N';
然而,下面的情况,name索引就不会被使用:
SELECT * FROM test WHERE first_name='Michael'; SELECT * FROM test WHERE last_name='Widenius' OR first_name='Michael';
现在假设需要执行这样一个查询:
SELECT * FROM tbl_name WHERE col1=val1 AND col2=val2;
如果col1和col2上存在多列索引,则可以直接获取相应的行。
如果col1和col2上存在单独的单列索引,优化程序将尝试使用索引合并优化(请参见第8.2.1.3节“索引合并优化”),或尝试通过确定哪个索引排除更多行来查找限制性最强的索引 并使用该索引来获取行。
如果表具有多列索引,则optimizer查找行可以使用索引的任何最左前缀。 例如,如果在(col1,col2,col3)上有三列索引,则在(col1),(col1,col2)和(col1,col2,col3)上编制索引搜索功能。(最左前缀规则)
六、验证索引的使用
要经常检查是,否所有的查询都真正使用了表中创建的索引。
使用EXPLAIN语句,查看8.8.1节中的章节“Optimizing Queries with EXPLAIN”
七、InnoDB和MyISAM索引的统计集合
存储引擎会收集有关表的统计信息以供优化器使用。
表统计信息基于值组,而值组是一组具有“相同键前缀值(the same key prefix value)”的行。 出于优化程序的目的,一个重要的统计数据是平均值组大小( the average value group size.)。
MySQL使用以下方式的平均值组大小:
- 用于估计每次ref访问必须读取行的方式
- 用于估计一个局部连接将产生多少行; 也就是说,此表单的操作将产生的行数:
(...) JOIN tbl_name ON tbl_name.key = expr
随着索引的平均值组大小增加,索引对于这两个目的不太有用,因为每个查找的平均行数增加:
为了使索引有利于优化,最好每个索引值都以表中的少量行为目标。 当给定的索引值产生大量行时,索引不太有用,MySQL不太可能使用它。
平均值组大小与表基数相关,表基数是值组的数量。
SHOW INDEX语句显示基于N / S的基数值,其中N是表中的行数,S是平均值组大小。 该比率产生表中的近似值组数。
八、B-tree索引和Hash索引的比较
了解它们的不同很重要,尤其对于MEMORY这样允许你选择B-tree索引或者hash索引的存储引擎。
8.1 B树索引的特征
B树索引可用于使用=,>,> =,<,<=或BETWEEN运算符的表达式中的列比较。
如果LIKE的参数是不以通配符开头的常量字符串,则索引也可用于LIKE比较。
例如,以下的查询语句可以使用索引:
SELECT * FROM tbl_name WHERE key_col LIKE 'Patrick%'; SELECT * FROM tbl_name WHERE key_col LIKE 'Pat%_ck%';
在第一个语句中,只考虑在 'Patrick'<= key_col <'Patrick' 范围内的行。
在第二个语句中,仅考虑范围在 'Pat'<= key_col <'Pau' 的行。
然而,下面的查询语句则不会使用索引:
SELECT * FROM tbl_name WHERE key_col LIKE '%Patrick%'; SELECT * FROM tbl_name WHERE key_col LIKE other_col;
第一个语句中,like的参数以通配符起头,第二个语句,like的值不是一个常量。
如果您使用“ ... LIKE'%string%' ”且string超过三个字符,则MySQL会使用Turbo Boyer-Moore算法初始化字符串的模式,然后使用此模式更快地执行搜索。
如果一个列col建立了索引,那么查询中使用“col IS NULL”会使用索引。
不跨越WHERE子句中所有AND级别的任何索引不用于优化查询。 换句话说,为了能够使用索引,必须在每个AND组中使用索引的前缀。
以下where子句的情况会使用索引:
... WHERE index_part1=1 AND index_part2=2 AND other_column=3 /* index = 1 OR index = 2 */ ... WHERE index=1 OR A=10 AND index=2 /* optimized like "index_part1='hello'" */ ... WHERE index_part1='hello' AND index_part3=5 /* Can use index on index1 but not on index2 or index3 */ ... WHERE index1=1 AND index2=2 OR index1=3 AND index3=3;
以下where子句的情况不会使用索引:
/* index_part1 is not used */ ... WHERE index_part2=1 AND index_part3=2 /* Index is not used in both parts of the WHERE clause */ ... WHERE index=1 OR A=10 /* No index spans all rows */ ... WHERE index_part1=1 OR index_part2=10
某些情况下,MySQL不会使用索引,即使某个索引是可用的。
发生这种情况的一种情况是,优化器估计使用索引将需要MySQL访问表中非常大比例的行。 (在这种情况下,表扫描可能会快得多,因为它需要较少的搜索。)
但是,如果这样的查询使用LIMIT来仅检索某些行,那么MySQL无论如何都会使用索引,因为它可以更快地找到在结果中返回的几行。
8.2 Hash索引的特征
Hash索引拥有一些不同的特征:
- 它们仅用于使用=或<=>运算符的相等比较(但速度非常快)。它们不用于例如“<”找到一定范围的值的操作符。 依赖于这种单值查找的类型的系统被称为“键值存储”; 要将MySQL用于此类应用程序,请尽可能使用哈希索引。
- 优化器无法使用哈希索引来加速ORDER BY操作。 (此类索引不能用于按顺序搜索下一个条目。)
- MySQL无法确定两个值之间大约有多少行(范围优化器使用它来决定使用哪个索引)。 如果将MyISAM或InnoDB表更改为哈希索引的MEMORY表,则可能会影响某些查询。
- 只有整个键可用于搜索行。 (使用B树索引,键的任何最左边的前缀都可用于查找行。)
九、索引使用的扩展
InnoDB通过将主键列附加到它来自动扩展每个次级索引。
有如下表:
CREATE TABLE t1 ( i1 INT NOT NULL DEFAULT 0, i2 INT NOT NULL DEFAULT 0, d DATE DEFAULT NULL, PRIMARY KEY (i1, i2), INDEX k_d (d) ) ENGINE = InnoDB;
此表定义列(i1,i2)上的主键。 它还在列(d)上定义了二级索引k_d,但InnoDB内部扩展了该索引并将其视为列(d,i1,i2)。
在确定如何以及是否使用该索引时,优化程序会考虑扩展二级索引的主键列。 这可以带来更高效的查询执行计划和更好的性能。
优化器可以使用扩展的二级索引进行ref,range和index_merge索引访问,用于松散索引扫描,用于连接和排序的优化,以及MIN()/ MAX()优化。
下面的例子展示了执行计划是如何被优化器是否使用了扩展的二级索引所影响的。
假设t1被这些行填充:
INSERT INTO t1 VALUES (1, 1, '1998-01-01'), (1, 2, '1999-01-01'), (1, 3, '2000-01-01'), (1, 4, '2001-01-01'), (1, 5, '2002-01-01'), (2, 1, '1998-01-01'), (2, 2, '1999-01-01'), (2, 3, '2000-01-01'), (2, 4, '2001-01-01'), (2, 5, '2002-01-01'), (3, 1, '1998-01-01'), (3, 2, '1999-01-01'), (3, 3, '2000-01-01'), (3, 4, '2001-01-01'), (3, 5, '2002-01-01'), (4, 1, '1998-01-01'), (4, 2, '1999-01-01'), (4, 3, '2000-01-01'), (4, 4, '2001-01-01'), (4, 5, '2002-01-01'), (5, 1, '1998-01-01'), (5, 2, '1999-01-01'), (5, 3, '2000-01-01'), (5, 4, '2001-01-01'), (5, 5, '2002-01-01');
现在考虑这个查询:
EXPLAIN SELECT COUNT(*) FROM t1 WHERE i1 = 3 AND d = '2000-01-01'
在这种情况下,优化器不能使用主键,因为它包含列(i1,i2),查询不引用i2。 相反,优化器可以在(d)上使用辅助索引k_d,执行计划取决于是否使用扩展索引。
当优化器不考虑索引扩展时,它将索引k_d视为仅(d)。 查询的EXPLAIN产生以下结果:
mysql> EXPLAIN SELECT COUNT(*) FROM t1 WHERE i1 = 3 AND d = '2000-01-01'G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: t1 type: ref possible_keys: PRIMARY,k_d key: k_d key_len: 4 ref: const rows: 5 Extra: Using where; Using index
当优化器考虑索引扩展时,它将k_d视为(d,i1,i2).这种情况下,可以使用最索引的最左前缀(d,i1)来获得更好的执行计划。
mysql> EXPLAIN SELECT COUNT(*) FROM t1 WHERE i1 = 3 AND d = '2000-01-01'G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: t1 type: ref possible_keys: PRIMARY,k_d key: k_d key_len: 8 ref: const,const rows: 1 Extra: Using index
在两个例子中,key显示了优化器会使用二级索引k_d,而EXPLAIN输出则展示了使用扩展索引后的改进:
- key_len从4个字节变为8个字节,表示key的查找使用列d和i1,而不仅仅是d。
- ref值从const变为const,const,因为key的查找使用两个key部分,而不是一个。
- 行计数从5减少到1,表明InnoDB应该检查更少的行以产生结果。
- Extra值从using where; using index来using index。 这意味着只能使用索引读取行,而无需查询数据行中的列。
最后,可以设置相关系统变量(use_index_extensions)来控制索引扩展的使用。
十、优化器使用生成的列索引
MySQL支持生成(generated)列的索引。例如:
CREATE TABLE t1 (f1 INT, gc INT AS (f1 + 1) STORED, INDEX (gc));
生成列gc被定义为f1+1,这个列也创建了索引。优化器在构建执行计划时会考虑这个索引。
在以下查询中,WHERE子句引用gc,优化程序会考虑该列上的索引,以确认是否能产生更有效的计划。
SELECT * FROM t1 WHERE gc > 9;
优化器可以使用生成列上的索引来生成执行计划,即使在查询中没有直接引用生成列的名字。如果WHERE,ORDER BY或GROUP BY子句引用与某个有索引的生成列的定义匹配的表达式,则会发生这种情况。如下所示:
SELECT * FROM t1 WHERE f1 + 1 > 9;
对于生成列的索引的使用,有以下限制和条件:
- 查询语句中的表达式如果匹配到了生成列的定义,那它们必须是完全一致的,且有同样的结果类型。例如,如果定义生成列的表达式为f1+1,查询语句中使用的表达式为“1+f1”,或者(f1+1)被用于与String比较,那么优化器则不会认同。
- 优化适用于以下运算符:=,<,<=,>,> =,BETWEEN和IN()。
- 必须将生成的列定义为至少包含函数调用或前一项中提到的运算符之一的表达式。
- ...(还有几条,实在是不想看了)