首页 > 数据库 > > 8.3 mysql 优化器和索引
8.3 优化器和索引
提高SELECT操作性能的最佳方法是在查询中测试的一个或多个列上创建索引。 索引条目就像指向表行的指针,允许查询快速确定哪些行与WHERE子句中的条件相匹配,并检索这些行的其他列值。 所有的MySQL数据类型都可以被索引。
尽管为查询中使用的每个可能的列创建索引是很诱人的,但是不必要的索引会浪费空间并浪费时间来确定使用哪个索引。 索引还会增加插入,更新和删除的成本,因为每个索引都必须更新。 您必须找到适当的平衡,以使用最佳索引集合实现快速查询。
8.3.1 MYSQL 如何使用索引
索引用于快速查找具有特定列值的行。 如果没有索引,MySQL必须从第一行开始,然后通读整个表来查找相关的行。表越大,成本越高。 如果表中有相关列的索引,MySQL可以快速确定在数据文件中间寻找的位置,而无需查看所有数据。 这比按顺序读取每一行要快得多。
大多数MySQL索引(PRIMARY KEY,UNIQUE,INDEX和FULLTEXT)都存储在B树中。 异常:空间数据类型的索引使用R树; MEMORY表也支持散列索引; InnoDB对FULLTEXT索引使用倒排列表。
MySQL为这些操作使用索引:
为了在非二进制字符串列之间进行比较,两列应该使用相同的字符集。例如,比较utf8列和latin1列不允许使用索引。
比较不同的列(例如,比较字符串列与时间或数字列)可能会阻止使用索引,如果无法直接比较值而不进行转换。 对于数字列中的给定值(例如1),它可能与字符串列中的任何数量的值(例如“1”,“1”,“00001”或“01 .e1”)比较相等。 这排除了字符串列的任何索引的使用。
--mysql 隐式转换字符串到数值
SELECT MIN(key_part2),MAX(key_part2)
FROM tbl_name WHERE key_part1=10;
--这就是之前讲的order by 优化和 group by 优化
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树数据结构使索引能够在WHERE子句中(例如=,>,≤,BETWEEN,IN等) 对应的特定值,一组值或一系列值进行快速的查找.
每个表的索引的最大数目和最大索引长度是根据存储引擎定义的。
所有存储引擎每个表至少支持16个索引,总索引长度至少为256个字节。 大多数存储引擎有更高的限制。
前缀索引
使用索引规范中的col_name(N)语法,可以创建仅使用字符串列的前N个字符的索引。 以这种方式只索引列值的前缀可以使索引文件小得多。 索引BLOB或TEXT列时,必须为索引指定前缀长度。 例如:
CREATE TABLE test (blob_col BLOB, INDEX(blob_col(10)));
前缀长度可以达到1000字节(InnoDB表格为767字节,除非你设置了innodb_large_prefix)。
--具体确定一个合适的索引字段长处,也就是说没必要对列上整个都索引起来。这个长度的大小和 cardinality有关,可以通过 SHOW INDEX FROM 看到。随着数据变化,之前合适的索引可能后面又需要调整了。
注意:
前缀限制以字节为单位,而CREATE TABLE语句中的前缀长度则被解释为字符数。 在为使用多字节字符集的列指定前缀长度时要考虑到这一点。
FULLTEXT 索引
您也可以创建FULLTEXT索引。 这些用于全文搜索。 只有InnoDB和MyISAM存储引擎支持FULLTEXT索引,并且仅支持CHAR,VARCHAR和TEXT列。索引始终在整列上进行,列前缀索引不受支持。
优化适用于针对单个InnoDB表的某些种类的FULLTEXT查询。 查询具有这些特征是特别有效的:
空间索引
您也可以在空间数据类型上创建索引。 目前,MyISAM和(从MySQL 5.7.5开始)InnoDB支持空间类型的R-tree索引。 其他存储引擎使用B树来索引空间类型(除了ARCHIVE,它不支持空间类型索引)。
MEMORY 存储引擎中的索引
MEMORY存储引擎默认使用HASH索引,但也支持BTREE索引。
8.3.5 复合索引(Mutiple-column Indexes)
MySQL可以创建复合索引(即多列索引)。 索引最多可以包含16列。 对于某些数据类型,您可以使用前缀索引。
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='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语句:
SELECT * FROM tbl_name
WHERE col1=val1 AND col2=val2;
如果col1和col2上存在组合索引,则可以直接获取相应的行。 如果在col1和col2上存在不同的单列索引,优化程序将尝试使用索引合并优化,或者通过确定哪个索引排除更多行并使用该索引来提取行来尝试查找最具限制性的索引。
如果表具有复合索引,则优化器可以使用该索引的最左边的前缀来查找行。 例如,如果在(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.7 INnoDB 和MYISAM 索引 统计信息收集
存储引擎收集关于优化器使用的表的统计信息。 表统计信息基于value group,其中value group 是具有相同关键字前缀值的一组行。 为了优化器的目的,一个重要的统计是平均value group 的大小。
MySQL通过以下方式使用平均value group 大小:
(...) JOIN tbl_name ON tbl_name.key = expr
随着索引的平均值组大小增加,索引对于这两个目的的用处不大,因为每次查找的平均行数增加:为了索引对于优化目的是有利的,最好的每个索引值的目标是缩小 表中的行数。 当给定的索引值产生大量的行时,索引不太有用,MySQL不太可能使用它。
平均value group 大小与表基数有关,即表value group 的数目。 SHOW INDEX语句显示基于N / S的基数值,其中N是表中的行数,S是平均value group 大小。 该比率在表格中产生近似的value group 数量。
对于基于<=>比较运算符的连接,NULL与其他值的处理方式不同:NULL <=> NULL,就像其他N的N <=> N一样。
但是,对于基于=操作符的连接,NULL与非NULL值不同:当expr1或expr2(或两者)都为NULL时,expr1 = expr2不为真。 这会影响ref的访问,以便比较表单tbl_name.key = expr:如果expr的当前值为NULL,则MySQL将不访问表,因为比较结果不能为true
对于=比较,表中有多少个NULL值无关紧要。 为了优化目的,相关值是非空value group 的平均大小。 但是,MySQL目前不支持收集或使用平均大小。
对于InnoDB和MyISAM表,您可以通过innodb_stats_method和myisam_stats_method系统变量分别控制表统计信息的收集。 这些变量有三个可能的值,其不同之处如下:
如果NULL value group大小远高于平均非NULL值组大小,则此方法向上倾斜平均值组大小。这使得索引对优化程序而言对于查找非 -NULL值没有帮助。 因此,nulls_equal方法可能会导致优化器不使用索引进行访问。
如果有多个NULL值,则此方法向下倾斜平均值组大小。 如果平均非空值组大小较大,则将NULL值作为一组大小1进行计数会导致优化程序高估查找非空值的连接的索引值。 因此,当其他方法可能更好时,nulls_unequal方法可能会导致优化器使用此索引进行引用查找。
--有点oracle 中直方图的味道
如果您倾向于使用多个使用<=>而不是=的连接,则NULL值在比较中不是特别的,一个NULL等于另一个。 在这种情况下,nulls_equal是适当的统计方法。
innodb_stats_method系统变量具有全局值; myisam_stats_method系统变量具有全局值和会话值。设置全局值会影响相应存储引擎中表的统计信息收集。 设置会话值仅影响当前客户端连接的统计信息收集。 这意味着您可以强制使用给定方法重新生成表的统计信息,而不会影响其他客户端,方法是设置myisam_stats_method的会话值。
要重新生成MyISAM表统计信息,可以使用以下任何一种方法:
有关使用innodb_stats_method和myisam_stats_method的一些注意事项:
8.3.8 B树和哈希索引的比较
了解B树和散列数据结构可以帮助预测不同的查询在使用索引中的这些数据结构的不同存储引擎上如何执行,特别是用于允许您选择B树或散列索引的MEMORY存储引擎。
B-tree 索引的特征
B树索引可用于使用=,>,> =,<,<=或BETWEEN运算符的表达式中的列比较。 如果LIKE的参数是一个不以通配符开头的常量字符串,那么索引也可用于LIKE比较。 例如,以下SELECT语句使用索引:
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语句不使用索引:
SELECT * FROM tbl_name WHERE key_col LIKE '%Patrick%';
SELECT * FROM tbl_name WHERE key_col LIKE other_col;
在第一个语句中,LIKE值以通配符开始。 在第二个语句中,LIKE值不是一个常量。
如果您使用... LIKE'%string%'且字符串长于三个字符,则MySQL使用Turbo Boyer-Moore算法初始化字符串的模式,然后使用此模式更快速地执行搜索。
使用col_name IS NULL 的搜索会使用到 col_name上的索引
没有在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.3.9 优化器使用生成的列索引
MySQL支持生成列上的索引。 例如:CREATE TABLE
CREATE TABLE t1 (f1 INT, gc INT AS (f1 + 1) STORED, INDEX (gc));
生成的列gc被定义为表达式f1 + 1。列也被索引,并且优化器可以在执行计划构建期间考虑该索引。 在下面的查询中,WHERE子句引用gc,优化器考虑该列上的索引是否产生更高效的计划:
SELECT * FROM t1 WHERE gc > 9;
从MySQL 5.7.8开始,优化器可以在生成的列上使用索引来生成执行计划,即使在没有按名称查询这些列的情况下直接引用也是如此。 如果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替换为与表达式匹配的生成列的名称。 在SHOW WARNINGS所显示的扩展EXPLAIN信息中可用的重写查询中,这也很明显:
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)
以下限制和条件适用于优化器使用生成的列索引:
对于除BETWEEN和IN()之外的其他运算符,任何一个操作数都可以被匹配的生成列替换。 对于BETWEEN和IN(),只有第一个参数可以被匹配的生成列替换,其他参数必须具有相同的结果类型。 BETWEEN和IN()还不支持涉及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(),那么优化器只会检测到第一个比较的匹配项。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/69946831/viewspace-3145013/ ,如需转载,请注明出处,否则将追究法律责任。