MySQL如何使用索引
索引用于快速查找具有特定列值的行。没有索引,MySQL必须从第一行开始,然后通读整个表以找到相关的行。桌子越大,花费越多。如果表中有相关列的索引,MySQL可以快速确定要在数据文件中间查找的位置,而不必查看所有数据。这比顺序读取每一行要快得多。
大多数MySQL索引(PRIMARY KEY
, UNIQUE
,INDEX
和 FULLTEXT
)存储在 B树。例外:空间数据类型的索引使用R树;MEMORY
表还支持哈希索引 ; InnoDB
对FULLTEXT
索引使用倒排列表。
通常,如以下讨论中所述使用索引。第8.3.8节“ B树和哈希索引的比较”MEMORY
中介绍了哈希索引特有的特性(如表中所用 ) 。
MySQL使用索引进行以下操作:
-
WHERE
快速 查找与子句匹配的行。 -
从考虑中消除行。如果可以在多个索引之间进行选择,MySQL通常会使用找到最少行数的索引(最具 选择性的索引)。
-
如果表具有多列索引,则优化器可以使用索引的任何最左前缀来查找行。例如,如果你有一个三列索引上
(col1, col2, col3)
,你有索引的搜索功能(col1)
,(col1, col2)
以及(col1, col2, col3)
。有关更多信息,请参见 第8.3.5节“多列索引”。 -
执行联接时从其他表中检索行。如果声明相同的类型和大小,MySQL可以更有效地在列上使用索引。在这种情况下,
VARCHAR
与CHAR
被认为是相同的,如果它们被声明为相同的大小。例如,VARCHAR(10)
和CHAR(10)
是相同的大小,但是VARCHAR(10)
和CHAR(15)
不是。对于非二进制字符串列之间的比较,两个列应使用相同的字符集。例如,将一
utf8
列与一latin1
列进行比较会排除使用索引。如果无法在不进行转换的情况下直接比较值,则比较不同的列(例如,将字符串列与时间或数字列进行比较)可能会阻止使用索引。对于给定的值,如
1
在数值列,它可能比较等于在字符串列,例如任何数量的值'1'
,' 1'
,'00001'
,或'01.e1'
。这排除了对字符串列使用任何索引的可能性。 -
查找特定索引列的
MIN()
或MAX()
值key_col
。这由预处理器优化,该预处理器检查您是否正在使用 索引中之前出现的所有关键部分。在这种情况下,MySQL对每个表达式或表达式进行一次键查找,并将其替换为常量。如果所有表达式都用常量替换,查询将立即返回。例如:WHERE
key_part_N
=constant
key_col
MIN()
MAX()
SELECT MIN(key_part2),MAX(key_part2) FROM tbl_name WHERE key_part1=10;
-
如果排序或分组是在可用索引的最左前缀(例如)上完成的,则对表进行排序或分组 。如果所有关键部分后面都有,则按相反顺序读取密钥。请参见 第8.2.1.14节“按优化排序”和 第8.2.1.15节“按优化分组”。
ORDER BY
key_part1
,key_part2
DESC
-
在某些情况下,可以优化查询以检索值而无需查询数据行。(为查询提供所有必要结果的索引称为 覆盖索引。)如果查询仅从表中使用某些索引中包含的列,则可以从索引树中检索所选值,以提高速度:
SELECT key_part3 FROM tbl_name WHERE key_part1=1
8.3.2主键优化
为表的主键表示您在最重要的查询中使用的列或列集。它具有关联的索引,可提高查询性能。查询性能可从
NOT NULL
优化中受益,因为它不能包含任何NULL
值。使用InnoDB
存储引擎,可以对表数据进行物理组织,以根据一个或多个主键列进行超快速查找和排序。如果您的表又大又重要,但是没有明显的列或一组列用作主键,则可以创建一个单独的列,并使用自动增量值作为主键。当您使用外键联接表时,这些唯一的ID可用作指向其他表中相应行的指针。
8.3.3外键优化
如果一个表有很多列,并且您查询了许多不同的列组合,将不常用的数据拆分成单独的表(每个表包含几列),然后通过复制数字ID将它们关联回主表可能会比较有效。主表中的列。这样,每个小表都可以具有一个主键来快速查找其数据,并且您可以使用联接操作仅查询所需的一组列。根据相关数据的分布方式,查询可能执行较少的I / O并占用较少的缓存,因为相关的列在磁盘上打包在一起。(为了最大化性能,查询尝试从磁盘读取尽可能少的数据块;
8.3.4列索引
索引的最常见类型涉及单个列,该列将来自该列的值的副本存储在数据结构中,从而允许快速查找具有相应列值的行。B树数据结构可以让索引快速查找特定值,一组值,或值的范围,对应于运营商,如
=
,>
,≤
,BETWEEN
,IN
,等等,一在WHERE
子句。每个存储引擎定义每个表的最大索引数和最大索引长度。请参阅 第14章,InnoDB存储引擎和 第15章,备用存储引擎。所有存储引擎每个表至少支持16个索引,并且总索引长度至少为256个字节。大多数存储引擎都有更高的限制。
有关列索引的更多信息,请参见 第13.1.14节“ CREATE INDEX语法”。
使用 字符串列的索引规范中的语法,您可以创建仅使用列首字符的索引 。以这种方式仅索引列值的前缀可以使索引文件小得多。在为a 或 column 编制索引时 , 必须为索引指定前缀长度。例如:
col_name
(N
)N
BLOB
TEXT
CREATE TABLE test (blob_col BLOB, INDEX(blob_col(10)));
前缀最长可以为1000个字节(
InnoDB
表中为767个字节 ,除非已innodb_large_prefix
设置)。注意前缀限制以字节为单位,而在前缀长度
CREATE TABLE
,ALTER TABLE
和CREATE INDEX
语句被解释为非二进制串类型的字符数(CHAR
,VARCHAR
,TEXT
二进制串类型()和字节数BINARY
,VARBINARY
,BLOB
)。为使用多字节字符集的非二进制字符串列指定前缀长度时,请考虑到这一点。如果搜索词超过索引前缀长度,则使用索引排除不匹配的行,然后检查其余行是否可能匹配。
有关索引前缀的更多信息,请参见 第13.1.14节“ CREATE INDEX语法”。
FULLTEXT
索引用于全文搜索。只有InnoDB
和MyISAM
存储引擎支持FULLTEXT
索引和仅适用于CHAR
,VARCHAR
和TEXT
列。索引始终在整个列上进行,并且不支持列前缀索引。有关详细信息,请参见 第12.9节“全文搜索功能”。优化适用于
FULLTEXT
针对单个InnoDB
表的某些类型的 查询 。具有以下特征的查询特别有效:-
FULLTEXT
仅返回文档ID或文档ID和搜索等级的查询。 -
FULLTEXT
查询以分数的降序对匹配行进行排序,并应用一个LIMIT
子句以获取前N个匹配行。为了应用此优化,必须没有WHERE
子句,只有一个ORDER BY
子句按降序排列。 -
FULLTEXT
仅检索COUNT(*)
与搜索词匹配的行的 值的查询,没有其他WHERE
子句。将WHERE
子句编码为 ,没有任何比较运算符。WHERE MATCH(
text
) AGAINST ('other_text
')> 0
对于包含全文表达式的查询,MySQL在查询执行的优化阶段评估这些表达式。优化器不仅查看全文表达式并进行估计,而且还在制定执行计划的过程中对它们进行评估。
这种行为的含义是,
EXPLAIN
对于全文查询,通常比在优化阶段未进行任何表达式求值的非全文查询要慢。EXPLAIN
由于优化期间发生匹配Select tables optimized away
,因此全文查询可能会显示在该Extra
列中;在这种情况下,以后执行期间无需进行表访问。该
MEMORY
存储引擎使用HASH
默认的索引,而且还支持BTREE
索引。8.3.5多列索引
MySQL可以创建复合索引(即,多列上的索引)。一个索引最多可以包含16列。对于某些数据类型,您可以为列的前缀建立索引(请参见 第8.3.4节“列索引”)。
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
指数是在一个索引last_name
和first_name
列。该索引可用于查询中的查找,这些查询指定在已知范围内的last_name
和first_name
值组合的 值。它也可以用于仅指定last_name
值的查询, 因为该列是索引的最左前缀(如本节稍后所述)。因此,该name
索引用于以下查询中的查找:SELECT * FROM test WHERE last_name='Jones'; SELECT * FROM test WHERE last_name='Jones' AND first_name='John'; SELECT * FROM test WHERE last_name='Jones' AND (first_name='John' OR first_name='Jon'); SELECT * FROM test WHERE last_name='Jones' AND first_name >='M' AND first_name < 'N';
但是,在以下查询中,
name
索引 不用于查找:SELECT * FROM test WHERE first_name='John'; SELECT * FROM test WHERE last_name='Jones' OR first_name='John';
假设您发出以下
SELECT
语句:SELECT * FROM tbl_name WHERE col1=val1 AND col2=val2;
如果
col1
和上 存在多列索引col2
,则可以直接获取相应的行。如果col1
和上存在单独的单列索引col2
,那么优化器将尝试使用索引合并优化(请参见 第8.2.1.3节“索引合并优化”),或者尝试通过确定哪个索引排除更多行并使用来查找限制性最强的索引。该索引以获取行。如果表具有多列索引,则优化器可以使用索引的任何最左前缀来查找行。例如,如果你有一个三列索引上
(col1, col2, col3)
,你有索引的搜索功能(col1)
,(col1, col2)
以及(col1, col2, col3)
。如果列不构成索引的最左前缀,则MySQL无法使用索引执行查找。假设您具有以下
SELECT
所示的语句:SELECT * FROM tbl_name WHERE col1=val1; SELECT * FROM tbl_name WHERE col1=val1 AND col2=val2; SELECT * FROM tbl_name WHERE col2=val2; SELECT * FROM tbl_name WHERE col2=val2 AND col3=val3;
如果存在一个索引
(col1, col2, col3)
,则仅前两个查询使用该索引。第三个查询和第四个查询确实涉及索引列,但是不使用索引来执行查找,因为(col2)
并且(col2, col3)
不是的最左前缀(col1, col2, col3)
。8.3.6验证索引使用情况
始终检查所有查询是否真的使用您在表中创建的索引。使用该
EXPLAIN
语句,如第8.8.1节“使用EXPLAIN优化查询”中所述。8.3.7 InnoDB和MyISAM索引统计信息收集
存储引擎收集有关表的统计信息,以供优化器使用。表统计信息基于值组,其中值组是一组具有相同键前缀值的行。出于优化目的,重要的统计数据是平均值组的大小。
MySQL通过以下方式使用平均值组大小:
-
估计每次
ref
访问 必须读取多少行 -
估计部分联接将产生多少行;也就是说,这种形式的操作将产生的行数:
(...) JOIN tbl_name ON tbl_name.key = expr
随着索引的平均值组大小的增加,该索引在这两个用途中的作用较小,因为每次查找的平均行数增加:为了使索引更好地用于优化目的,最好将每个索引值作为目标表中的行数。当给定的索引值产生大量的行时,该索引的作用较小,而MySQL不太可能使用它。
平均值组的大小与表基数有关,表基数是值组的数目。该
SHOW INDEX
语句显示基于的基数值N/S
,其中N
是表中的行数,并且S
是平均值组的大小。该比率在表中产生大约数量的值组。为联接基础上,
<=>
比较运营商,NULL
没有从任何其它值区别对待:NULL <=> NULL
,就像任何其他 。N
<=>N
N
但是,对于基于
=
运算符的联接,NULL
它与非NULL
值是不同的: 当或 (或两者)均为 时不为真 。这会影响 以下形式的比较访问:如果的当前值是,MySQL将不会访问表 ,因为比较不能为真。expr1
=expr2
expr1
expr2
NULL
ref
tbl_name.key
=expr
expr
NULL
为了
=
进行比较,NULL
表中有多少个值都没有关系。为了优化目的,相关值是非NULL
值组的平均大小。但是,MySQL当前不支持收集或使用该平均大小。对于
InnoDB
和MyISAM
表,分别可以通过innodb_stats_method
和myisam_stats_method
系统变量来控制对表统计信息的收集 。这些变量具有三个可能的值,其区别如下:-
当变量设置为时
nulls_equal
,所有NULL
值都被视为相同(也就是说,它们全部形成一个值组)。如果
NULL
值组的大小比平均非NULL
值组的大小大得多,则此方法会使平均值组的大小向上倾斜。这使得索引在优化器中似乎没有那么有用,而对于查找非NULL
值的联接而言,索引的作用实际上不那么有用。因此,该nulls_equal
方法可能导致优化器ref
在应该使用索引时不使用索引 。 -
当变量设置为时
nulls_unequal
,NULL
值将被认为是不同的。而是,每个NULL
值构成一个单独的大小为1的值组。如果您有很多
NULL
值,此方法会使平均值组的大小向下倾斜。如果平均非NULL
值组大小很大,则将NULL
每个值作为一组大小1进行计数会导致优化器高估寻找非NULL
值的联接的索引值。因此,当其他方法可能更好时,该nulls_unequal
方法可能会导致优化器将此索引用于ref
查找。 -
将变量设置为时
nulls_ignored
,NULL
将忽略值。
如果您倾向于使用许多使用
<=>
而不是的联接=
,则NULL
值在比较中并不特殊,并且一个NULL
等于另一个。在这种情况下,nulls_equal
是适当的统计方法。该
innodb_stats_method
系统变量具有全局值; 该myisam_stats_method
系统变量有全局和会话值。设置全局值会影响从相应存储引擎收集表的统计信息。设置会话值只会影响当前客户端连接的统计信息收集。这意味着您可以通过设置会话值来强制使用给定的方法重新生成表的统计信息,而不会影响其他客户端myisam_stats_method
。要重新生成
MyISAM
表统计信息,可以使用以下任何一种方法:-
更改表以使其统计信息过时(例如,插入一行然后将其删除),然后设置
myisam_stats_method
并发出一条ANALYZE TABLE
语句
关于使用的一些注意事项
innodb_stats_method
和myisam_stats_method
:-
如前所述,您可以强制显式收集表统计信息。但是,MySQL可能还会自动收集统计信息。例如,如果在执行表语句的过程中,其中一些语句修改了表,则MySQL可能会收集统计信息。(例如,这可能发生在批量插入或删除操作或某些
ALTER TABLE
语句中。)如果发生这种情况,则使用任何值innodb_stats_method
或myisam_stats_method
当时有。因此,如果您使用一种方法收集统计信息,但是稍后又自动收集表的统计信息时,系统变量设置为另一种方法,则将使用另一种方法。 -
无法确定使用哪种方法为给定表生成统计信息。
-
这些变量仅适用于
InnoDB
和MyISAM
表。其他存储引擎只有一种收集表统计信息的方法。通常它更接近该nulls_equal
方法。
8.3.8 B树和哈希索引的比较
了解B树和哈希数据结构可以帮助预测不同查询如何对在索引中使用这些数据结构的不同存储引擎执行不同的查询,尤其是对于
MEMORY
允许您选择B树或哈希索引的存储引擎。A B树索引可以在使用表达式中使用的对列的比较
=
,>
,>=
,<
,<=
,或BETWEEN
运营商。LIKE
如果to的参数LIKE
是不以通配符开头的常量字符串,则索引也可以用于比较 。例如,以下SELECT
语句使用索引:SELECT * FROM tbl_name WHERE key_col LIKE 'Patrick%'; SELECT * FROM tbl_name WHERE key_col LIKE 'Pat%_ck%';
在第一条语句,只用行被认为是。在第二条语句,只用行被认为是。
'Patrick' <=
key_col
< 'Patricl''Pat' <=
key_col
< 'Pau'以下
SELECT
语句不使用索引:SELECT * FROM tbl_name WHERE key_col LIKE '%Patrick%'; SELECT * FROM tbl_name WHERE key_col LIKE other_col;
在第一个语句中,该
LIKE
值以通配符开头。在第二条语句中,该LIKE
值不是常数。如果您使用且 长度超过三个字符,MySQL将使用Turbo Boyer-Moore算法来初始化字符串的模式,然后使用该模式来更快地执行搜索。
... LIKE '%
string
%'string
如果使用
索引,则 使用的搜索会使用col_name
IS NULLcol_name
索引。没有覆盖子句中所有
AND
级别的 任何索引都WHERE
不会用于优化查询。换句话说,为了能够使用索引,必须在每个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仍会使用索引,因为它可以更快地找到索引。几行返回结果。哈希索引与刚刚讨论的索引具有一些不同的特征:
-
它们仅用于使用
=
or<=>
运算符的相等比较 (但非常快)。它们不用于比较运算符,例如<
查找值范围的运算符 。依赖于这种单值查找类型的系统称为“ 键值存储 ”;要将MySQL用于此类应用程序,请尽可能使用哈希索引。 -
优化器无法使用哈希索引来加快
ORDER BY
操作速度 。(此索引类型不能用于按顺序搜索下一个条目。) -
MySQL无法确定两个值之间大约有多少行(范围优化器使用它来决定要使用哪个索引)。如果你改变这可能会影响一些查询
MyISAM
或InnoDB
表散列索引MEMORY
表。 -
仅整个键可用于搜索行。(对于B树索引,键的任何最左边的前缀都可用于查找行。)
8.3.9索引扩展的使用
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)
。它还k_d
在列上定义了辅助索引(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'
执行计划取决于是否使用扩展索引。
当优化器不考虑索引扩展时,它会将索引
k_d
视为only(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个字节去,表明键查找中使用的列d
和i1
,而不仅仅是d
。 -
该
ref
从价值变动const
到const,const
,因为键查找使用两个关键部分,没有之一。 -
的
rows
计数降低从5到1,表明InnoDB
应该需要检查更少的行,以产生结果。 -
该
Extra
值从变化Using where; Using index
到Using index
。这意味着可以仅使用索引来读取行,而无需查阅数据行中的列。
使用扩展索引的优化器行为也可以通过以下方式看到
SHOW STATUS
:FLUSH TABLE t1; FLUSH STATUS; SELECT COUNT(*) FROM t1 WHERE i1 = 3 AND d = '2000-01-01'; SHOW STATUS LIKE 'handler_read%'
前面的语句包括
FLUSH TABLES
和,FLUSH STATUS
以刷新表缓存并清除状态计数器。没有索引扩展名,将
SHOW STATUS
产生以下结果:+-----------------------+-------+ | Variable_name | Value | +-----------------------+-------+ | Handler_read_first | 0 | | Handler_read_key | 1 | | Handler_read_last | 0 | | Handler_read_next | 5 | | Handler_read_prev | 0 | | Handler_read_rnd | 0 | | Handler_read_rnd_next | 0 | +-----------------------+-------+
使用索引扩展,可
SHOW STATUS
产生此结果。该Handler_read_next
值从5减少到1,表示可以更有效地使用索引:+-----------------------+-------+ | Variable_name | Value | +-----------------------+-------+ | Handler_read_first | 0 | | Handler_read_key | 1 | | Handler_read_last | 0 | | Handler_read_next | 1 | | Handler_read_prev | 0 | | Handler_read_rnd | 0 | | Handler_read_rnd_next | 0 | +-----------------------+-------+
系统变量 的
use_index_extensions
标志optimizer_switch
允许控制在确定如何使用InnoDB
表的二级索引时优化器是否考虑主键列 。默认情况下use_index_extensions
启用。要检查禁用索引扩展的使用是否可以提高性能,请使用以下语句:SET optimizer_switch = 'use_index_extensions=off';
优化器对索引扩展的使用受制于对索引中关键部分的数量(16)和最大密钥长度(3072字节)的通常限制。
8.3.10优化器对生成的列索引的使用
MySQL支持在生成的列上建立索引。例如:
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
条款是指一些索引生成列的定义相匹配的表达式。以下查询未直接引用,gc
但使用与以下定义匹配的表达式gc
:SELECT * FROM t1 WHERE f1 + 1 > 9;
优化器认识到表达式
f1 + 1
与的定义匹配gc
并且gc
被索引,因此它在执行计划构建期间会考虑该索引。您可以使用EXPLAIN
以下命令查看 :mysql> EXPLAIN SELECT * FROM t1 WHERE f1 + 1 > 9\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: t1 partitions: NULL type: range possible_keys: gc key: gc key_len: 5 ref: NULL rows: 1 filtered: 100.00 Extra: Using index condition
实际上,优化器已将表达式替换为与表达式
f1 + 1
匹配的已生成列的名称。在EXPLAIN
由SHOW WARNINGS
以下命令显示的扩展信息中可用的重写查询中也很明显:mysql> SHOW WARNINGS\G *************************** 1. row *************************** Level: Note Code: 1003 Message: /* select#1 */ select `test`.`t1`.`f1` AS `f1`,`test`.`t1`.`gc` AS `gc` from `test`.`t1` where (`test`.`t1`.`gc` > 9)
以下限制和条件适用于优化器对生成的列索引的使用:
-
为了使查询表达式与生成的列定义匹配,该表达式必须相同,并且必须具有相同的结果类型。例如,如果生成的列表达式为
f1 + 1
,则如果查询使用1 + f1
,或者f1 + 1
(整数表达式)与字符串进行比较,则优化器将无法识别匹配项 。 -
优化适用于这些操作符:
=
,<
,<=
,>
,>=
,BETWEEN
,和IN()
。对于
BETWEEN
和 以外的运算符IN()
,可以用匹配的生成列替换任何一个操作数。对于BETWEEN
和IN()
,只有第一个参数可以由匹配的生成的列替换,其他参数必须具有相同的结果类型。BETWEEN
并且IN()
尚不支持涉及JSON值的比较。 -
必须将生成的列定义为至少包含一个函数调用或前一项中提到的运算符之一的表达式。该表达式不能包含对另一列的简单引用。例如,
gc INT AS (f1) STORED
仅由列引用组成,因此gc
不考虑索引on 。 -
为了将字符串与索引生成的列进行比较,索引生成的列会从返回带引号的字符串的JSON函数计算出一个值,
JSON_UNQUOTE()
因此在列定义中需要从函数值中删除多余的引号。(为了将字符串与函数结果直接进行比较,JSON比较器会处理引号删除,但是对于索引查找不会发生这种情况。)例如,与其编写这样的列定义:doc_name TEXT AS (JSON_EXTRACT(jdoc, '$.name')) STORED
像这样写:
doc_name TEXT AS (JSON_UNQUOTE(JSON_EXTRACT(jdoc, '$.name'))) STORED
使用后一个定义,优化器可以为这两个比较检测到匹配:
... WHERE JSON_EXTRACT(jdoc, '$.name') = 'some_string' ... ... WHERE JSON_UNQUOTE(JSON_EXTRACT(jdoc, '$.name')) = 'some_string' ...
如果没有
JSON_UNQUOTE()
在列定义中,则优化器仅针对这些比较中的第一个比较检测到匹配项。 -
如果优化器无法选择所需的索引,则可以使用索引提示来强制优化器做出其他选择。
-
8.3.11从TIMESTAMP列进行索引查找
时间值
TIMESTAMP
作为UTC值存储在 列中,插入到TIMESTAMP
列中或从列中检索的值 在会话时区和UTC之间转换。(这与CONVERT_TZ()
函数执行的转换类型相同 。如果会话时区为UTC,则实际上没有时区转换。)由于诸如夏令时(DST)等本地时区更改的约定,UTC与非UTC时区之间的转换在两个方向上都不是一对一的。不同的UTC值在另一个时区可能不会不同。以下示例显示了不同的UTC值,它们在非UTC时区中变得相同:
mysql> CREATE TABLE tstable (ts TIMESTAMP); mysql> SET time_zone = 'UTC'; -- insert UTC values mysql> INSERT INTO tstable VALUES ('2018-10-28 00:30:00'), ('2018-10-28 01:30:00'); mysql> SELECT ts FROM tstable; +---------------------+ | ts | +---------------------+ | 2018-10-28 00:30:00 | | 2018-10-28 01:30:00 | +---------------------+ mysql> SET time_zone = 'MET'; -- retrieve non-UTC values mysql> SELECT ts FROM tstable; +---------------------+ | ts | +---------------------+ | 2018-10-28 02:30:00 | | 2018-10-28 02:30:00 | +---------------------+
注意要使用诸如
'MET'
或的 命名时区'Europe/Amsterdam'
,必须正确设置时区表。有关说明,请参见 第5.1.12节“ MySQL服务器时区支持”。您会看到两个不同的UTC值在转换
'MET'
为时区时是相同的。对于特定的TIMESTAMP
列查询,此现象可能导致不同的结果 ,具体取决于优化器是否使用索引来执行查询。假设查询使用
WHERE
子句从前面显示的表中选择值,以在该ts
列中搜索 单个特定值,例如用户提供的时间戳文字:SELECT ts FROM tstable WHERE ts = 'literal';
进一步假设查询在以下条件下执行:
-
会话时区不是UTC,并且具有DST偏移。例如:
SET time_zone = 'MET';
-
TIMESTAMP
由于DST偏移 ,该列中存储的唯一UTC值在 会话时区中不是唯一的。(前面显示的示例说明了这种情况的发生。) -
该查询指定了在会话时区中输入DST小时内的搜索值。
在这种情况下,
WHERE
对于未建立索引和建立索引的查找,子句中的比较以 不同的方式发生,并导致不同的结果:-
如果没有索引或优化器无法使用它,则会在会话时区中进行比较。优化器执行表扫描,在其中检索每个
ts
列值,将其从UTC转换为会话时区,然后将其与搜索值(也在会话时区中解释)进行比较:mysql> SELECT ts FROM tstable WHERE ts = '2018-10-28 02:30:00'; +---------------------+ | ts | +---------------------+ | 2018-10-28 02:30:00 | | 2018-10-28 02:30:00 | +---------------------+
由于
ts
已将存储的值转换为会话时区,因此查询有可能返回两个时间戳值,这些时间戳值与UTC值不同,但在会话时区中相等:当时钟改变时,在DST移位之前出现的一个值, DST移位后出现的一个值。 -
如果有可用的索引,则以UTC进行比较。优化器执行索引扫描,首先将搜索值从会话时区转换为UTC,然后将结果与UTC索引条目进行比较:
mysql> ALTER TABLE tstable ADD INDEX (ts); mysql> SELECT ts FROM tstable WHERE ts = '2018-10-28 02:30:00'; +---------------------+ | ts | +---------------------+ | 2018-10-28 02:30:00 | +---------------------+
在这种情况下,(转换后的)搜索值仅与索引条目匹配,并且由于不同存储的UTC值的索引条目也不同,因此搜索值只能匹配其中之一。
由于针对非索引和索引查找的优化器操作不同,因此在每种情况下查询都会产生不同的结果。非索引查找的结果将返回在会话时区中匹配的所有值。索引查找不能这样做:
-
它在仅了解UTC值的存储引擎内执行。
-
对于映射到同一UTC值的两个不同的会话时区值,索引查找仅匹配相应的UTC索引条目,并且仅返回单行。
在前面的讨论中,存储在其中的数据集
tstable
恰好由不同的UTC值组成。在这种情况下,所示形式的所有使用索引的查询最多匹配一个索引条目。如果索引不是
UNIQUE
,则表(和索引)可以存储给定UTC值的多个实例。例如,该ts
列可能包含UTC value的多个实例'2018-10-28 00:30:00'
。在这种情况下,使用索引的查询将返回它们中的每一个(转换为'2018-10-28 02:30:00'
结果集中的MET值)。仍然使用索引的查询将转换后的搜索值与UTC索引条目中的单个值进行匹配,而不是将在会话时区中转换为搜索值的多个UTC值进行匹配。如果返回
ts
在会话时区中匹配的所有值很重要,则解决方法是禁止使用带有IGNORE INDEX
提示的索引:mysql> SELECT ts FROM tstable IGNORE INDEX (ts) WHERE ts = '2018-10-28 02:30:00'; +---------------------+ | ts | +---------------------+ | 2018-10-28 02:30:00 | | 2018-10-28 02:30:00 | +---------------------+
在其他情况下,例如使用
FROM_UNIXTIME()
和UNIX_TIMESTAMP()
功能执行的转换,在两个方向上也存在相同的缺少时空转换的一对一映射 。请参见 第12.7节“日期和时间函数”。 -
-