查看“MySQL 优化:优化 SQL 语句:优化 SELECT 语句”的源代码
←
MySQL 优化:优化 SQL 语句:优化 SELECT 语句
跳到导航
跳到搜索
因为以下原因,您没有权限编辑本页:
您请求的操作仅限属于该用户组的用户执行:
用户
您可以查看和复制此页面的源代码。
[[category:MySQL]] == 关于 == 查询以“SELECT”语句的形式执行数据库中的所有查找操作。 * 除了“SELECT”语句外,查询的调整技术还适用于:“CREATE TABLE...AS SELECT”,“INSERT INTO...SELECT”和“DELETE”语句中的“WHERE”子句。这些语句还有其他性能方面的考虑,因为它们将写操作与面向读取的查询操作结合在一起。 * NDB Cluster 支持 join 下推优化,将符合条件的 join 整体发送到 NDB Cluster 数据节点,在这些节点之间可以分布并并行执行。【???】 优化查询的主要注意事项是: # 要使慢速“SELECT ... WHERE”查询更快,首先要检查的是是否可以添加'''index'''。在“WHERE”子句中使用的列上设置索引,以加快评估,过滤和最终检索结果的速度。为了避免浪费磁盘空间,请构造一小组索引,以加快应用程序中使用的许多相关查询的速度。 #: 对于使用“joins”和“foreign keys”之类的功能引用不同表的查询,索引尤为重要。您可以使用“'''EXPLAIN'''”语句来确定 SELECT 使用哪些索引。 # 隔离并调整查询中花费时间过多的任何部分,例如函数调用。根据查询的结构方式,可以对结果集中的每一行调用一次函数,甚至可以对表中的每一行调用一次函数,从而极大地提高了效率。 # 最小化查询中的'''全表扫描'''数量,尤其是对于大表。 # 通过定期使用“'''ANALYZE TABLE'''”语句来使表统计信息保持最新,因此优化器具有构造有效执行计划所需的信息。 # 了解每个表的'''存储引擎'''特定的调整技术,索引技术和配置参数。InnoDB 和 MyISAM 都有一套准则,用于启用和维持查询的高性能。 # 避免以难以理解的方式转换查询,尤其是在优化程序自动执行某些相同转换的情况下。【?】 # 如果使用基本准则之一不能轻松解决性能问题,请通过阅读“EXPLAIN”计划并调整'''索引,WHERE 子句,join 子句'''等来调查特定查询的内部详细信息。 #* (当您达到一定的专业水平时,阅读 EXPLAIN 计划可能是每个查询的第一步。)【!】 # 调整 MySQL 用于缓存的内存区域的大小和属性。通过有效地使用'''InnoDB buffer pool(缓冲池),MyISAM 键高速缓存和 MySQL 查询高速缓存''',重复查询的运行速度更快,因为第二次及以后都从内存中检索结果。 # 即使对于使用缓存区域快速运行的查询,您也可能会进一步优化,以使它们需要更少的缓存,从而使您的应用程序更具可伸缩性。可伸缩性意味着您的应用程序可以处理更多的并发用户,更大的请求等,而不会导致性能大幅下降。 # 处理'''锁'''问题,其中其他会话同时访问表可能会影响查询速度。 == WHERE 子句优化【适用于“SELECT”、“DELETE”和“UPDATE”语句中的“WHERE”子句】== 您可能会试图重写查询以加快算术运算,同时牺牲可读性。 * 因为'''MySQL会自动进行类似的优化''',所以通常可以避免这项工作,并以更易于理解和维护的形式保留查询。 MySQL 执行的一些优化如下: # 删除不必要的括号: #: <syntaxhighlight lang="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) </syntaxhighlight> # 常数折叠: #: <syntaxhighlight lang="mysql"> (a<b AND b=c) AND a=5 -> b>5 AND b=c AND a=5 </syntaxhighlight> # 常数条件移除: #: <syntaxhighlight lang="mysql"> (b>=5 AND b=5) OR (b=6 AND 5=5) OR (b=7 AND 5=6) -> b=5 OR b=6 </syntaxhighlight> # 索引使用的常量表达式仅计算一次。 # '''“COUNT(*)”:在没有“WHERE”单表上,是直接从 MyISAM 和 MEMORY 表的表信息中检索的。''' #* 当其仅与一个表一起使用时,对于任何“NOT NULL”表达式也将执行此操作。 # 早期检测无效常量表达式。MySQL很快检测到一些“SELECT”语句是不可能的,并且不返回任何行。 # '''如果不使用“GROUP BY”或集合函数(“COUNT()”、“MIN()”等),“HAVING”将与“WHERE”合并'''。 # 对于联接中的每个表,构造一个'''更简单的“WHERE”'''来快速计算表的“WHERE”,并尽快跳过行。 # 在查询中的所有其他表之前,首先读取所有'''常量表'''。常量表可以是以下任意一个:【???】 ## 空表或具有一行的表。 ## 在“主键”或“唯一索引”上使用“WHERE”子句的表,其中所有索引部分都与常量表达式进行比较,并定义为“NOT NULL”。【?】 #: 以下所有表均用作常量表: #: <syntaxhighlight lang="mysql"> SELECT * FROM t WHERE primary_key=1; SELECT * FROM t1,t2 WHERE t1.primary_key=1 AND t2.primary_key=t1.id; </syntaxhighlight> # 通过尝试所有可能的方法,找到用于联接表的最佳联接组合。'''如果“ORDER BY”和“GROUP BY”子句中的所有列都来自同一表,则在联接时优先使用该表。''' # 如果有一个“ORDER BY”子句和另一个“GROUP BY”子句,或者“ORDER BY”或“GROUP BY”包含联接队列中第一个表以外的表中的列,则会创建一个临时表。【???】 # 如果使用“'''SQL_SMALL_RESULT'''”修饰符,则 MySQL 使用内存中的临时表。【???】 # 查询每个表索引,并使用'''最佳索引''',除非优化程序认为使用全表扫描更有效。同时,使用扫描是基于最佳索引是否跨越了表的 30%以上,但是固定百分比不再决定使用索引还是扫描。现在,优化器更加复杂,其评估基于其他因素,例如表大小,行数和 I/O 块大小。 # 在输出每一行之前,将跳过与“HAVING”子句不匹配的行。 快速查询示例: <syntaxhighlight lang="mysql"> 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; </syntaxhighlight> 假设索引列是数字,MySQL 仅使用索引树来解析以下查询: <syntaxhighlight lang="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; </syntaxhighlight> 以下查询使用索引来按排序 Sequences 检索行,而无需单独的排序遍历: <syntaxhighlight lang="mysql"> SELECT ... FROM tbl_name ORDER BY key_part1,key_part2,... ; SELECT ... FROM tbl_name ORDER BY key_part1 DESC, key_part2 DESC, ... ; </syntaxhighlight> == 范围优化【??????】 == range访问方法使用单个索引来检索包含在一个或几个索引值间隔内的表行的子集。它可以用于单部分或多部份索引。 === 单部分索引的范围访问方法【???】 === 对于单部分索引,索引值间隔可以方便地由“WHERE”子句中的相应条件表示,表示为范围条件,而不是“间隔”。【?】 单部分索引的范围条件的定义如下: # 对于 B树 和 HASH 索引,使用'''=''','''<=>''','''IN()''','''IS NULL'''或'''不为空'''运算符时,将关键部分与常量值进行比较是一个范围条件。 # 此外,对于 B树 索引,使用'''>''','''<''','''>=''','''<=''','''BETWEEN''','''!='''或'''<>'''运算符时,将关键部分与常量值进行比较是一个范围条件; #* 如果'''LIKE'''的参数为常量字符串,则'''进行 LIKE 比较不能以通配符开头'''。 # 对于所有索引类型,将多个范围条件与'''OR'''或'''AND'''组合在一起可形成范围条件。 前面的描述中的“常量值”表示以下之一: # 查询字符串中的常量; # 来自同一联接的常量或system表中的列; # 不相关子查询的结果; # 任何完全由上述类型的子表达式组成的表达式; 以下是“WHERE”子句中具有范围条件的查询示例: <syntaxhighlight lang="mysql"> 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_col BETWEEN 'bar' AND 'foo'; </syntaxhighlight> * 在优化程序常数传播阶段,某些非常数值可以转换为常数。 MySQL 尝试从“WHERE”子句中为每个可能的索引'''提取范围条件'''。在提取过程中,删除了不能用于构建范围条件的条件,合并了产生重叠范围的条件,并删除了产生空范围的条件。【???】 示例:【?????????】<br/> 请考虑以下语句,其中 key1 是索引列,而 nonkey 没有索引: <syntaxhighlight lang="mysql"> 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'); </syntaxhighlight> 密钥 key1 的提取过程如下: # 从原始的 WHERE 子句开始: #: <syntaxhighlight lang="mysql"> (key1 < 'abc' AND (key1 LIKE 'abcde%' OR key1 LIKE '%b')) OR (key1 < 'bar' AND nonkey = 4) OR (key1 < 'uux' AND key1 > 'z') </syntaxhighlight> # 删除“nonkey = 4”【B树索引“=”结果不为范围】和“key1 LIKE '%b'”【“LIKE”以通配符开始结果不为范围】,因为它们不能用于范围扫描。删除它们的正确方法是将它们替换为“TRUE”,这样我们在进行范围扫描时就不会丢失任何匹配的行。用 TRUE 替换它们会产生: #: <syntaxhighlight lang="mysql"> (key1 < 'abc' AND (key1 LIKE 'abcde%' OR TRUE)) OR (key1 < 'bar' AND TRUE) OR (key1 < 'uux' AND key1 > 'z') </syntaxhighlight> # 折叠始终为 true 或 false 的条件: #* (key1 LIKE 'abcde%' OR TRUE) 始终为真 #* (key1 < 'uux' AND key1 > 'z') 始终为假 # 用常量替换这些条件将产生: #: <syntaxhighlight lang="mysql"> (key1 < 'abc' AND TRUE) OR (key1 < 'bar' AND TRUE) OR (FALSE) </syntaxhighlight> # 删除不必要的 TRUE 和 FALSE 常量将产生: #: <syntaxhighlight lang="mysql"> (key1 < 'abc') OR (key1 < 'bar') </syntaxhighlight> # 将重叠的间隔合并为一个会产生用于范围扫描的最终条件: #: <syntaxhighlight lang="mysql"> (key1 < 'bar') </syntaxhighlight> 一般而言(如前面的示例所示),范围扫描所使用的条件比“WHERE”子句的限制要少。 MySQL 执行附加检查以过滤出 满足范围条件但不完整的“WHERE”子句的行。【?】 范围条件提取算法可以处理任意深度的嵌套 AND / OR 结构,并且其输出不取决于条件在 WHERE 子句中出现的顺序。 MySQL 不支持为空间索引的range访问方法合并多个范围。要解决此限制,可以将“UNION”与相同的“SELECT”语句一起使用,除了将每个空间谓词放在不同的“SELECT”中。 === 多部分索引的范围访问方法【???】 === 多部分索引的范围条件是单部分索引的范围条件的扩展。 Multipart 索引的范围条件是单部分索引的范围条件的扩展。Multipart 索引上的范围条件将索引行限制在一个或几个键 Tuples 间隔内。使用从索引开始的 Sequences,在一组键 Tuples 上定义键 Tuples 间隔。 例如,考虑定义为“key1(key_part1, key_part2, key_part3)”的 Multipart 索引,并按键 Sequences 列出以下一组键 Tuples: <syntaxhighlight lang="mysql"> 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' </syntaxhighlight> 条件“key_part1 = 1”定义了此间隔: <syntaxhighlight lang="mysql"> (1,-inf,-inf) <= (key_part1,key_part2,key_part3) < (1,+inf,+inf) </syntaxhighlight> 该间隔涵盖了先前数据集中的第 4,第 5 和第 6 个 Tuples,并且可以由范围访问方法使用。 相反,条件“key_part3 = 'abc'”并未定义单个间隔,并且不能被范围访问方法使用。 关于范围条件如何作用于 Multipart 索引:【???】 # 对于 HASH 索引,可以使用每个包含相同值的间隔。【???】 #: 这意味着只能针对以下形式的条件生成间隔: #: <syntaxhighlight lang="mysql"> key_part1 cmp const1 AND key_part2 cmp const2 AND ... AND key_partN cmp constN; </syntaxhighlight> #: 这里,const1,const2,…是常量,cmp 是 =,<=> 或 IS NULL 比较运算符之一,并且条件涵盖所有索引部分。(也就是说,存在 N 条件, N-part 索引的每个部分都有一个条件)例如,以下是由三部分组成的 HASH索引 的范围条件: #: <syntaxhighlight lang="mysql"> key_part1 = 1 AND key_part2 IS NULL AND key_part3 = 'foo' </syntaxhighlight> # 对于 B树 索引,间隔可用于与“AND”组合的条件,其中每个条件使用 =,<=>,IS NULL,>,<,>=,<=,!=,<>,BETWEEN 或 LIKE 'pattern' 将键部分与常数值进行比较(其中'pattern'不能以通配符开头)。只要可以确定包含所有与条件匹配的行的单个键 Tuples,就可以使用一个间隔(如果使用 <> 或 !=,则可以使用两个间隔)。【???】 #: 只要比较运算符是 =,<=> 或 IS NULL,优化器就会尝试使用其他关键部分来确定间隔。如果运算符是 >,<,>=,<=,!=,<>,BETWEEN 或 LIKE,则优化器将使用它,但不再考虑其他关键部分。对于以下表达式,优化器使用第一个比较中的 =。它还从第二次比较中使用 >=,但不考虑其他关键部分,并且不将第三次比较用于间隔构造: #: <syntaxhighlight lang="mysql"> key_part1 = 'foo' AND key_part2 >= 10 AND key_part3 > 10 </syntaxhighlight> #: 单个间隔为: #: <syntaxhighlight lang="mysql"> ('foo',10,-inf) < (key_part1,key_part2,key_part3) < ('foo',+inf,+inf) </syntaxhighlight> #* 创建的间隔可能包含比初始条件更多的行。例如,前面的时间间隔包含不满足原始条件的值('foo', 11, 0)。 # 如果将覆盖间隔中包含的行集合的条件与“OR”组合,则它们将形成覆盖间隔中的并集内包含的行集合的条件。如果条件与“AND”组合,则它们将形成一个条件,该条件覆盖其间隔的交点内包含的一组行。例如,对于由两部分组成的索引的这种情况: #: <syntaxhighlight lang="mysql"> (key_part1 = 1 AND key_part2 < 2) OR (key_part1 > 5) </syntaxhighlight> #: 间隔为: #: <syntaxhighlight lang="mysql"> (1,-inf) < (key_part1,key_part2) < (1,2) (5,-inf) < (key_part1,key_part2) </syntaxhighlight> #: 在此示例中,第一行的间隔使用一个关键部分作为左边界,使用两个关键部分作为右边界。第二行的间隔仅使用一个关键部分。 EXPLAIN 输出中的 key_len 列表示所使用的键前缀的最大长度。 在某些情况下,key_len 可能表明已使用了关键部件,但这可能不是您期望的。【???】 : 假设 key_part1 和 key_part2 可以是 NULL。然后 key_len 列显示以下条件的两个关键 Component 长度: : <syntaxhighlight lang="mysql"> key_part1 >= 1 AND key_part2 < 2 </syntaxhighlight> : 但是,实际上,条件已转换为: : <syntaxhighlight lang="mysql"> key_part1 >= 1 AND key_part2 IS NOT NULL </syntaxhighlight> === 多值比较的等距范围优化【???】 === 考虑以下表达式,其中“col_name”是索引列: <syntaxhighlight lang="mysql"> col_name IN(val1, ..., valN) col_name = val1 OR ... OR col_name = valN </syntaxhighlight> 如果 col_name 等于多个值中的任何一个,则每个表达式为 true。这些比较是相等范围比较(其中“范围”是单个值)。优化器估算读取相等行以进行相等范围比较的成本,如下所示: # 如果 col_name 上有唯一索引,则每个范围的行估计为 1,因为最多一行可以具有给定值。 # 否则,col_name 上的任何索引都是唯一的,优化器可以使用对索引或索引统计数据的深入估算来估计每个范围的行数。【???】 使用'''索引下潜'''时,优化器在范围的每个末端进行下潜,并将范围中的行数用作估计值。例如,表达式“col_name IN (10, 20, 30)”具有三个相等范围,并且优化程序对每个范围进行两次下潜以生成行估计。每对下潜都会得出具有给定值的行数的估计值。【???】 * 索引下潜可提供准确的行估计,但是随着表达式中比较值的数量增加,优化器将花费更长的时间来生成行估计。使用索引统计信息的准确性不如使用索引统计法准确,但允许对大型值列表进行更快的行估计。 '''eq_range_index_dive_limit'''系统变量使您可以配置优化器从一种行估计策略切换到另一种行估计策略的值的数量: # 要允许索引下潜用于最多 N 个相等范围的比较,请将 eq_range_index_dive_limit 设置为 N + 1。 # 要禁用统计信息的使用,并且无论 N 始终使用索引潜水,请将 eq_range_index_dive_limit 设置为 0。 要更新表索引统计信息以获得最佳估计值,请使用“'''ANALYZE TABLE'''”。 即使在本应使用索引下潜的条件下,对于满足所有这些条件的查询也将跳过它们: # 存在单索引“FORCE INDEX”索引提示。其思想是,如果强制使用索引,那么执行下潜索引的额外开销将无济于事。 # 索引不是唯一的,不是“FULLTEXT”索引。 # 没有子查询。 # 没有“DISTINCT”,“GROUP BY”或“ORDER BY”子句。 这些下潜跳过条件仅适用于单表查询。对于多表查询(联接),不会跳过索引下潜。 === 行构造函数表达式的范围优化【???】 === 优化程序可以将范围扫描访问方法应用于以下形式的查询: '''<syntaxhighlight lang="mysql"> SELECT ... FROM t1 WHERE ( col_1, col_2 ) IN (( 'a', 'b' ), ( 'c', 'd' )); </syntaxhighlight>''' * 以前,要使用范围扫描,必须将查询编写为: *: <syntaxhighlight lang="mysql"> SELECT ... FROM t1 WHERE ( col_1 = 'a' AND col_2 = 'b' ) OR ( col_1 = 'c' AND col_2 = 'd' ); </syntaxhighlight> 为了使优化器使用范围扫描,查询必须满足以下条件: # '''仅使用“IN()”谓词,而不使用“NOT IN()”'''。 # 在“IN()”谓词的左侧,行构造函数仅包含列引用。 # 在“IN()”谓词的右侧,行构造函数仅包含运行时常量,这些常量是在执行期间绑定到常量的 Literals 或本地列引用。 # 在“IN()”谓词的右侧,有多个行构造器。 === 限制内存用于范围优化【???】 === 要控制范围优化器可用的内存,请使用 '''range_optimizer_max_mem_size''' 系统变量: # 值 0 表示“无限制”。 # 值大于 0 时,优化程序将在考虑范围访问方法时跟踪消耗的内存。如果将要超出指定的限制,则将放弃范围访问方法,而改为考虑其他方法,包括全表扫描。这可能不太理想。如果发生这种情况,则会发生以下警告(其中 N 是当前的 range_optimizer_max_mem_size 值): #: <syntaxhighlight lang="mysql"> Warning 3170 Memory capacity of N bytes for 'range_optimizer_max_mem_size' exceeded. Range optimization was not done for this query. </syntaxhighlight> * 对于“UPDATE”和“DELETE”语句,如果优化器退回到全表扫描并且启用了 sql_safe_updates 系统变量,则会发生错误而不是警告,因为实际上,没有键用于确定要修改的行。 * 对于超出可用范围优化内存的单个查询,并且对于该查询,优化器后退到次优计划,增大 range_optimizer_max_mem_size 值可以提高性能。 若要估计处理范围表达式所需的内存量,请使用以下准则:【?】 # 对于如下所示的简单查询,其中存在一个用于范围访问方法的候选键,每个与“'''OR'''”谓词的组合大约使用 230 个字节: #: <syntaxhighlight lang="mysql"> SELECT COUNT(*) FROM t WHERE a=1 OR a=2 OR a=3 OR .. . a=N; </syntaxhighlight> # 同样,对于以下查询,每个与“'''AND'''”谓词的组合大约使用 125 个字节: #: <syntaxhighlight lang="mysql"> SELECT COUNT(*) FROM t WHERE a=1 AND b=1 AND c=1 ... N; </syntaxhighlight> # 对于带有“'''IN()'''”谓词的查询: #: <syntaxhighlight lang="mysql"> SELECT COUNT(*) FROM t WHERE a IN (1,2, ..., M) AND b IN (1,2, ..., N); </syntaxhighlight> #* “IN()”列表中的每个 Literals 值都算作与“OR”组合的谓词。如果有两个“IN()”列表,则谓词与“OR”组合的数量是每个列表中 Literals 值数量的乘积。因此,在前一种情况下与OR组合的谓词数为“M × N”。 #* 在 5.7.11 之前,每个谓词与“OR”相结合的字节数更高,大约为 700 个字节。 == 索引合并优化 == == 引擎调教下推优化【???】 == == 索引条件下推优化【???】 == == 嵌套循环连接算法 == == 嵌套联接优化 == == Outer Join 优化 == == Outer Join 简化 == == 多范围读取优化 == == 阻止嵌套循环和批量key访问联接 == == 条件过滤 == == IS NULL 优化 == == ORDER BY 优化 == == GROUP BY 优化 == == DISTINCT 优化 == 在许多情况下,'''“DISTINCT”和“ORDER BY”结合需要一个临时表'''。 在大多数情况下,'''“DISTINCT”子句可以视为“GROUP BY”的特例'''。例如,以下两个查询是等效的: <syntaxhighlight lang="mysql"> SELECT DISTINCT c1, c2, c3 FROM t1 WHERE c1 > const; SELECT c1, c2, c3 FROM t1 WHERE c1 > const GROUP BY c1, c2, c3; </syntaxhighlight> 由于这种等效性,适用于“GROUP BY”查询的优化也可以应用于带有“DISTINCT”子句的查询。 * 当结合“LIMIT row_count”和“DISTINCT”时,MySQL 一旦找到 row_count 个唯一行,就会停止运行。 * 如果不使用查询中命名的所有表中的列,MySQL会在找到第一个匹配项后立即停止扫描任何未使用的表。 *: 在以下情况下,假设在 t2 之前使用 t1(您可以使用“EXPLAIN”进行检查),则当 MySQL 在 t2 中找到第一行时,它将停止从 t2(对于 t1 中的任何特定行)读取: *: <syntaxhighlight lang="mysql"> SELECT DISTINCT t1.a FROM t1, t2 where t1.a=t2.a; </syntaxhighlight> == LIMIT 查询优化 == 【LIMIT:从结果集中只需要指定数量的行,而非整个结果集】<br/> MySQL 有时会优化具有“LIMIT row_count”子句而没有“HAVING”子句的查询: # 如果只选择“LIMIT”的几行,MySQL在某些情况下会使用索引,而'''通常它会执行全表扫描'''。 # 如果将“LIMIT row_count”和“ORDER BY”结合使用,MySQL 会在找到排序结果前面的 row_count 个行后立即停止排序,而'''不是对整个结果进行排序'''。 #* 如果通过使用索引进行排序,这将非常快。 #* 如果必须执行“filesort”(文件排序?),则在找到前面的 row_count 个行之前,将选择所有匹配但不带有 LIMIT 子句的行,并对其中的大多数或全部进行排序。找到初始行后,MySQL 不会对结果集的其余部分进行排序。【?】 #*【此行为的一种体现是,具有和不具有 LIMIT 的 ORDER BY 查询可能以不同的 Sequences 返回行】 # 如果将“LIMIT row_count”和“DISTINCT”结合使用,MySQL 将在找到 row_count 唯一行后立即停止。 # 在某些情况下,可以通过按顺序读取索引(或对索引进行排序),然后计算汇总直到索引值更改来解决“GROUP BY”。在这种情况下,“LIMIT row_count”不会计算任何不必要的“GROUP BY”值。 # MySQL 一旦向 Client 端发送了所需的行数,它将立即中止查询,除非您使用“'''SQL_CALC_FOUND_ROWS'''”【见:“[http://wiki.eijux.com/MySQL_%E5%87%BD%E6%95%B0%E5%92%8C%E8%BF%90%E7%AE%97%E7%AC%A6%EF%BC%9A%E4%BF%A1%E6%81%AF%E5%87%BD%E6%95%B0 MySQL 函数和运算符:信息函数]”的“FOUND_ROWS()”部分,其用于“知道该语句在没有 LIMIT 的情况下将返回多少行”】。 #* 在这种情况下,可以使用“SELECT FOUND_ROWS()”检索行数。 # “LIMIT 0”快速返回一个空集。这对于检查查询的有效性很有用。 #* 它还可以用于获取使用 MySQL API 的应用程序中结果列元数据的类型的结果列的类型。通过mysql Client 端程序,您可以使用“--column-type-info”选项显示结果列类型。【?】 # 如果服务器使用临时表来解析查询,则它使用“LIMIT row_count”子句来计算所需的空间。【?】 # 如果没有为“ORDER BY”使用索引,但是也存在“LIMIT”子句,则优化器可能能够避免使用合并文件,并使用内存中“filesort”操作对内存中的行进行排序。【?】 如果“ORDER BY”列中的多行具有相同的值,服务器可以按任意顺序返回这些行,并且根据总体执行计划的不同,返回的顺序也可能不同。换句话说,这些行的排序顺序相对于非排序列是不确定的。 ** 影响执行计划的一个因素是“LIMIT”,因此具有 LIMIT 和不具有 LIMIT 的“ORDER BY”查询可能以不同的顺序返回行。 **: <syntaxhighlight lang="mysql"> mysql> SELECT * FROM ratings ORDER BY category; +----+----------+--------+ | id | category | rating | +----+----------+--------+ | 1 | 1 | 4.5 | | 5 | 1 | 3.2 | | 3 | 2 | 3.7 | | 4 | 2 | 3.5 | | 6 | 2 | 3.5 | | 2 | 3 | 5.0 | | 7 | 3 | 2.7 | +----+----------+--------+ mysql> SELECT * FROM ratings ORDER BY category LIMIT 5; +----+----------+--------+ | id | category | rating | +----+----------+--------+ | 1 | 1 | 4.5 | | 5 | 1 | 3.2 | | 4 | 2 | 3.5 | | 3 | 2 | 3.7 | | 6 | 2 | 3.5 | +----+----------+--------+ </syntaxhighlight> ** 要确保带有 LIMIT 和不带有 LIMIT 的行顺序相同,请在 ORDER BY 子句中包括其他列以使顺序确定。 **: <syntaxhighlight lang="mysql"> mysql> SELECT * FROM ratings ORDER BY category, id; +----+----------+--------+ | id | category | rating | +----+----------+--------+ | 1 | 1 | 4.5 | | 5 | 1 | 3.2 | | 3 | 2 | 3.7 | | 4 | 2 | 3.5 | | 6 | 2 | 3.5 | | 2 | 3 | 5.0 | | 7 | 3 | 2.7 | +----+----------+--------+ mysql> SELECT * FROM ratings ORDER BY category, id LIMIT 5; +----+----------+--------+ | id | category | rating | +----+----------+--------+ | 1 | 1 | 4.5 | | 5 | 1 | 3.2 | | 3 | 2 | 3.7 | | 4 | 2 | 3.5 | | 6 | 2 | 3.5 | +----+----------+--------+ </syntaxhighlight> == 函数调用优化 == MySQL 函数在内部被标记为确定性或不确定性。 * 不确定:如果给定参数固定值的函数可以'''为不同的调用返回不同的结果'''。如:“RAND()”,“UUID()”。 如果一个函数被标记为不确定的,则对 WHERE 子句中的每一行(从一个表中选择时)或行组合(从多表联接中选择时)的引用进行评估。 MySQL 还根据参数的类型(参数是表列还是常量值)确定何时评估函数。每当表列更改值时,都必须评估将表列作为参数的确定性函数。 '''非确定性函数可能会影响查询性能'''。例如,某些优化可能不可用,或者可能需要更多锁定。 示例: 假设表 t 具有以下定义: <syntaxhighlight lang="mysql"> CREATE TABLE t (id INT NOT NULL PRIMARY KEY, col_a VARCHAR(100)); </syntaxhighlight> 考虑以下两个查询: <syntaxhighlight lang="mysql"> SELECT * FROM t WHERE id = POW(1,2); SELECT * FROM t WHERE id = FLOOR(1 + RAND() * 49); </syntaxhighlight> 由于与主键的相等性比较,两个查询似乎都使用了主键查找,但这仅适用于第一个查询: # 第一个查询始终最多产生一行,因为带有常量参数的“POW()”是常量值,用于索引查找。 # 第二个查询包含一个使用不确定函数“RAND()”的表达式,该函数在查询中不是常量,但实际上对于表 t 的每一行都有一个新值。因此,查询将读取表的每一行,评估每一行的谓词,并输出主键与随机值匹配的所有行。这可能是零行,一行或多行,具体取决于 id 列的值和 RAND() 序列中的值。 '''不确定性的影响不仅限于“SELECT”语句'''。此“UPDATE”语句使用非确定性函数来选择要修改的行: <syntaxhighlight lang="mysql"> UPDATE t SET col_a = some_expr WHERE id = FLOOR(1 + RAND() * 49); </syntaxhighlight> 大概的目的是最多更新主键与表达式匹配的一行。但是,它可能更新零,一或多个行,具体取决于 id 列的值和 RAND() 序列中的值。 刚刚描述的行为对性能和复制有影响: * 由于不确定函数不会产生恒定值,因此优化器无法使用其他可能适用的策略,例如索引查找。结果可能是全表扫描。 * InnoDB 可能升级为范围键锁定,而不是为一个匹配的行获取单个行锁定。 * 无法确定执行的更新对于复制是不安全的。 困难源于对表的每一行都对“RAND()”函数进行一次评估的事实。为了避免进行多功能评估,请使用以下技术之一: # 将包含不确定性函数的表达式移到单独的语句,将值保存在'''变量'''中。在原始语句中,'''将表达式替换为对变量的引用''',优化器可以将该变量视为常量值:【!】 <syntaxhighlight lang="mysql"> SET @keyval = FLOOR(1 + RAND() * 49); UPDATE t SET col_a = some_expr WHERE id = @keyval; </syntaxhighlight> # 将随机值分配给派生表中的'''变量'''。此技术使变量'''在 WHERE 子句中的比较中使用之前被分配一个值''': <syntaxhighlight lang="mysql"> SET optimizer_switch = 'derived_merge=off'; UPDATE t, (SELECT @keyval := FLOOR(1 + RAND() * 49)) AS dt SET col_a = some_expr WHERE id = @keyval; </syntaxhighlight> #: 如前所述,WHERE 子句中的不确定性表达式可能会阻止优化并导致表扫描。但是,如果其他表达式是确定性的,则可以部分优化 WHERE 子句。例如: #: <syntaxhighlight lang="mysql"> SELECT * FROM t WHERE partial_key=5 AND some_column=RAND(); </syntaxhighlight> #: 如果优化器可以使用“partial_key”来减少所选行的集合,则执行“RAND()”的次数将减少,从而减少了不确定性对优化的影响。 == 行构造函数表达式优化 == 行构造函数【???就是用函数表示条件语句???】允许'''同时比较多个值'''。 : 例如,以下两个语句在语义上是等效的: <syntaxhighlight lang="mysql"> SELECT * FROM t1 WHERE (column1,column2) = (1,1); SELECT * FROM t1 WHERE column1 = 1 AND column2 = 1; </syntaxhighlight> : 另外,优化器以'''相同的方式处理两个表达式'''。 如果行构造器的列不覆盖索引的前缀,则优化器不太可能使用可用索引。 示例:表在(c1, c2, c3)上具有主键: <syntaxhighlight lang="mysql"> CREATE TABLE t1 ( c1 INT, c2 INT, c3 INT, c4 CHAR(100), PRIMARY KEY(c1,c2,c3) ); </syntaxhighlight> 在此查询中,WHERE 子句使用索引中的所有列。但是,行构造器本身不包含索引前缀,因此优化器仅使用 c1:【“key_len=4”:c1的大小,而非联合主键的大小】 <syntaxhighlight lang="mysql" highlight="2,8,11,12"> mysql> EXPLAIN SELECT * FROM t1 WHERE c1=1 AND (c2,c3) > (1,1)\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: t1 partitions: NULL type: ref possible_keys: PRIMARY key: PRIMARY key_len: 4 ref: const rows: 3 filtered: 100.00 Extra: Using where </syntaxhighlight> 在这种情况下,使用等效的'''“非构造函数表达式”'''重写“行构造函数表达式”可能会导致更完整的索引使用。对于给定的查询,行构造函数 和等效的 非构造函数表达式 为:【???】 <syntaxhighlight lang="mysql"> (c2,c3) > (1,1) c2 > 1 OR ((c2 = 1) AND (c3 > 1)) </syntaxhighlight> 使用索引中的所有三列重写查询以使用非构造函数表达式会在优化器中产生结果:【“key_len=12”:非联合主键的大小】 <syntaxhighlight lang="mysql" highlight="2,8,11,12"> mysql> EXPLAIN SELECT * FROM t1 WHERE c1 = 1 AND (c2 > 1 OR ((c2 = 1) AND (c3 > 1)))\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: t1 partitions: NULL type: range possible_keys: PRIMARY key: PRIMARY key_len: 12 ref: NULL rows: 3 filtered: 100.00 Extra: Using where </syntaxhighlight> 因此,为了获得更好的结果,请'''避免将行构造函数与“AND”/“OR”表达式混合使用'''。 在某些情况下,优化器可以将范围访问方法应用于具有行构造函数参数的“IN()”表达式。 == '''避免全表扫描''' == 当 MySQL 使用全表扫描解析查询时,'''EXPLAIN'''的输出在“type”列中显示“'''ALL'''”【即,使用了“全表扫描”】。 这通常在以下情况下发生: # '''表太小'''了,执行表扫描比进行键查找要快。这对于行数少于10行且行长较短的表很常见。 # 对于索引列,'''“ON”或“WHERE”子句中没有可用的限制'''。 # 正将索引列与常量值进行比较,MySQL 已经计算出(基于索引树)'''常量覆盖了表的很大一部分,并且表扫描会更快'''。【???】 # 正在通过另一列使用'''基数低的键'''(许多行与键值匹配)。 #* 在这种情况下,MySQL 假定:通过使用键,它可能会执行许多键查找,而表扫描会更快。 对于小型表,表扫描通常是适当的,并且对性能的影响可以忽略不计。对于大型表,请尝试以下技术,以避免优化器错误地选择表扫描: # 使用“'''ANALYZE TABLE tbl_name'''”更新扫描表的 key 分布。 # 对被扫描的表使用“'''FORCE INDEX'''”来告诉 MySQL:与使用给定索引相比,表扫描非常昂贵: #: <syntaxhighlight lang="mysql"> SELECT * FROM t1, t2 FORCE INDEX (index_for_column) WHERE t1.col_name=t2.col_name; </syntaxhighlight> # 使用“--max-seeks-for-key=1000”选项启动 mysqld,或使用“SET max_seeks_for_key=1000”告诉优化器假定没有键扫描会导致超过 1000 个键查找。
返回至“
MySQL 优化:优化 SQL 语句:优化 SELECT 语句
”。
导航菜单
个人工具
登录
命名空间
页面
讨论
大陆简体
已展开
已折叠
查看
阅读
查看源代码
查看历史
更多
已展开
已折叠
搜索
导航
首页
最近更改
随机页面
MediaWiki帮助
笔记
服务器
数据库
后端
前端
工具
《To do list》
日常
阅读
电影
摄影
其他
Software
Windows
WIKIOE
所有分类
所有页面
侧边栏
站点日志
工具
链入页面
相关更改
特殊页面
页面信息