8.1 优化概述
据库性能取决于数据库级别的几个因素,如表,查询和配置设置。 这些软件构造在硬件级别上产生CPU和I / O操作,您必须尽可能降低和尽可能高效。 在处理数据库性能时,首先学习软件方面的高级规则和准则,然后使用wall-clock时间测量性能。 当你成为专家的时候,你会更多地了解内部发生的事情,并开始测量诸如CPU周期和I / O操作等事情。
典型的用户旨在从现有的软件和硬件配置中获得最佳的数据库性能。 高级用户寻找改进MySQL软件本身的机会,或者开发自己的存储引擎和硬件设备来扩展MySQL生态系统。
在数据库级别优化
建立数据库应用程序快速的最重要的因素是其基本设计:表格结构是否正确? 特别是,列是否具有正确的数据类型,每个表是否都有适合于工作类型的列? 例如,执行频繁更新的应用程序通常包含列数很少的许多表,而分析大量数据的应用程序通常只有很少的表,列数很多。
是否有正确的索引来提高查询的效率?
您是否为每个表使用适当的存储引擎,并利用您使用的每个存储引擎的优势和特性? 具体而言,诸如InnoDB之类的事务性存储引擎或诸如MyISAM之类的非事务性存储引擎的选择对于性能和可伸缩性可能是非常重要的。
注意:
在MySQL 5.5和更高版本中,InnoDB是新表的默认存储引擎。 在实践中,InnoDB的高级性能特征意味着InnoDB表经常优于简单的MyISAM表,特别是对于繁忙的数据库。
每个表是否使用适当的行格式? 这个选择也取决于表中使用的存储引擎。 特别是,压缩表使用较少的磁盘空间,因此需要较少的磁盘I/O来读取和写入数据。 压缩可用于InnoDB表的各种工作负载,以及只读的MyISAM表。
应用程序是否使用适当的锁定策略? 例如,通过允许共享访问,以便数据库操作可以并发运行,并在适当的时候请求独占访问,从而使关键操作成为最高优先级。 同样,存储引擎的选择也是非常重要的。InnoDB存储引擎可以处理大多数锁定问题,无需您的参与,从而实现更好的数据库并发性,并减少代码的实验和调优。
所有用于缓存大小的内存区域是否正确? 也就是说,足够大以容纳经常访问的数据,但不会太大以至于超载物理内存并导致分页。 要配置的主要内存区域是InnoDB缓冲池,MyISAM密钥缓存和MySQL查询缓存。
在硬件级别优化
随着数据库变得越来越繁忙,任何数据库应用程序最终都会遇到硬件限制.DBA必须评估是否可以调整应用程序或重新配置服务器以避免这些瓶颈,或者是否需要更多的硬件资源。 系统瓶颈通常来自这些来源:磁盘寻找。 磁盘需要一段时间才能找到一块数据。 对于现代磁盘,平均时间通常低于10ms,所以理论上大概有100seek 每秒。 这次用新磁盘缓慢地改进,很难为单个表进行优化。 优化查找时间的方法是将数据分配到多个磁盘上。
磁盘读取和写入。 当磁盘位于正确的位置时,我们需要读取或写入数据。 使用现代磁盘,一个磁盘至少可以提供10-20MB / s的吞吐量。 这比寻求更容易优化,因为您可以从多个磁盘并行读取。
CPU周期。 当数据在主存中时,我们必须处理它以得到我们的结果。 与内存大小相比,大表是最常见的限制因素。 但是用小表,速度通常不是问题。
内存带宽。 当CPU需要的数据量超过CPU缓存容量时,主内存带宽成为瓶颈。 对大多数系统来说,这是不是一个寻常的瓶颈,但需要注意的。
平衡可移植性和性能
要在便携式MySQL程序中使用面向性能的SQL扩展,可以将特定于MySQL的关键字封装在/ *! * /注释分隔符。 其他SQL服务器忽略注释的关键字。
8.2 优化sql 语句
数据库应用程序的核心逻辑是通过SQL语句执行的,无论是直接通过解释器发布,还是通过API在幕后提交。 本节中的调整准则有助于加速各种MySQL应用程序。 这些指导原则涵盖了读取和写入数据的SQL操作,一般SQL操作的幕后开销以及特定场景(如数据库监视)中使用的操作。
1.优化 SELECT 语句
查询以SELECT语句的形式执行数据库中的所有查找操作。 调整这些语句是重中之重,无论是实现动态网页的亚秒级响应时间还是缩短产生大量隔夜报告的时间。
除SELECT语句外,查询的调整技术也适用于构造,如DELATE语句中的CREATE TABLE ... AS SELECT,INSERT INTO ... SELECT和WHERE子句。 这些语句具有额外的性能考虑,因为它们将写操作与面向读取的查询操作结合在一起。
Select 语句的速度
优化查询的主要考虑因素是:要使SELECT ... WHERE查询更快,首先要检查的是是否可以添加索引。 为WHERE子句中使用的列设置索引,以加快评估,筛选和最终检索结果。 为了避免浪费磁盘空间,构建一小组索引,以加快应用程序中使用的许多相关查询。
对于引用不同表的查询,索引是特别重要的,使用joins和foreign keys等特性。 您可以使用EXPLAIN语句来确定哪些索引用于SELECT。隔离并调整查询的任何部分,如函数调用,这需要花费过多的时间。 根据查询的结构,可以为结果集中的每一行只调用一次函数,甚至可以为表中的每一行只调用一次函数,从而大大放大效率。
尽量减少查询中全表扫描的次数,特别是对于大表。
通过定期使用ANALYZE TABLE语句保持表的统计信息是最新的,所以优化器具有构建高效执行计划所需的信息。
了解针对每个表的存储引擎特定的调整技术,索引技术和配置参数。 InnoDB和MyISAM都有一套指导方针,可以在查询中保持高性能
您可以使用第8.5.3节“优化InnoDB只读事务”中的技术来优化InnoDB表的单个查询事务。
避免以难以理解的方式转换查询,特别是如果优化器自动执行一些相同的转换。
如果性能问题不能通过基本准则之一轻易解决,请通过阅读EXPLAIN计划并调整索引,WHERE子句,连接子句等来调查特定查询的内部详细信息。 (当您达到一定水平的专业知识时,阅读EXPLAIN计划可能是您每个查询的第一步。)
调整MySQL用于缓存的内存区域的大小和属性。 通过高效地使用InnoDB缓冲池,MyISAM密钥缓存和MySQL查询缓存,重复查询的运行速度更快,因为第二次和以后的时间内都会从内存中检索结果。
即使对于使用高速缓存内存区域快速运行的查询,您仍然可以进一步优化,以使它们所需的缓存内存更少,从而使您的应用程序具有更高的可扩展性。 可伸缩性意味着您的应用程序可以同时处理更多的用户,更大的请求等,而不会出现大幅下降的性能。
处理锁定问题,其中查询速度可能会受到同时访问表的其他会话影响。
MySQL 优化器如何优化WHERE子句
本节讨论可以处理WHERE子句的优化。 这些示例使用SELECT语句,但是相同的优化适用于DELETE和UPDATE语句中的WHERE子句。
注意:
因为MySQL优化器的工作正在进行,所以并不是所有MySQL执行的优化都记录在这里。
您可能会试图重写您的查询来加快算术运算,同时牺牲可读性。 由于MySQL会自动执行类似的优化,所以通常可以避免这种工作,并使查询更易于理解和维护。 MySQL执行的一些优化如下:删除不必要的括号:
((a AND b) AND c OR (((a AND b) AND (c AND d))))
-> (a AND b AND c) OR (a AND b AND c AND d)
持续折叠:
(a
-> b>5 AND b=c AND a=5
恒定的条件去除(因为不断的折叠):
(B>=5 AND B=5) OR (B=6 AND 5=5) OR (B=7 AND 5=6)
-> B=5 OR B=6
被索引使用的常量表达式只被计算一次。
直接从MyISAM和MEMORY表的表信息中检索没有WHERE的单个表上的COUNT(*)。 当仅与一个表一起使用时,这也适用于任何NOT NULL表达式。
尽早检测无效的常量表达式。 MySQL快速检测到一些SELECT语句是不可能的,并且不返回任何行。
如果不使用GROUP BY或集合函数(COUNT(),MIN()等),则HAVING与WHERE合并。
对于连接中的每个表,构造一个更简单的WHERE,以获得对表的快速WHERE评估,并尽可能快地跳过行。
在查询中的任何其他表之前首先读取所有常量表。 常数表是以下任一项:空表或者只有一行的表
与PRIMARY KEY或UNIQUE索引中的WHERE子句一起使用的表,其中所有索引部分都与常量表达式进行比较,并被定义为NOT NULL。
以下所有表均用作常量表:
SELECT * FROM t WHERE primary_key=1;
SELECT * FROM t1,t2
WHERE t1.primary_key=1 AND t2.primary_key=t1.id;
尝试所有可能性,找到加入表的最佳连接组合。 如果ORDER BY和GROUP BY子句中的所有列都来自同一个表,则在加入时首先选择该表。
如果存在ORDER BY子句和不同的GROUP BY子句,或者ORDER BY或GROUP BY包含来自联接队列中第一个表以外的表的列,则会创建一个临时表。
如果使用SQL_SMALL_RESULT选项,MySQL将使用内存中的临时表。
如果使用SQL_SMALL_RESULT选项,则MySQL使用内存中的每个表索引都将被查询,并使用最佳索引,除非优化器认为使用表扫描更高效。 以前,根据最佳索引是否超过了表格的30%来使用扫描,但是固定百分比不再决定使用索引或扫描之间的选择。 优化器现在更加复杂,并且根据附加因素(如表大小,行数和I / O块大小)进行估计。
在某些情况下,MySQL甚至可以从索引中读取行,而无需咨询数据文件。 如果索引中使用的所有列都是数字,则仅使用索引树来解析查询
在输出每行之前,跳过与HAVING子句不匹配的行。
查询速度非常快的一些示例:
SELECT COUNT(*) FROM tbl_name;
SELECT MIN(key_part1),MAX(key_part1) FROM tbl_name;
SELECT MAX(key_part2) FROM tbl_name
WHERE key_part1=constant;
SELECT ... FROM tbl_name
ORDER BY key_part1,key_part2,... LIMIT 10;
SELECT ... FROM tbl_name
ORDER BY key_part1 DESC, key_part2 DESC, ... LIMIT 10;
MySQL仅使用索引树来解析以下查询,假定索引列是数字型的:
SELECT key_part1,key_part2 FROM tbl_name WHERE key_part1=val;
SELECT COUNT(*) FROM tbl_name
WHERE key_part1=val1 AND key_part2=val2;
SELECT key_part2 FROM tbl_name GROUP BY key_part1;
以下查询使用索引来按照排序顺序检索行,而无需单独的排序传递:
SELECT ... FROM tbl_name
ORDER BY key_part1,key_part2,... ;
SELECT ... FROM tbl_name
ORDER BY key_part1 DESC, key_part2 DESC, ... ;
范围优化
范围访问方法使用单个索引来检索包含在一个或多个索引值区间内的表行的子集。 它可以用于单部分或多部分索引。 以下部分将介绍优化程序使用范围访问的条件。
Single-Part索引的范围访问方法
对于单部分索引,索引值区间可以用WHERE子句中的相应条件方便地表示,所以我们说的是范围条件而不是“间隔”。
单个部分索引的范围条件定义如下:
对于BTREE和HASH索引,使用=,<=>,IN(),IS NULL或IS NOT NULL运算符时,关键部分与常量值的比较是一个范围条件。
另外,对于BTREE索引关键部分的比较是范围条件,当使用>,<=>,<=,BETWEEN,!=或<>运算符,或LIKE比较如果LIKE的参数是具有常数值的不以通配符开始的常量字符串时 。
对于所有类型的指标,多个范围条件结合OR或AND形成一个范围条件。
前面描述中的“常量”是指以下之一:来自查询字符串的常量
来自同一个连接的const或系统表的列
一个不相关的子查询的结果集
任何表达式完全由前面的类型的子表达式组成
以下是在WHERE子句中使用范围条件进行查询的一些示例:
SELECT * FROM t1
WHERE key_col > 1
AND key_col < 10;
SELECT * FROM t1
WHERE key_col = 1
OR key_col IN (15,18,20);
SELECT * FROM t1
WHERE key_col LIKE 'ab%'
OR key_colBETWEEN 'bar' AND 'foo';
在常量传播阶段,一些非恒定值可能被转换为常量。
MySQL试图从WHERE子句中为每个可能的索引提取范围条件。 在提取过程中,不能用于构建范围条件的条件被删除,产生重叠范围的条件被合并,产生空的范围的条件被去除。
考虑下面的语句,其中key1是一个索引列,nonkey没有索引:
SELECT * FROM t1 WHERE
(key1 < 'abc' AND (key1 LIKE 'abcde%' OR key1 LIKE '%b')) OR
(key1 < 'bar' AND nonkey = 4) OR
(key1 < 'uux' AND key1 > 'z');
关键字 key1的提取过程如下:
1.从原本的where 子句开始:
(key1 < 'abc' AND (key1 LIKE 'abcde%' OR key1 LIKE '%b')) OR
(key1 < 'bar' AND nonkey = 4) OR
(key1 < 'uux' AND key1 > 'z')
2.删除nonkey = 4和key1 LIKE'%b',因为它们不能用于范围扫描。 删除它们的正确方法是用TRUE替换它们,以便在进行范围扫描时不会错过任何匹配的行。 将它们替换为TRUE后,我们得到:
(key1 < 'abc' AND (key1 LIKE 'abcde%' OR TRUE)) OR
(key1 < 'bar' AND TRUE) OR
(key1 < 'uux' AND key1 > 'z')
3.折叠条件始终为真或假:
•(key1 LIKE'abcde%'或TRUE)始终为真
•(key1 'z')始终为false
用常数替换这些条件,我们得到:
(key1
删除不必要的TRUE和FALSE常量,我们得到:
(key1 < 'abc') OR (key1 < 'bar')
4.将重叠的时间间隔合并为一个,将产生用于范围扫描的最终条件:
(key1 < 'bar')
一般来说(和前面的例子一样),用于范围扫描的条件与WHERE子句相比限制性更小。 MySQL执行额外的检查来筛选满足范围条件但不满足WHERE子句的行。
范围条件提取算法可以处理任意深度的嵌套AND/OR结构,其输出不依赖于条件出现在WHERE子句中的顺序。
目前,MySQL不支持为空间索引的范围访问方法合并多个范围。 要解决此限制,可以使用具有相同SELECT语句的UNION,不同之处在于您将每个空间谓词放在不同的SELECT中。
--空间索引是R树。
Multiple-Part 索引的范围访问方法(组合索引)
多部分索引的范围条件是单部分索引的范围条件的扩展。 多部分索引的范围条件将索引行限制在一个或多个关键元组间隔内。使用索引中的排序,在一组关键元组上定义关键元组间隔。
例如,考虑定义为key1(key_part1,key_part2,key_part3)的多部分索引以及按键顺序列出的以下一组键元组:
key_part1 key_part2 key_part3
NULL 1 'abc'
NULL 1 'xyz'
NULL 2 'foo'
1 1 'abc'
1 1 'xyz'
1 2 'abc'
2 1 'aaa'
条件key_part1 = 1定义了这个间隔:
(1,-inf,-inf) <= (key_part1,key_part2,key_part3) < (1,+inf,+inf)
区间覆盖了前面数据集中的第4,5和6个元组,并且可以被范围访问方法使用。
相比之下,条件key_part3 ='abc'没有定义单个时间间隔,不能被范围访问方法使用。
以下说明更详细地指出了范围条件如何适用于多部分索引。
对于HASH索引,可以使用包含相同值的每个区间。 这意味着间隔只能在以下形式的条件下产生:
key_part1 cmp const1
AND key_part2 cmp const2
AND ...
AND key_partN cmp constN;
在这里,const1,const2,...是常量,cmp是=,<=>或IS NULL比较运算符之一,条件覆盖所有索引部分。 (也就是说,有N个条件,一个用于N部分索引的每个部分)。例如,以下是三部分HASH索引的范围条件:
key_part1 = 1 AND key_part2 IS NULL AND key_part3 = 'foo'
对于一个BTREE索引,一个区间可以用于与AND结合的条件,其中每个条件使用=,<=>,IS NULL,>,<=>,=,<=,!=来比较关键部分和常数值, <>,BETWEEN或LIKE 'pattern'(其中'pattern'不以通配符开头)。 只要可以确定包含与条件匹配的所有行的单个关键元组(或者使用<>或!=的两个间隔),就可以使用间隔。
只要比较运算符是=,<=>或IS NULL,优化器就会尝试使用其他关键部分来确定间隔。 如果运算符是>, =,<=,!=,<>,BETWEEN或LIKE,则优化程序使用它,但不考虑更多关键部分。 对于下面的表达式,优化器在第一个比较中使用=。 它也使用> =从第二个比较,但不考虑更多的关键部分,不使用第三个比较的区间构造:
key_part1 = 'foo' AND key_part2 >= 10 AND key_part3 > 10
单个时间间隔是:
('foo',10,-inf) < (key_part1,key_part2,key_part3) < ('foo',+inf,+inf)
创建的区间可能包含比初始条件更多的行。 例如,前面的间隔包括不符合原始条件的值('foo',11,0)。
如果覆盖区间内包含的行集的条件与OR组合,则它们形成一个条件,该条件涵盖了包含在区间并集中的一组行。 如果条件与“与”组合,则它们形成一个条件,覆盖包含在它们的区间交集内的一组行。 例如,对于两部分索引中的这种情况:
(key_part1 = 1 AND key_part2 < 2) OR (key_part1 > 5)
间隔为:
(1,-inf) < (key_part1,key_part2) < (1,2)
(5,-inf) < (key_part1,key_part2)
在此示例中,第一行的间隔使用一个关键部分作为左边界,第二个关键部分作为右边界。 第二行的间隔只使用一个关键部分。 EXPLAIN输出中的key_len列表示所用密钥前缀的最大长度。
在某些情况下,key_len可能表示使用了关键部分,但这可能不是您所期望的。 假设key_part1和key_part2可以是NULL。 然后,key_len列显示以下情况下的两个关键部件长度:
key_part1 >= 1 AND key_part2 < 2
但是,事实上,条件转换为:
key_part1 >= 1 AND key_part2 IS NOT NULL
单部分索引的范围访问方法描述了如何执行优化来合并或消除单部分索引的范围条件间隔。 在多部分索引的范围条件下执行类似的步骤。
ken_len 可以用来判断哪些字段上使用了索引(组合索引)所有的索引字段,如果没有设置not null,则需要加一个字节。
定长字段,int占四个字节、date占三个字节、char(n)占n个字符。
对于变成字段varchar(n),则有n个字符+两个字节。
不同的字符集,一个字符占用的字节数不同。latin1编码的,一个字符占用一个字节,gbk编码的,一个字符占用两个字节,utf8编码的,一个字符占用三个字节。
多值比较的等式范围优化
考虑这些表达式,其中col_name是一个索引列:
col_name IN(val1, ..., valN)
col_name = val1 OR ... OR col_name = valN
如果col_name等于几个值中的任何一个,则每个表达式都是true。 这些比较是等同范围比较(其中“范围”是单个值)。 优化程序估计阅读符合等价范围比较的合格行的成本,如下所示:
如果col_name上有唯一的索引,则每个范围的行估计值为1,因为最多有一行可以具有给定的值。
否则,优化程序可以使用 dives into the index 或索引统计来估计每个范围的行数
使用索引dives时,优化程序会在范围的每一端进行潜水,并使用该范围内的行数作为估计值。 例如,表达式col_name IN(10,20,30)具有三个相等的范围,优化器每个范围两次潜水来产生一个行估计。 每一对潜水产生具有给定值的行数的估计值。
--有点像oracle 的动态采样
索引潜水提供准确的行估计,但随着表达式中比较值的数量增加,优化程序花费较长时间来生成行估计。 索引统计信息的使用不如索引潜水准确,但允许对大值列表进行更快的行估计。
通过eq_range_index_dive_limit系统变量,您可以配置优化程序从一个行估算策略切换到另一个行的值的数量。 要禁用统计信息并始终使用索引潜水,请将eq_range_index_dive_limit设置为0.要允许使用索引潜水来比较最多N个相等范围,请将eq_range_index_dive_limit设置为N + 1。
--越大越消耗时间,但是对范围等值的评估会越精确
要更新表索引统计信息以获取最佳估计值,请使用ANALYZE TABLE。
行构造器表达式的范围优化
从MySQL 5.7.3开始,优化器能够将范围扫描访问方法应用于这种形式的查询:
SELECT ... FROM t1 WHERE ( col_1, col_2 ) IN (( 'a', 'b' ), ( 'c', 'd' ));
以前,要使用范围扫描,需要将查询写为:
SELECT ... FROM t1 WHERE ( col_1 = 'a' AND col_2 = 'b' )
OR ( col_1 = 'c' AND col_2 = 'd' );
为了优化器使用范围扫描,查询必须满足以下条件:只有IN谓词可以使用,NOT IN 不行。
IN谓词左边的行构造函数中可能只有列引用。
IN谓词的右边必须有多个行构造函数。
IN谓词右边的行构造函数必须只包含运行时常量,这些常量是在执行期间绑定到常量的文字或本地列引用。
与MySQL 5.7.3之前执行的类似查询相比,适用查询的EXPLAIN输出从全表或索引扫描变为范围扫描。 通过检查Handler_read_first,Handler_read_key和Handler_read_next状态变量的值也可以看到更改。
4.Index Merge 优化
Index Merge方法用于检索具有多个范围扫描的行,并将其结果合并为一个。 合并可以产生union,交集或其底层扫描的unions-of-intersections。 此访问方法合并来自单个表的索引扫描; 它不会将扫描合并到多个表中。
在EXPLAIN输出中,索引合并方法在类型列中显示为index_merge。 在这种情况下,键列包含使用的索引列表,而key_len包含这些索引的最长键部分的列表。
例如:
SELECT * FROM tbl_name WHERE key1 = 10 OR key2 = 20;
SELECT * FROM tbl_name
WHERE (key1 = 10 OR key2 = 20) AND non_key=30;
SELECT * FROM t1, t2
WHERE (t1.key1 IN (1,2) OR t1.key2 LIKE 'value%')
AND t2.key1=t1.some_col;
SELECT * FROM t1, t2
WHERE t1.key1=1
AND (t2.key1=t1.some_col OR t2.key2=t1.some_col2);
索引合并方法有几种访问算法(见EXPLAIN输出的Extra列):
• Using intersect(...)
• Using union(...)
• Using sort_union(...)
注意:
索引合并优化算法有以下已知的缺陷:如果你的查询有一个复杂的WHERE子句和深层的AND / OR嵌套,并且MySQL没有选择最佳的计划,可以尝试使用以下的身份法则来分配条件:
(x AND y) OR z = (x OR z) AND (y OR z)
(x OR y) AND z = (x AND z) OR (y AND z)索引合并不适用于全文索引。 我们计划在未来的MySQL发行版中扩展它。
索引合并交集访问算法
当将WHERE子句转换为与AND组合的不同键上的几个范围条件时,可以使用此访问算法,并且每个条件是以下之一:在这种形式下,索引具有N个部分(即所有索引部分都被覆盖):
key_part1=const1 AND key_part2=const2 ... AND key_partN=constNInnoDB表主键的任何范围条件。
例子:
SELECT * FROM innodb_table WHERE primary_key < 10 AND key_col1=20;
SELECT * FROM tbl_name
WHERE (key1_part1=1 AND key1_part2=2) AND key2=2;
索引合并相交算法对所有使用的索引执行同时扫描,并产生从合并索引扫描中接收到的行序列的交集。
如果查询中使用的所有列都被使用的索引覆盖,则不会检索完整的表行(EXPLAIN输出包含在这种情况下在Extra字段中使用索引)。 这是一个这样的查询的例子:
SELECT COUNT(*) FROM t1 WHERE key1=1 AND key2=1;
如果使用的索引不包括查询中使用的所有列,则只有满足所有使用的键的范围条件时才检索完整的行。
如果其中一个合并条件是InnoDB表的主键条件,则不用于行检索,而是用于过滤使用其他条件检索到的行。
索引合并 并集访问方法
该算法的适用性标准与索引合并方法相交算法相似。 当表的WHERE子句被转换为与OR组合的不同键上的几个范围条件时,可以采用该算法,并且每个条件是以下之一:
在这种形式下,索引具有N个部分(即所有索引部分都被覆盖):
key_part1=const1 AND key_part2=const2 ... AND key_partN=constNInnoDB表主键的任何范围条件。
索引合并方法,相交算法适用的条件。
例如:
SELECT * FROM t1 WHERE key1=1 OR key2=2 OR key3=3;
SELECT * FROM innodb_table WHERE (key1=1 AND key2=2) OR
(key3='foo' AND key4='bar') AND key5=5;
索引合并排序并集访问算法
当WHERE子句被转换为由OR组合的若干个范围条件,但Index Merge方法联合算法不适用时,使用此访问算法。
例如:
SELECT * FROM tbl_name WHERE key_col1 < 10 OR key_col2 < 20;
SELECT * FROM tbl_name
WHERE (key_col1 > 10 OR key_col2 = 20) AND nonkey_col=30;
排序联合算法和联合算法的区别在于,排序联合算法必须首先为所有行提取行ID,然后在返回任何行之前对其进行排序。
5.引擎条件下推的优化
这种优化提高了非索引列和常量之间的直接比较的效率。 在这种情况下,条件被“下推”到存储引擎进行评估。 这个优化只能由NDB存储引擎使用。
注意:
NDB存储引擎目前在MySQL 5.7中不可用。 如果您有兴趣使用MySQL Cluster,请参阅MySQL Cluster NDB 7.2,其中提供了有关MySQL Cluster NDB 7.2的信息,该信息基于MySQL 5.5,但包含NDBCLUSTER的最新改进和修复。
对于MySQL群集,这种优化可以消除在群集的数据节点和发出查询的MySQL服务器之间通过网络发送不匹配的行的需要,并且可以加速比使用5到10倍的查询更快的查询 那里的条件下推可能是,但没有使用。
假设 MYSQL 集群表定义如下:
CREATE TABLE t1 (
a INT,
b INT,
KEY(a)
) ENGINE=NDB;
条件下推可以用于这里显示的查询,其中包括非索引列和常量之间的比较:
SELECT a, b FROM t1 WHERE b = 10;
在EXPLAIN的输出中可以看到使用条件下推:
mysql> EXPLAIN SELECT a,b FROM t1 WHERE b = 10\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: t1
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 10
Extra: Using where with pushed condition
但是,下面的两个查询都不能使用条件下推:
SELECT a,b FROM t1 WHERE a = 10;
SELECT a,b FROM t1 WHERE b + 1 = 10;
条件下推不适用于第一个查询,因为列a上存在索引。 (索引访问方法会更高效,因此会优先选择。)第二个查询不能使用条件下推,因为涉及非索引列b的比较是间接的。 (但是,如果要在WHERE子句中将b + 1 = 10减少到b = 9,则可以应用条件下推。)
使用>或
mysql>EXPLAIN SELECT a, b FROM t1 WHERE a < 2\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: t1
type: range
possible_keys: a
key: a
key_len: 5
ref: NULL
rows: 2
Extra: Using where with pushed condition
其他支持的条件下推的比较包括以下内容:column [NOT] LIKE pattern
模式必须是包含要匹配的模式的字符串文字;column IS [NOT] NULL
column IN (value_list)
value_list中的每个项目都必须是常量,字面值。column BETWEEN constant1 AND constant2
常量1和常量2每个都必须是常量,字符值。
在前面列表中的所有情况中,条件可以被转换成列和常数之间的一个或多个直接比较的形式。
引擎条件下推是默认启用的。 要在服务器启动时禁用它,请设置optimizer_switch系统变量。 例如,在my.cnf文件中,使用这些行:
optimizer_switch=engine_condition_pushdown=off
在运行时,像这样启用条件下推:
SET optimizer_switch='engine_condition_pushdown=off';
限制。引擎条件下推有如下限制:
•条件下推仅由NDB存储引擎支持。
•列可能只与常量进行比较; 但是,这包括评估为常数值的表达式。
•用于比较的列不能是任何BLOB或TEXT类型。
•要与列进行比较的字符串值必须使用与列相同的排序规则。
•join不直接支持; 涉及多个表格的条件在可能的情况下分开推送 使用EXPLAIN EXTENDED来确定哪些条件实际上被推下来。
6.索引下推优化
索引条件下推(ICP)是一种优化,MySQL使用索引从表中检索行。 如果没有ICP,存储引擎将遍历索引来查找基表中的行,并将其返回给MySQL服务器,该服务器将评估行的WHERE条件。 启用ICP后,如果只能使用索引中的字段来评估WHERE条件的某些部分,那么MySQL服务器会将这部分WHERE条件向下推送到存储引擎。 然后,存储引擎通过使用索引条目来评估推送索引条件,并且只有满足时才从表中读取行.ICP可以减少存储引擎必须访问基表的次数以及MySQL服务器必须访问存储引擎的次数 。
索引条件当需要访问全表行时,下推优化用于range,ref,eq_ref和ref_or_null访问方法。 这个策略可以用于InnoDB和MyISAM表。 从MySQL 5.7.3开始,它也可以与分区的InnoDB和MyISAM表一起使用(Bug#17306882,Bug#70001)。 但是,对于InnoDB表,ICP仅用于二级索引。 ICP的目标是减少全记录读取次数,从而减少IO操作。对于InnoDB聚簇索引,完整的记录已经被读入InnoDB缓冲区。 在这种情况下使用ICP不会减少IO。
在虚拟生成的列上创建的二级索引不支持ICP优化。 InnoDB支持自MySQL 5.7.8开始的虚拟生成列的二级索引。
--这里的聚簇索引 就是主键上的索引,和ORACLE 的IOT类似,这样就好理解二级索引了。二级索引查到之后,还要根据主键再去聚餐索引中拿数据行。
(mysql innodb中 一个表只能有一个主键上的聚簇索引,其他的索引都是二级索引。如果没主键,会使用ID自动创建一个6字节影藏的聚餐索引。)
--oracle 中的簇表和cluster index 概念上与这里有些不同。
要了解此优化如何工作,请首先考虑索引扫描如何在不使用“索引条件下推”时进行:
获取下一行,首先读取索引元组,然后使用索引元组来定位并读取整个表的行。
2.测试适用于此表的WHERE条件的部分。 根据测试结果接受或拒绝该行。
当使用索引条件下推时,扫描就会像这样进行:获取下一行的索引元组(但不是全表行)。
测试适用于此表的WHERE条件的部分,并且只使用索引列进行检查。 如果条件不满足,则为下一行转到索引元组。
如果满足条件,则使用索引元组来定位并读取整个表行。
测试适用于此表的WHERE条件的其余部分。 根据测试结果接受或拒绝该行。
使用索引条件下推时,EXPLAIN输出中的Extra列输出 Using index condition。 它不会显示indxe only,因为当必须读取全表行时,索引不适用。
假设我们有一个包含关于人员及其地址的信息的表,并且表具有定义为INDEX(zipcode, lastname, firstname)的索引。 如果我们知道一个人的邮政编码值,但不知道姓氏,我们可以这样搜索:
SELECT * FROM people
WHERE zipcode='95054'
AND lastname LIKE '%etrunia%'
AND address LIKE '%Main Street%';
MySQL可以使用索引扫描zipcode ='95054'的人。 第二部分(lastname LIKE '%etrunia%'))不能用于限制必须扫描的行数,因此,如果没有索引条件下推,此查询必须检索所有具有zipcode ='95054'的人员的全表行。
使用索引条件下推,MySQL将在读取完整的表行之前检查lastname LIKE'%etrunia%'部分。 这样可以避免读取与所有不符合lastname条件的索引元组相对应的完整行。
--这个就是 在oracle B-tree索引中组合索引的优势。
索引条件下推是默认启用; 可以使用optimizer_switch系统变量通过设置index_condition_pushdown标志来控制它。
7.使用索引拓展
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)。
在MySQL 5.7中,优化器在确定如何以及是否使用该索引时考虑了扩展二级索引的主键列。这可以导致更有效的查询执行计划和更好的性能。
优化程序可以为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
---一般情况下都会选择这个,这个执行计划更好。
在这两种情况下,键都表示优化器将使用二级索引k_d,但EXPLAIN输出显示了使用扩展索引的这些改进:key_len从4字节到8字节,表明键查找使用列d和i1,而不仅仅是d。
ref值从const更改为const,const,因为key查找使用两个关键部分,而不是一个。
行数从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 TABLE和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,表示更有效地使用索引:
optimizer_switch系统变量的use_index_extensions标志允许控制优化程序在确定如何使用InnoDB表的二级索引时是否考虑主键列。 默认情况下,use_index_extensions被启用。 要检查禁用索引扩展是否会提高性能,请使用以下语句:
SET optimizer_switch = 'use_index_extensions=off';
优化器使用索引扩展受索引(16)中关键部分数量和最大关key len(3072字节)的通常限制。