目录
本章介绍如何优化MySQL性能并提供示例。 优化涉及在多个级别配置,调整和测量性能。 根据您的工作角色(开发人员,DBA或两者的组合),您可以在单个SQL语句,整个应用程序,单个数据库服务器或多个联网数据库服务器的级别进行优化。 有时您可以主动并提前计划性能,而有时您可能会在出现问题后解决配置或代码问题。 优化CPU和内存使用还可以提高可伸缩性,允许数据库处理更多负载而不会降低速度。
数据库性能取决于数据库级别的多个因素,例如表,查询和配置设置。 这些软件构造导致硬件级别的CPU和I / O操作,您必须尽可能地最小化并尽可能高效。 在处理数据库性能时,首先要学习软件方面的高级规则和指南,并使用挂钟时间来衡量性能。 当您成为专家时,您将了解内部发生的更多信息,并开始测量诸如CPU周期和I / O操作之类的事情。
典型用户的目标是从现有的软件和硬件配置中获得最佳的数据库性能。 高级用户寻找改进MySQL软件本身的机会,或者开发自己的存储引擎和硬件设备来扩展MySQL生态系统。
使数据库应用程序快速运行的最重要因素是其基本设计:
表格结构合理吗? 特别是,列是否具有正确的数据类型,并且每个表是否具有适合工作类型的列? 例如,执行频繁更新的应用程序通常具有许多具有少量列的表,而分析大量数据的应用程序通常具有很少列的表。
是否有适当的 索引 来提高查询效率?
您是否为每个表使用适当的存储引擎,并利用您使用的每个存储引擎的优势和功能?
特别是,事务性存储引擎(例如
InnoDB
非
事务性存储引擎)的选择
MyISAM
对于性能和可伸缩性来说非常重要。
InnoDB
是新表的默认存储引擎。
实际上,高级
InnoDB
性能特征意味着
InnoDB
表通常优于更简单的
MyISAM
表,尤其是对于繁忙的数据库。
每个表是否使用适当的行格式?
此选择还取决于用于表的存储引擎。
特别是,压缩表使用较少的磁盘空间,因此需要较少的磁盘I / O来读取和写入数据。
压缩适用于具有
InnoDB
表的
所有类型的工作负载
以及只读
MyISAM
表。
应用程序是否使用适当的
锁定策略
?
例如,通过允许可能的共享访问,以便数据库操作可以并发运行,并在适当时请求独占访问,以便关键操作成为最高优先级。
同样,存储引擎的选择也很重要。
该
InnoDB
存储引擎处理大部分锁定的问题,而不需要您的参与,允许在数据库更好的并发,减少试验和调整的金额,让您的代码。
是否
正确
使用了用于缓存的
所有
内存区域
?
也就是说,足够大以容纳频繁访问的数据,但不能太大以至于它们会超载物理内存并导致分页。
要配置的主要内存区域是
InnoDB
缓冲池和
MyISAM
密钥缓存。
随着数据库变得越来越繁忙,任何数据库应用程序最终都会达到硬件限制。 DBA必须评估是否可以调整应用程序或重新配置服务器以避免这些 瓶颈 ,或者是否需要更多硬件资源。 系统瓶颈通常来自这些来源:
磁盘寻求。 磁盘需要一段时间才能找到一块数据。 对于现代磁盘,平均时间通常低于10毫秒,因此我们理论上可以做到大约100次寻找。 这个时间用新磁盘慢慢改善,并且很难针对单个表进行优化。 优化寻道时间的方法是将数据分配到多个磁盘上。
磁盘读写。 当磁盘位于正确位置时,我们需要读取或写入数据。 使用现代磁盘,一个磁盘可提供至少10-20MB / s的吞吐量。 这比搜索更容易优化,因为您可以从多个磁盘并行读取。
CPU周期。 当数据在主存储器中时,我们必须处理它以获得我们的结果。 与内存量相比具有大表是最常见的限制因素。 但是对于小桌子,速度通常不是问题。
内存带宽。 当CPU需要的数据量超过CPU缓存容量时,主内存带宽成为瓶颈。 对于大多数系统来说,这是一个不常见的瓶颈,但需要注意的是。
要在可移植的MySQL程序中使用面向性能的SQL扩展,您可以在
/*! */
注释分隔符
的语句中包含特定于MySQL的关键字
。
其他SQL服务器忽略注释的关键字。
有关编写注释的信息,请参见
第9.6节“注释语法”
。
数据库应用程序的核心逻辑是通过SQL语句执行的,无论是直接通过解释器发出还是通过API在后台提交。 本节中的调整准则有助于加速各种MySQL应用程序。 这些准则涵盖了读取和写入数据的SQL操作,一般SQL操作的幕后开销,以及特定方案(如数据库监视)中使用的操作。
查询以
SELECT
语句
的形式
执行数据库中的所有查找操作。
调整这些语句是首要任务,无论是为动态网页实现亚秒响应时间,还是为了产生巨大的隔夜报告而缩短工作时间。
此外
SELECT
语句,进行查询调谐技术也适用于结构,如
CREATE
TABLE...AS SELECT
,
INSERT
INTO...SELECT
和
WHERE
在条款
DELETE
的语句。
这些语句具有额外的性能考虑因素,因为它们将写操作与面向读取的查询操作相结合。
NDB Cluster支持连接下推优化,从而将合格连接完整地发送到NDB Cluster数据节点,在NDB Cluster数据节点中可以将它们分布在它们之间并且并行执行。 有关此优化的更多信息,请参阅 NDB下推连接的条件 ,
优化查询的主要考虑因素是:
要
SELECT ... WHERE
加快查询速度,首先要检查的是是否可以添加
索引
。
在
WHERE
子句中
使用的列上设置索引
,以加快评估,过滤和结果的最终检索。
为避免浪费磁盘空间,请构建一小组索引,以加速应用程序中使用的许多相关查询。
索引对于引用不同表的查询尤其重要,使用
联接
和
外键等功能
。
您可以使用该
EXPLAIN
语句来确定用于a的索引
SELECT
。
请参见
第8.3.1节“MySQL如何使用索引”
和
第8.8.1节“使用EXPLAIN优化查询”
。
隔离并调整查询的任何部分,例如函数调用,这会占用过多时间。 根据查询的结构,可以为结果集中的每一行调用一次函数,甚至可以为表中的每一行调用一次函数,从而大大减轻任何低效率。
最大限度地减少 查询中 的 全表扫描 数 ,尤其是对于大表。
通过
ANALYZE
TABLE
定期
使用
语句
使表统计信息保持最新
,因此优化程序具有构建高效执行计划所需的信息。
了解特定于每个表的存储引擎的调优技术,索引技术和配置参数。
双方
InnoDB
并
MyISAM
有两套准则的实现和维持查询高性能。
有关详细信息,请参见
第8.5.6节“优化InnoDB查询”
和
第8.6.1节“优化MyISAM查询”
。
您可以
InnoDB
使用
第8.5.3节“优化InnoDB只读事务”中
的技术
优化
表的
单查询事务
。
避免以难以理解的方式转换查询,尤其是在优化程序自动执行某些相同转换的情况下。
如果其中一个基本准则无法轻松解决性能问题,请通过阅读
EXPLAIN
计划并调整索引,
WHERE
子句,连接子句等来
调查特定查询的内部详细信息
。
(当您达到一定的专业水平时,阅读
EXPLAIN
计划可能是您每次查询的第一步。)
调整MySQL用于缓存的内存区域的大小和属性。
通过有效使用
InnoDB
缓冲池
,
MyISAM
密钥缓存和MySQL查询缓存,重复查询运行得更快,因为在第二次及以后的时间内从内存中检索结果。
即使对于使用高速缓存存储区域快速运行的查询,您仍可以进一步优化,以便它们需要更少的高速缓存,从而使您的应用程序更具可伸缩性。 可伸缩性意味着您的应用程序可以处理更多的并发用户,更大的请求等,而不会出现性能大幅下降的情况。
处理锁定问题,其中查询的速度可能会受到同时访问表的其他会话的影响。
本节讨论可以为处理
WHERE
子句
进行的优化
。
这些示例使用
SELECT
语句,但相同的优化适用
WHERE
于
DELETE
和
UPDATE
语句中的
子句
。
由于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 AND b = c)AND a = 5 - > 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或b = 6
在MySQL 8.0.14及更高版本中,这是在准备期间而不是在优化阶段期间进行的,这有助于简化连接。 有关 更多信息和示例, 请参见 第8.2.1.8节“外部连接优化” 。
索引使用的常量表达式仅计算一次。
从MySQL 8.0.16开始,对具有常量值的数值类型列进行比较,检查并折叠或删除无效或不合法值:
#CREATE TABLE t(c TINYINT UNSIGNED NOT NULL); SELECT * FROM t WHERE c«256; -yes SELECT * FROM t WHERE 1;
有关 更多信息 , 请参见 第8.2.1.13节“常量折叠优化” 。
COUNT(*)
在没有a的单个表上
WHERE
直接从表信息
MyISAM
和
MEMORY
表中
检索
。
NOT NULL
当仅与一个表一起使用时,
也可以对任何
表达式执行
此操作
。
早期检测无效常量表达式。
MySQL快速检测到某些
SELECT
语句是不可能的,并且不返回任何行。
HAVING
WHERE
如果您不使用
GROUP
BY
或聚合函数(
COUNT()
,
MIN()
等等),
则合并
。
对于连接中的每个表,
WHERE
构造
一个更简单
WHERE
的表来
快速
评估表,并尽快跳过行。
在查询中的任何其他表之前首先读取所有常量表。 常量表是以下任何一种:
一张空表或一行表。
与a
或
index
WHERE
上
的
子句一起
使用的表
,其中所有索引部分都与常量表达式进行比较并定义为
。
PRIMARY KEY
UNIQUE
NOT NULL
以下所有表都用作常量表:
SELECT * FROM t WHEREprimary_key
= 1; SELECT * FROM t1,t2 在哪里t1。primary_key
= 1和t2。primary_key
= t1.id;
通过尝试所有可能性,可以找到加入表格的最佳连接组合。
如果
ORDER BY
和
GROUP
BY
子句中的
所有列
都来自同一个表,则在加入时首先首选该表。
如果存在
ORDER BY
子句和不同的
GROUP BY
子句,或者如果
ORDER BY
或者
GROUP BY
包含连接队列中第一个表以外的表中的列,则会创建临时表。
如果使用
SQL_SMALL_RESULT
修饰符,MySQL使用内存临时表。
查询每个表索引,并使用最佳索引,除非优化程序认为使用表扫描更有效。 有一次,根据最佳索引是否跨越超过表的30%使用扫描,但固定百分比不再决定使用索引或扫描之间的选择。 优化器现在更复杂,并且基于其他因素(例如表大小,行数和I / O块大小)进行估算。
在某些情况下,MySQL甚至无需咨询数据文件即可从索引中读取行。 如果索引中使用的所有列都是数字,则仅使用索引树来解析查询。
在输出每一行之前,
HAVING
将跳过
与该
子句
不匹配的行
。
一些非常快的查询示例:
SELECT COUNT(*)FROMtbl_name
; SELECT MIN(key_part1
),MAX(key_part1
)FROMtbl_name
; SELECT MAX(key_part2
)FROMtbl_name
WHEREkey_part1
=constant
; SELECT ... FROMtbl_name
ORDER BYkey_part1
,key_part2
... LIMIT 10; SELECT ... FROMtbl_name
ORDER BYkey_part1
DESC,key_part2
DESC,... LIMIT 10;
MySQL仅使用索引树解析以下查询,假设索引列是数字:
SELECTkey_part1
,key_part2
FROMtbl_name
WHEREkey_part1
=val
; SELECT COUNT(*)FROMtbl_name
WHEREkey_part1
=val1
ANDkey_part2
=val2
; SELECTkey_part2
FROMtbl_name
GROUP BYkey_part1
;
以下查询使用索引来按排序顺序检索行,而不使用单独的排序传递:
SELECT ... FROMtbl_name
ORDER BYkey_part1
,key_part2
...; SELECT ... FROMtbl_name
ORDER BYkey_part1
DESC,key_part2
DESC,...;
的
range
访问方法使用单个索引来检索包含一个或若干个索引值的时间间隔内表行的子集。
它可用于单部分或多部分索引。
以下部分描述了优化程序使用范围访问的条件。
对于单部分索引,索引值间隔可以方便地由
WHERE
子句中
的相应条件
表示,表示为
范围条件
而不是
“
间隔”。
”
单部分索引的范围条件的定义如下:
前面描述中的 “ 常量值 ” 表示以下之一:
以下是
WHERE
子句中
具有范围条件的查询的一些示例
:
SELECT * FROM t1 在哪里key_col
> 1 ANDkey_col
<10; SELECT * FROM t1 WHEREkey_col
= 1 或key_col
(15,18,20); SELECT * FROM t1 在哪里key_col
'ab%' 或key_col
'BETWEEN'bar'和'foo';
在优化器常量传播阶段,一些非常量值可以转换为常量。
MySQL尝试从
WHERE
每个可能索引
的
子句中
提取范围条件
。
在提取过程期间,丢弃不能用于构建范围条件的条件,组合产生重叠范围的条件,并且去除产生空范围的条件。
请考虑以下语句,其中
key1
是索引列
nonkey
且未编入索引:
SELECT * FROM t1 WHERE (key1 <'abc'AND(key1 LIKE'abcde%'或key1 LIKE'%b'))或者 (key1 <'bar'和nonkey = 4)或 (key1 <'uux'和key1>'z');
密钥的提取过程
key1
如下:
从原始
WHERE
条款
开始
:
(key1 <'abc'AND(key1 LIKE'abcde%'或key1 LIKE'%b'))或者 (key1 <'bar'和nonkey = 4)或 (key1 <'uux'和key1>'z')
删除
nonkey = 4
并
key1
LIKE '%b'
因为它们不能用于范围扫描。
删除它们的正确方法是用它们替换它们
TRUE
,这样我们在进行范围扫描时不会错过任何匹配的行。
用
TRUE
产量
替换它们
:
(key1 <'abc'AND(key1 LIKE'abcde%'或TRUE))或 (key1 <'bar'和TRUE)或 (key1 <'uux'和key1>'z')
折叠条件始终为真或假:
(key1 LIKE 'abcde%' OR TRUE)
总是如此
(key1 < 'uux' AND key1 >
'z')
总是假的
用常数替换这些条件会产生:
(key1 <'abc'和TRUE)或(key1 <'bar'和TRUE)或(FALSE)
删除不必要的
TRUE
和
FALSE
常量会产生:
(key1 <'abc')或(key1 <'bar')
将重叠间隔组合成一个会产生用于范围扫描的最终条件:
(key1 <'bar')
通常(并且如前面的示例所示),用于范围扫描的条件比该
WHERE
子句的
限制性更小
。
MySQL执行额外的检查以过滤掉满足范围条件但不满足完整
WHERE
子句的行。
范围条件提取算法可以处理
任意深度的
嵌套
AND
/
OR
构造,并且其输出不依赖于条件在
WHERE
子句中
出现的顺序
。
MySQL不支持合并
range
空间索引
的
访问方法的
多个范围
。
要解决此限制,
除了将每个空间谓词放在不同的语句中之外
,您可以使用
UNION
具有相同
SELECT
语句的语句
SELECT
。
多部分索引的范围条件是单部分索引的范围条件的扩展。 多部分索引上的范围条件将索引行限制在一个或多个关键元组间隔内。 使用索引中的排序在一组关键元组上定义关键元组间隔。
例如,考虑定义为的多部分索引
,以及按键顺序列出的以下一组关键元组:
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
)<(1,+ INF,+ INF)key_part2
key_part3
间隔覆盖前面数据集中的第4,第5和第6个元组,并且可以由范围访问方法使用。
相反,条件
不定义单个间隔,并且不能由范围访问方法使用。
key_part3
=
'abc'
以下描述更详细地说明了范围条件如何适用于多部分索引。
对于
HASH
索引,可以使用包含相同值的每个间隔。
这意味着只能为以下形式的条件生成间隔:
key_part1
cmp
const1
和key_part2
cmp
const2
和...... AND ;key_partN
cmp
constN
这里
const1
,
const2
...是常数,
cmp
是一个
=
,
<=>
或者
IS NULL
比较运营商,以及条件覆盖所有指数部分。
(也就是说,有一些
N
条件,一个用于
N
-part索引的
每个部分
。)例如,以下是三部分
HASH
索引
的范围条件
:
key_part1
= 1 ANDkey_part2
IS NULL ANDkey_part3
='foo'
有关被认为是常量的定义,请参阅 单部分索引的范围访问方法 。
对于一个
BTREE
索引,以一定间隔可能是条件组合可用
AND
,其中每个条件使用的恒定值的关键部分进行比较
=
,
<=>
,
IS NULL
,
>
,
<
,
>=
,
<=
,
!=
,
<>
,
BETWEEN
,或
(其中
LIKE
'
pattern
''
不以通配符开头)。
可以使用间隔,只要可以确定包含与条件匹配的所有行的单个密钥元组(或者如果使用
pattern
'<>
或者
!=
使用
两个间隔
)。
只要比较运算符是
,或
=
,
优化程序就会尝试使用其他关键部分来确定间隔
。
如果操作是
,
,
,
,
,
,
,或者
,优化器使用它,但认为没有更多的关键部分。
对于以下表达式,优化程序将使用
第一个比较。
它也使用
<=>
IS NULL
>
<
>=
<=
!=
<>
BETWEEN
LIKE
=
>=
从第二次比较,但没有考虑其他关键部分,并没有使用间隔构造的第三个比较:
key_part1
='foo'ANDkey_part2
> = 10 ANDkey_part3
> 10
单个间隔是:
( '富',10,-INF)<( ,,key_part1
)<( '富',+ INF,+ INF)key_part2
key_part3
创建的间隔可能包含比初始条件更多的行。
例如,前面的间隔包括
('foo',
11, 0)
不满足原始条件的值。
如果包含区间内包含的行集的条件与其组合
OR
,则它们形成一个条件,该条件覆盖其间隔的并集中包含的一组行。
如果条件与
AND
它们
组合
,则它们形成一个条件,该条件覆盖其间隔的交集中包含的一组行。
例如,对于这个由两部分组成的索引:
(key_part1
= 1 ANDkey_part2
<2)OR(key_part1
> 5)
间隔是:
(1,-inf)<(key_part1
,key_part2
)<(1,2) (5,-inf)<(key_part1
,key_part2
)
在此示例中,第一行的间隔使用左边界的一个关键部分和右边界的两个关键部分。
第二行的间隔仅使用一个关键部分。
输出中
的
key_len
列
EXPLAIN
指示使用的密钥前缀的最大长度。
在某些情况下,
key_len
可能表示使用了关键部件,但这可能不是您所期望的。
假设
key_part1
并且
key_part2
可以
NULL
。
然后该
key_len
列显示以下条件的两个关键部分长度:
key_part1
> = 1 ANDkey_part2
<2
但是,事实上,条件转换为:
key_part1
> = 1并且key_part2
不是NULL
有关如何执行优化以组合或消除单部分索引上的范围条件的间隔的说明,请参阅单部分索引的 范围访问方法 。 对多部分索引的范围条件执行类似步骤。
考虑这些表达式,其中
col_name
是索引列:
col_name
IN(val1
,...,valN
)col_name
=val1
OR ... ORcol_name
=valN
如果
col_name
等于多个值中的任何一个,
则每个表达式都为真
。
这些比较是等式范围比较(其中
“
范围
”
是单个值)。
优化程序估计读取限定行的成本以进行相等范围比较,如下所示:
如果有唯一索引
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
。
在MySQL 8.0之前,除了使用
eq_range_index_dive_limit
系统变量
之外,没有办法跳过使用索引潜水来估计索引的有用性
。
在MySQL 8.0中,对于满足所有这些条件的查询,可以进行索引潜水跳过:
查询用于单个表,而不是多个表的连接。
存在单索引
FORCE INDEX
索引提示。
我们的想法是,如果强制使用索引,那么从潜在的索引中获取额外开销就无法获得任何好处。
该索引不是唯一的而不是
FULLTEXT
索引。
没有子查询。
不
DISTINCT
,
GROUP
BY
或
ORDER BY
条款存在。
对于
EXPLAIN
FOR
CONNECTION
,如果跳过索引潜水,则输出更改如下:
对于传统输出,
rows
和
filtered
值是
NULL
。
对于JSON输出,
rows_examined_per_scan
并且
rows_produced_per_join
不显示,
skip_index_dive_due_to_force
是
true
,并且成本计算不准确。
如果没有
FOR CONNECTION
,
EXPLAIN
则跳过索引潜水时输出不会更改。
在执行跳过索引潜水的查询后,
INFORMATION_SCHEMA.OPTIMIZER_TRACE
表中
的相应行
包含
index_dives_for_range_access
值
skipped_due_to_force_index
。
请考虑以下情形:
CREATE TABLE t1(f1 INT NOT NULL,f2 INT NOT NULL,PRIMARY KEY(f1,f2)); 插入t1值 (1,1),(1,2),(1,3),(1,4),(1,5), (2,1),(2,2),(2,3),(2,4),(2,5); INSERT INTO t1 SELECT f1,f2 + 5 FROM t1; INSERT INTO t1 SELECT f1,f2 + 10 FROM t1; INSERT INTO t1 SELECT f1,f2 + 20 FROM t1; INSERT INTO t1 SELECT f1,f2 + 40 FROM t1; 分析表t1; EXPLAIN SELECT f1,f2 FROM t1 WHERE f2> 40;
要执行此查询,MySQL可以选择索引扫描来获取所有行(索引包括要选择的所有列),然后应用
子句中
的
f2 > 40
条件
WHERE
以生成最终结果集。
范围扫描比完整索引扫描更有效,但在这种情况下不能使用,因为
f1
第一个索引列
没有条件
。
但是,从MySQL 8.0.13开始,优化器可以
f1
使用类似于松散索引扫描的称为Skip Scan的方法
对每个值执行多个范围扫描
(请参见
第8.2.1.16节“GROUP BY优化”
):
跳过第一个索引部分的不同值
f1
(索引前缀)。
f2 > 40
对剩余索引部分上的条件的
每个不同前缀值执行子范围扫描
。
对于前面显示的数据集,算法的运行方式如下:
获取第一个关键部分(
f1 = 1
)
的第一个不同值
。
根据第一个和第二个关键部分构造范围(
f1 = 1 AND f2 > 40
)。
执行范围扫描。
获取第一个关键部分(
f1 = 2
)
的下一个不同值
。
根据第一个和第二个关键部分构造范围(
f1 = 2 AND f2 > 40
)。
执行范围扫描。
使用此策略会减少访问行的数量,因为MySQL会跳过不符合每个构造范围的行。 此Skip Scan访问方法适用于以下条件:
表T具有至少一个具有形式的关键部分的复合索引([A_1,...,A_
k
,] B_1,...,B_
m
,C [,D_1,...,D_
n
])。
关键部分A和D可能是空的,但B和C必须是非空的。
查询仅引用一个表。
查询不使用
GROUP BY
或
DISTINCT
。
该查询仅引用索引中的列。
A_1,...,A_的
k
谓词必须是等式谓词,它们必须是常量。
这包括
IN()
运营商。
查询必须是连接查询;
即,
AND
的
OR
条件:
(
cond1
(key_part1
)
OR
cond2
(key_part1
))
AND
(cond1
(key_part2
)
OR ...) AND ...
C上必须有一个范围条件。
允许D列条件。 D上的条件必须与C上的范围条件一起使用。
EXPLAIN
输出中
指示使用Skip Scan
如下:
Using index for skip scan
在
Extra
列中表示使用松散索引Skip Scan访问方法。
如果索引可用于Skip Scan,则索引应在
possible_keys
列中
可见
。
在
"skip scan"
此格式
的
元素的
优化程序跟踪输出中指示使用Skip Scan
:
“skip_scan_range”:{ “type”:“skip_scan”, “指数”:index_used_for_skip_scan
, “key_parts_used_for_access”:[key_parts_used_for_access
], “范围”:[range
] }
您可能还会看到一个
"best_skip_scan_summary"
元素。
如果选择“跳过扫描”作为最佳范围访问变体,
"chosen_range_access_summary"
则会写入a。
如果选择“跳过扫描”作为总体最佳访问方法,
"best_access_path"
则存在元素。
使用Skip Scan取决于
系统变量
skip_scan
标志的
optimizer_switch
值。
请参见
第8.9.2节“可切换的优化”
。
默认情况下,此标志为
on
。
要禁用它,请设置
skip_scan
为
off
。
除了使用
optimizer_switch
系统变量控制优化程序在会话范围内使用Skip Scan之外,MySQL还支持优化程序提示,以便在每个语句的基础上影响优化程序。
请参见
第8.9.3节“优化程序提示”
。
优化器能够将范围扫描访问方法应用于此表单的查询:
SELECT ... FROM t1 WHERE(col_1,col_2)IN(('a','b'),('c','d'));
以前,对于要使用的范围扫描,有必要将查询编写为:
SELECT ... FROM t1 WHERE(col_1 ='a'和col_2 ='b') 或(col_1 ='c'和col_2 ='d');
要使优化器使用范围扫描,查询必须满足以下条件:
有关优化程序和行构造函数的更多信息,请参见 第8.2.1.21节“行构造函数表达式优化”
要控制范围优化程序可用的内存,请使用
range_optimizer_max_mem_size
系统变量:
值为0表示 “ 无限制。 ”
值大于0时,优化程序会在考虑范围访问方法时跟踪消耗的内存。
如果要超过指定的限制,则放弃范围访问方法,并考虑其他方法,包括全表扫描。
这可能不太理想。
如果发生这种情况,会发生以下警告(
N
当前
range_optimizer_max_mem_size
值
在哪里
):
警告3170内存容量的N
字节数
超出'range_optimizer_max_mem_size'。范围
此查询未执行优化。
对于
UPDATE
和
DELETE
语句,如果优化器回退到全表扫描并且
sql_safe_updates
启用
了
系统变量,则会发生错误而不是警告,因为实际上没有使用任何键来确定要修改的行。
有关更多信息,请参阅
使用安全更新模式(--safe-updates)
。
对于超出可用范围优化内存并且优化程序回退到不太理想的计划的单个查询,增加该
range_optimizer_max_mem_size
值可以提高性能。
要估计处理范围表达式所需的内存量,请使用以下准则:
对于诸如以下的简单查询,其中有一个候选键用于范围访问方法,每个谓词组合
OR
使用大约230个字节:
SELECT COUNT(*)FROM t
其中a = 1或a = 2或a = 3 OR .. a = N
;
类似地,对于诸如以下的查询,每个谓词组合
AND
使用大约125个字节:
SELECT COUNT(*)FROM t
在哪里a = 1 AND b = 1 AND c = 1 ... N
;
对于带
IN()
谓词
的查询
:
SELECT COUNT(*)FROM t IN(1,2,...,M
)和B IN(1,2,...,N
);
IN()
列表
中的每个文字值都
计为一个谓词组合
OR
。
如果有两个
IN()
列表,则组合的谓词
OR
数是每个列表中文字值的数量的乘积。
因此,与
OR
前一种情况
相结合的谓词数
是
M
×
N
。
该
指数合并
访问方法检索与多行
range
扫描和他们的结果合并到一个。
此访问方法仅合并来自单个表的索引扫描,而不是跨多个表扫描。
合并可以生成其基础扫描的联合,交叉或交叉联合。
可以使用Index Merge的示例查询:
SELECT * FROMtbl_name
WHEREkey1
= 10 ORkey2
= 20; SELECT * FROMtbl_name
WHERE(key1
= 10 ORkey2
= 20)ANDnon_key
= 30; SELECT * FROM t1,t2 WHERE(t1。INkey1
(1,2)ORkey2
t1。LIKE'value
%') 和t2。key1
= t1。some_col
; SELECT * FROM t1,t2 在哪里t1。key1
= 1 AND(t2。key1
= t1。some_col
或者t2。key2
= t1。some_col2
);
索引合并优化算法具有以下已知限制:
在
EXPLAIN
输出中,索引合并方法显示
index_merge
在
type
列中。
在这种情况下,该
key
列包含使用的索引列表,并
key_len
包含这些索引的最长关键部分的列表。
Index Merge访问方法有几种算法,它们显示在
输出
Extra
字段中
EXPLAIN
:
Using intersect(...)
Using union(...)
Using sort_union(...)
以下部分更详细地描述了这些算法。 优化器根据各种可用选项的成本估算在不同的可能索引合并算法和其他访问方法之间进行选择。
当
WHERE
子句在不同的键上组合
时
转换为多个范围条件
时,此访问算法适用
AND
,并且每个条件都是以下条件之一:
N
这个表单的
一个
-part表达式,其中索引具有完全
N
部分(即,所有索引部分都被覆盖):
key_part1
=const1
ANDkey_part2
=const2
... ANDkey_partN
=constN
InnoDB
表
的主键上的任何范围条件
。
例子:
SELECT * FROMinnodb_table
WHEREprimary_key
<10 ANDkey_col1
= 20; SELECT * FROMtbl_name
WHEREkey1_part1
= 1 ANDkey1_part2
= 2 ANDkey2
= 2;
索引合并交集算法对所有使用的索引执行同时扫描,并生成从合并索引扫描接收的行序列的交集。
如果查询中使用的所有列都被使用的索引覆盖,则不会检索完整的表行(
在这种情况下
EXPLAIN
输出包含
Using index
在
Extra
字段中)。
以下是此类查询的示例:
SELECT COUNT(*)FROM t1 WHERE key1 = 1 AND key2 = 1;
如果使用的索引未涵盖查询中使用的所有列,则仅在满足所有使用的键的范围条件时才检索完整行。
如果其中一个合并条件是
InnoDB
表
的主键上的条件
,则它不用于行检索,而是用于过滤掉使用其他条件检索的行。
该算法的标准类似于索引合并交集算法的标准。
当表的
WHERE
子句在不同的键上组合时转换为多个范围条件
时,该算法适用
OR
,并且每个条件都是以下条件之一:
N
这个表单的
一个
-part表达式,其中索引具有完全
N
部分(即,所有索引部分都被覆盖):
key_part1
=const1
ANDkey_part2
=const2
... ANDkey_partN
=constN
InnoDB
表
的主键上的任何范围条件
。
索引合并交集算法适用的条件。
例子:
SELECT * FROM t1 WHEREkey1
= 1 ORkey2
= 2 ORkey3
= 3; SELECT * FROMinnodb_table
WHERE(key1
= 1 ANDkey2
= 2) OR(key3
='foo'ANDkey4
='bar')ANDkey5
= 5;
当
WHERE
子句转换为多个范围条件
时,此访问算法适用
OR
,但索引合并并集算法不适用。
例子:
SELECT * FROMtbl_name
WHEREkey_col1
<10 ORkey_col2
<20; SELECT * FROMtbl_name
WHERE(key_col1
> 10 ORkey_col2
= 20)ANDnonkey_col
= 30;
sort-union算法和union算法之间的区别在于sort-union算法必须首先获取所有行的行ID,然后在返回任何行之前对它们进行排序。
索引合并的使用是受价值
index_merge
,
index_merge_intersection
,
index_merge_union
,和
index_merge_sort_union
该旗
optimizer_switch
系统变量。
请参见
第8.9.2节“可切换的优化”
。
默认情况下,所有这些标志都是
on
。
要仅启用某些算法,请设置
index_merge
为
off
,并仅启用应允许的其他
算法
。
除了使用
optimizer_switch
系统变量来控制优化程序在会话范围内使用索引合并算法之外,MySQL还支持优化程序提示以基于每个语句影响优化程序。
请参见
第8.9.3节“优化程序提示”
。
这种优化提高了非索引列和常量之间直接比较的效率。
在这种情况下,将条件
“
下推
”
到存储引擎以进行评估。
此优化只能由
NDB
存储引擎使用。
对于NDB Cluster,此优化可以消除在群集的数据节点和发出查询的MySQL服务器之间通过网络发送不匹配行的需要,并且可以将查询的使用速度提高5到10倍。条件下推可能但不使用。
假设NDB Cluster表定义如下:
CREATE TABLE t1( 一个INT, b INT, KEY(一) )ENGINE = NDB;
条件下推可以用于查询,例如此处显示的查询,其中包括非索引列和常量之间的比较:
SELECT a,b FROM t1 WHERE b = 10;
条件下推的使用可以在输出中看到
EXPLAIN
:
MySQL的> EXPLAIN SELECT a,b FROM t1 WHERE b = 10\G
*************************** 1。排******************** *******
id:1
select_type:SIMPLE
表:t1
类型:全部
possible_keys:NULL
key:NULL
key_len:NULL
ref:NULL
行:10
额外:使用推动条件的地方
但是,条件下推 不能 与这两个查询中的任何一个一起使用:
SELECT a,b FROM t1 WHERE a = 10; SELECT a,b FROM t1 WHERE b + 1 = 10;
条件下推不适用于第一个查询,因为列上存在索引
a
。
(索引访问方法将更有效,因此将优先选择条件下推。)条件下推不能用于第二个查询,因为涉及非索引列的比较
b
是间接的。
(但是,如果您
b + 1 =
10
要
b = 9
在
WHERE
条款中
减少条件,则可以应用条件下推
。)
当索引列与使用
>
或
<
运算符
的常量进行比较时,也可以使用条件下推
:
MySQL的> EXPLAIN SELECT a, b FROM t1 WHERE a < 2\G
*************************** 1。排******************** *******
id:1
select_type:SIMPLE
表:t1
类型:范围
possible_keys:a
关键:a
key_len:5
ref:NULL
行:2
额外:使用推动条件的地方
其他支持的条件下推比较包括以下内容:
column
[NOT] LIKE
pattern
pattern
必须是包含要匹配的模式的字符串文字;
有关语法,请参见
第12.5.1节“字符串比较函数”
。
column
IS [NOT]
NULL
column
IN
(value_list
)
每个项目
value_list
必须是一个常量,字面值。
column
BETWEEN
constant1
AND
constant2
constant1
并且
constant2
每个都必须是一个恒定的字面值。
在前面列表中的所有情况中,条件可以转换为列和常量之间的一个或多个直接比较的形式。
默认情况下启用发动机状态下推。
要在服务器启动时禁用它,请设置
optimizer_switch
系统变量。
例如,在
my.cnf
文件中,使用以下行:
的[mysqld] optimizer_switch = engine_condition_pushdown =关
在运行时,禁用条件下推,如下所示:
SET optimizer_switch ='engine_condition_pushdown = off';
限制。 发动机状态下推受以下限制:
条件下推仅由
NDB
存储引擎
支持
。
列可以仅与常量进行比较; 但是,这包括评估为常量值的表达式。
要与列进行比较的字符串值必须使用与列相同的排序规则。
不直接支持联接;
涉及多个表的条件在可能的情况下单独推送。
使用扩展
EXPLAIN
输出来确定实际按下哪些条件。
请参见
第8.8.3节“扩展EXPLAIN输出格式”
。
以前,条件下推仅限于引用条件被推送到的同一表中的列值的术语。 从NDB 8.0.16开始,查询计划中较早的表中的列值也可以从推送条件中引用。 这减少了在连接处理期间SQL节点必须处理的行数。 过滤也可以在LDM线程中并行执行,而不是在单个 mysqld 进程中执行。 这有可能大大提高查询性能。
在以下两种情况下,连接算法不能与先前表中的引用列组合:
当任何前面提到的表都在连接缓冲区中时。 在这种情况下,从扫描过滤表中检索的每一行都与缓冲区中的每一行进行匹配。 这意味着在生成扫描过滤器时,没有单个特定行可从中获取列值。
当列来自推送连接中的子操作时。 这是因为在生成扫描过滤器时尚未检索从连接中的祖先操作引用的行。
索引条件下推(ICP)是对MySQL使用索引从表中检索行的情况的优化。
如果没有ICP,存储引擎会遍历索引以查找基表中的行,并将它们返回给MySQL服务器,该服务器会评估
WHERE
行
的
条件。
启用ICP后,如果
WHERE
只使用索引中的列来评估
部分
条件,MySQL服务器会推送这部分内容。
WHERE
条件下到存储引擎。
然后,存储引擎使用索引条目评估推送的索引条件,并且仅当满足该条件时才从表中读取行。
ICP可以减少存储引擎必须访问基表的次数以及MySQL服务器必须访问存储引擎的次数。
指数条件下推优化的适用性受以下条件限制:
ICP用于
range
,
ref
,
eq_ref
,和
ref_or_null
访问方法时,有必要访问完整的表行。
对于
InnoDB
表,ICP仅用于二级索引。
ICP的目标是减少全行读取的数量,从而减少I / O操作。
对于
InnoDB
聚簇索引,已将完整记录读入
InnoDB
缓冲区。
在这种情况下使用ICP不会降低I / O.
在虚拟生成列上创建的二级索引不支持ICP。
InnoDB
支持虚拟生成列上的二级索引。
引用子查询的条件无法下推。
无法推下涉及存储函数的条件。 存储引擎无法调用存储的函数。
触发条件无法下推。 (有关触发条件的信息,请参见 第8.2.2.3节“使用EXISTS策略优化子查询” 。)
要了解此优化的工作原理,请首先考虑在不使用索引条件下推时索引扫描的进度:
获取下一行,首先读取索引元组,然后使用索引元组找到并读取整个表行。
测试
WHERE
适用于此表
的
条件
部分
。
根据测试结果接受或拒绝该行。
使用索引条件下推,扫描会像这样进行:
获取下一行的索引元组(但不是完整的表行)。
测试
WHERE
适用于此表
的
条件
部分,
并且只能使用索引列进行检查。
如果不满足条件,则继续下一行的索引元组。
如果满足条件,请使用索引元组来查找并读取整个表行。
测试
WHERE
适用于此表
的
条件
的剩余部分
。
根据测试结果接受或拒绝该行。
EXPLAIN
使用“索引条件下推”时,
输出显示
Using index condition
在
Extra
列中。
它没有显示,
Using index
因为当必须读取完整的表行时,这不适用。
假设一个表包含有关人员及其地址的信息,并且该表的索引定义为
INDEX (zipcode, lastname, firstname)
。
如果我们知道一个人的
zipcode
价值但不确定姓氏,我们可以这样搜索:
选择*来自人 WHERE zipcode ='95054' AND lastname LIKE'%etrunia%' 和地址LIKE'%Main Street%';
MySQL可以使用索引来扫描人
zipcode='95054'
。
第二部分(
lastname LIKE '%etrunia%'
)不能用于限制必须扫描的行数,因此如果没有Index Condition Pushdown,此查询必须为所有拥有的人检索完整的表行
zipcode='95054'
。
使用索引条件下推,MySQL
lastname LIKE '%etrunia%'
在读取整个表行之前
检查该
部分。
这样可以避免读取与索引元组相对应的完整行,这些行匹配
zipcode
条件而不是
lastname
条件。
默认情况下启用索引条件下推。
可以
optimizer_switch
通过设置
index_condition_pushdown
标志
来控制
系统变量
:
SET optimizer_switch ='index_condition_pushdown = off'; SET optimizer_switch ='index_condition_pushdown = on';
请参见 第8.9.2节“可切换的优化” 。
MySQL使用嵌套循环算法或其变体在表之间执行连接。
一个简单的嵌套循环连接(NLJ)算法一次一个循环地从第一个表中读取行,将每一行传递给一个嵌套循环,该循环处理连接中的下一个表。 这个过程重复多次,因为还有待连接的表。
假设三个表之间的连接
t1
,
t2
以及
t3
是使用以下类型的连接来执行:
表连接类型 t1范围 t2 ref t3 ALL
如果使用简单的NLJ算法,则连接处理如下:
对于t1匹配范围中的每一行{ 对于t2中匹配引用键的每一行{ 对于t3中的每一行{ 如果行满足连接条件,则发送给客户端 } } }
因为NLJ算法一次一行地从外循环到内循环传递行,所以它通常多次读取在内循环中处理的表。
块嵌套循环(BNL)连接算法使用外部循环中读取的行的缓冲来减少必须读取内部循环中的表的次数。 例如,如果将10行读入缓冲区并将缓冲区传递给下一个内部循环,则可以将内循环中读取的每一行与缓冲区中的所有10行进行比较。 这将内表必须读取的次数减少一个数量级。
MySQL join缓存具有以下特征:
当连接类型为
(
ALL
或
index
换句话说,当不能使用任何可能的键,并且分别完成数据或索引行的完全扫描时),或者,
可以使用连接缓冲
range
。
缓冲的使用也适用于外连接,如
第8.2.1.11节“块嵌套循环和批量密钥访问连接”中所述
。
只有连接感兴趣的列存储在其连接缓冲区中,而不是整行。
的
join_buffer_size
系统变量来确定每个的大小联接缓冲液用于处理查询。
为可以缓冲的每个连接分配一个缓冲区,因此可以使用多个连接缓冲区处理给定查询。
在执行连接之前分配连接缓冲区,并在查询完成后释放。
对于之前针对NLJ算法(不使用缓冲)描述的示例连接,使用join buffering进行如下连接:
对于t1匹配范围中的每一行{ 对于t2中匹配引用键的每一行{ 存储在连接缓冲区中使用t1,t2中的列 如果缓冲区已满{ 对于t3中的每一行{ 对于连接缓冲区中的每个t1,t2组合{ 如果行满足连接条件,则发送给客户端 } } 空连接缓冲区 } } } 如果缓冲区不为空{ 对于t3中的每一行{ 对于连接缓冲区中的每个t1,t2组合{ 如果行满足连接条件,则发送给客户端 } } }
如果
S
是每一个存储的大小
t1
,
t2
在加入缓冲液组合和
C
是组合在缓冲器中的数,次表的数量
t3
被扫描的是:
(S
*C
)/ join_buffer_size + 1
数
t3
扫描降低为价值
join_buffer_size
时增加,最高可达点
join_buffer_size
是大到足以容纳所有上一行组合。
在那时,通过使其变大不会获得速度。
表达联接的语法允许嵌套联接。 以下讨论涉及 第13.2.10.2节“JOIN语法”中 描述的连接语法 。
table_factor
与SQL标准相比,
语法
扩展了。
后者只接受
table_reference
,而不是在一对括号内的列表。
如果我们将
table_reference
项目
列表中的每个逗号
视为等同于内部
联接,则这是保守扩展
。
例如:
SELECT * FROM t1 LEFT JOIN(t2,t3,t4) ON(t2.a = t1.a AND t3.b = t1.b AND t4.c = t1.c)
相当于:
SELECT * FROM t1 LEFT JOIN(t2 CROSS JOIN t3 CROSS JOIN t4) ON(t2.a = t1.a AND t3.b = t1.b AND t4.c = t1.c)
在MySQL中,
CROSS JOIN
在语法上等同于
INNER JOIN
;
他们可以互相替换。
在标准SQL中,它们不等效。
INNER JOIN
与
ON
条款
一起使用
;
CROSS JOIN
否则使用。
通常,在仅包含内部联接操作的联接表达式中可以忽略括号。 考虑这个连接表达式:
t1 LEFT JOIN(t2 LEFT JOIN t3 ON t2.b = t3.b或t2.b IS NULL) ON t1.a = t2.a
删除括号并将操作分组到左侧后,该连接表达式将转换为此表达式:
(t1 LEFT JOIN t2 ON t1.a = t2.a)LEFT JOIN t3 ON t2.b = t3.b或t2.b IS NULL
然而,这两个表达并不相同。
看到这一点,假设表
t1
,
t2
以及
t3
具有以下状态:
表
t1
包含行
(1)
,
(2)
表
t2
包含行
(1,101)
表
t3
包含行
(101)
在这种情况下,第一个表达式返回结果集包括行
(1,1,101,101)
,
(2,NULL,NULL,NULL)
,而第二表达式返回的行
(1,1,101,101)
,
(2,NULL,NULL,101)
:
MySQL的>SELECT *
FROM t1
LEFT JOIN
(t2 LEFT JOIN t3 ON t2.b=t3.b OR t2.b IS NULL)
ON t1.a=t2.a;
+ ------ + ------ + ------ + ------ + | a | a | b | b | + ------ + ------ + ------ + ------ + | 1 | 1 | 101 | 101 | | 2 | NULL | NULL | NULL | + ------ + ------ + ------ + ------ + MySQL的>SELECT *
FROM (t1 LEFT JOIN t2 ON t1.a=t2.a)
LEFT JOIN t3
ON t2.b=t3.b OR t2.b IS NULL;
+ ------ + ------ + ------ + ------ + | a | a | b | b | + ------ + ------ + ------ + ------ + | 1 | 1 | 101 | 101 | | 2 | NULL | NULL | 101 | + ------ + ------ + ------ + ------ +
在以下示例中,外部联接操作与内部联接操作一起使用:
t1 LEFT JOIN(t2,t3)ON t1.a = t2.a
该表达式无法转换为以下表达式:
t1 LEFT JOIN t2 ON t1.a = t2.a,t3
对于给定的表状态,这两个表达式返回不同的行集:
MySQL的>SELECT *
FROM t1 LEFT JOIN (t2, t3) ON t1.a=t2.a;
+ ------ + ------ + ------ + ------ + | a | a | b | b | + ------ + ------ + ------ + ------ + | 1 | 1 | 101 | 101 | | 2 | NULL | NULL | NULL | + ------ + ------ + ------ + ------ + MySQL的>SELECT *
FROM t1 LEFT JOIN t2 ON t1.a=t2.a, t3;
+ ------ + ------ + ------ + ------ + | a | a | b | b | + ------ + ------ + ------ + ------ + | 1 | 1 | 101 | 101 | | 2 | NULL | NULL | 101 | + ------ + ------ + ------ + ------ +
因此,如果我们在带有外连接运算符的连接表达式中省略括号,我们可能会更改原始表达式的结果集。
更准确地说,我们不能忽略左外连接操作的右操作数和右连接操作的左操作数中的括号。 换句话说,我们不能忽略外连接操作的内部表表达式的括号。 可以忽略其他操作数(外部表的操作数)的括号。
以下表达式:
(t1,t2)LEFT JOIN t3 ON P(t2.b,t3.b)
相当于这个表达式的任何表
t1,t2,t3
和任何条件
P
在属性
t2.b
和
t3.b
:
t1,t2 LEFT JOIN t3 ON P(t2.b,t3.b)
每当连接表达式(
joined_table
)中
的连接操作的执行顺序
不是从左到右时,我们就讨论嵌套连接。
请考虑以下查询:
SELECT * FROM t1 LEFT JOIN(t2 LEFT JOIN t3 ON t2.b = t3.b)ON t1.a = t2.a 在哪里t1.a> 1 SELECT * FROM t1 LEFT JOIN(t2,t3)ON t1.a = t2.a WHERE(t2.b = t3.b OR t2.b IS NULL)AND t1.a> 1
这些查询被认为包含这些嵌套连接:
t2 LEFT JOIN t3 ON t2.b = t3.b. t2,t3
在第一个查询中,嵌套连接由左连接操作形成。 在第二个查询中,它由内部联接操作形成。
在第一个查询中,可以省略括号:连接表达式的语法结构将指示连接操作的相同执行顺序。
对于第二个查询,不能省略括号,尽管这里的连接表达式可以在没有它们的情况下明确解释。
在我们的扩展语法中,
(t2,
t3)
第二个查询
的括号
是必需的,尽管理论上可以在没有它们的情况下解析查询:我们仍然会为查询提供明确的语法结构,因为
LEFT JOIN
它
ON
扮演了表达式的左右分隔符的角色
(t2,t3)
。
前面的例子证明了这些要点:
对于仅涉及内部联接(而不是外部联接)的联接表达式,可以删除括号并从左到右计算联接。 实际上,可以按任何顺序评估表。
通常,对于外连接或与内连接混合的外连接,情况也是如此。 删除括号可能会改变结果。
具有嵌套外连接的查询以与具有内连接的查询相同的管道方式执行。
更确切地说,利用了嵌套循环连接算法的变体。
回想一下嵌套循环连接执行查询的算法(参见
第8.2.1.6节“嵌套循环连接算法”
)。
假设3个表的连接查询
T1,T2,T3
具有以下形式:
SELECT * FROM T1 INNER JOIN T2 ON P1(T1,T2) INNER JOIN T3 ON P2(T2,T3) P(T1,T2,T3)
这里,
P1(T1,T2)
和
P2(T3,T3)
是一些连接条件(上表达),而
P(T1,T2,T3)
超过表中的列的条件
T1,T2,T3
。
嵌套循环连接算法将以下列方式执行此查询:
对于T1中的每一行t1 { 对于T2中的每一行t2,使得P1(t1,t2){ 对于T3中的每一行t3,使得P2(t2,t3){ IF P(t1,t2,t3){ T:= T 1 || || T2 T3; 输出t; } } } }
符号
t1||t2||t3
表示通过连接的行的列构成的行
t1
,
t2
和
t3
。
在以下某些示例中,
NULL
表名称的显示表示一行
NULL
用于该表的每一列。
例如,
t1||t2||NULL
指示通过连接的行的列构成的行
t1
和
t2
,以及
NULL
对于每一列
t3
。
据说这样一行是完成的
NULL
。
现在考虑使用嵌套外连接的查询:
SELECT * FROM T1 LEFT JOIN (T2 LEFT JOIN T3 ON P2(T2,T3)) ON P1(T1,T2) P(T1,T2,T3)
对于此查询,修改嵌套循环模式以获取:
对于T1中的每一行t1 { BOOL f1:= FALSE; 对于T2中的每一行t2,使得P1(t1,t2){ BOOL f2:= FALSE; 对于T3中的每一行t3,使得P2(t2,t3){ IF P(t1,t2,t3){ T:= T 1 || || T2 T3; 输出t; } F2 = TRUE; F1 = TRUE; } IF(!f2){ IF P(t1,t2,NULL){ T:= T 1 || || T2 NULL; 输出t; } F1 = TRUE; } } IF(!f1){ IF P(t1,NULL,NULL){ T:= T 1 || || NULL NULL; 输出t; } } }
通常,对于外连接操作中第一个内部表的任何嵌套循环,引入一个在循环之前关闭并在循环之后检查的标志。
当对于来自外部表的当前行,找到与表示内部操作数的表的匹配时,该标志被打开。
如果在循环周期结束时标志仍然关闭,则找不到外部表的当前行的匹配项。
在这种情况下,行由
NULL
内部表的列的值
补充
。
结果行将传递给输出的最终检查或下一个嵌套循环,但前提是该行满足所有嵌入外连接的连接条件。
在该示例中,嵌入了由以下表达式表示的外连接表:
(T2 LEFT JOIN T3 ON P2(T2,T3))
对于具有内部联接的查询,优化程序可以选择不同的嵌套循环顺序,例如:
对于T3中的每一行t3 { 对于T2中的每一行t2,使得P2(t2,t3){ 对于T1中的每一行t1,使得P1(t1,t2){ IF P(t1,t2,t3){ T:= T 1 || || T2 T3; 输出t; } } } }
对于具有外连接的查询,优化器只能选择这样的顺序,其中外部表的循环位于内部表的循环之前。
因此,对于具有外连接的查询,只能有一个嵌套顺序。
对于以下查询,优化程序将评估两种不同的嵌套。
在两个嵌套中,
T1
必须在外部循环中处理,因为它在外部联接中使用。
T2
并
T3
在内部联接中使用,因此必须在内部循环中处理联接。
但是,因为连接是内连接,
T2
并且
T3
可以按任何顺序处理。
SELECT * T1 LEFT JOIN(T2,T3)ON P1(T1,T2)和P2(T1,T3) P(T1,T2,T3)
一个嵌套评估
T2
,然后
T3
:
对于T1中的每一行t1 { BOOL f1:= FALSE; 对于T2中的每一行t2,使得P1(t1,t2){ 对于T3中的每一行t3,使得P2(t1,t3){ IF P(t1,t2,t3){ T:= T 1 || || T2 T3; 输出t; } F1:= TRUE } } IF(!f1){ IF P(t1,NULL,NULL){ T:= T 1 || || NULL NULL; 输出t; } } }
另一个嵌套评估
T3
,然后
T2
:
对于T1中的每一行t1 { BOOL f1:= FALSE; 对于T3中的每一行t3,使得P2(t1,t3){ 对于T2中的每一行t2,使得P1(t1,t2){ IF P(t1,t2,t3){ T:= T 1 || || T2 T3; 输出t; } F1:= TRUE } } IF(!f1){ IF P(t1,NULL,NULL){ T:= T 1 || || NULL NULL; 输出t; } } }
在讨论内部联接的嵌套循环算法时,我们省略了一些细节,这些细节对查询执行性能的影响可能很大。
我们没有提到所谓的
“
推倒
”
条件。
假设我们的
WHERE
条件
P(T1,T2,T3)
可以用一个连接公式表示:
P(T1,T2,T2)= C1(T1)和C2(T2)和C3(T3)。
在这种情况下,MySQL实际上使用以下嵌套循环算法来执行带有内连接的查询:
对于T1中的每一行t1,使得C1(t1){ 对于T2中的每一行t2,使得P1(t1,t2)和C2(t2){ 对于T3中的每一行t3,使得P2(t2,t3)和C3(t3){ IF P(t1,t2,t3){ T:= T 1 || || T2 T3; 输出t; } } } }
你看,每个合取的
C1(T1)
,
C2(T2)
,
C3(T3)
是最内环的推到最外环的地方进行评估。
如果
C1(T1)
是一个非常严格的条件,这种情况下推可能会大大减少
T1
传递给内部循环的
表中的行数
。
结果,查询的执行时间可能会大大改善。
对于具有外部联接的查询,
WHERE
仅在发现外部表中的当前行与内部表中的匹配项之后才检查条件。
因此,从内嵌套循环中推出条件的优化不能直接应用于具有外连接的查询。
这里我们必须引入条件下推谓词,这些谓词由遇到匹配时打开的标志保护。
回想一下外连接的这个例子:
P(T1,T2,T3)= C1(T1)和C(T2)和C3(T3)
对于该示例,使用受保护的下推条件的嵌套循环算法如下所示:
对于T1中的每一行t1,使得C1(t1){ BOOL f1:= FALSE; 对于T2中的每一行t2 这样P1(t1,t2)AND(f1?C2(t2):TRUE){ BOOL f2:= FALSE; 对于T3中的每一行t3 这样P2(t2,t3)AND(f1 && f2?C3(t3):TRUE){ IF(f1 && f2?TRUE:(C2(t2)AND C3(t3))){ T:= T 1 || || T2 T3; 输出t; } F2 = TRUE; F1 = TRUE; } IF(!f2){ IF(f1?TRUE:C2(t2)&& P(t1,t2,NULL)){ T:= T 1 || || T2 NULL; 输出t; } F1 = TRUE; } } IF(!f1 && P(t1,NULL,NULL)){ T:= T 1 || || NULL NULL; 输出t; } }
通常,可以从诸如
P1(T1,T2)
和的
连接条件中提取下推谓词
P(T2,T3)
。
在这种情况下,下推谓词也受到一个标志的保护,该标志阻止检查
NULL
由相应的外连接操作生成
的
- 实现行
的谓词
。
如果由
WHERE
条件中
的谓词引起,则禁止在同一嵌套连接中通过键从一个内部表访问另一个内部表
。
外连接包括
LEFT JOIN
和
RIGHT JOIN
。
MySQL实现
如下:
A
LEFT
JOIN B
join_specification
表
B
设置为依赖于表
A
和所有
依赖的表
A
。
表
A
设置为依赖于
条件
B
中使用的
所有表(除外
)
LEFT JOIN
。
该
LEFT JOIN
条件用于决定如何从表中检索行
B
。
(换句话说,
WHERE
不使用
该
子句中的
任何条件
。)
执行所有标准连接优化,但始终在所依赖的所有表之后读取表。 如果存在循环依赖关系,则会发生错误。
WHERE
执行
所有标准
优化。
如果
A
匹配该
WHERE
子句
的行中有一行
,但没有
B
匹配
ON
条件的
B
行,则会生成
一个额外的
行,并将所有列设置为
NULL
。
如果您用于
LEFT JOIN
查找某些表中不存在的行,并且您有以下测试:
在
col_name
IS
NULLWHERE
部件中,哪
col_name
一个是声明为的列
NOT NULL
,MySQL在找到后停止搜索更多行(对于特定的键组合)一行符合
LEFT
JOIN
条件。
该
RIGHT JOIN
实现类似于
LEFT JOIN
表格角色颠倒的实现。
右连接转换为等效的左连接,如
第8.2.1.9节“外连接简化”中所述
。
对于a
LEFT JOIN
,如果
WHERE
生成的
NULL
行
的
条件始终为false
,则将
LEFT
JOIN
其更改为内部联接。
例如,
WHERE
如果条款是在下面的查询错误的
t2.column1
是
NULL
:
SELECT * FROM t1 LEFT JOIN t2 ON(column1)WHERE t2.column2 = 5;
因此,将查询转换为内部联接是安全的:
SELECT * FROM t1,t2 WHERE t2.column2 = 5 AND t1.column1 = t2.column1;
在MySQL 8.0.14及更高版本中,
WHERE
由常量文字表达式引起的
微不足道的
条件在准备期间被移除,而不是在优化的后期阶段被移除,此时连接已经被简化。
早期删除琐碎的条件允许优化器将外连接转换为内连接;
这可以改善包含
WHERE
子句中
包含琐碎条件的外连接的查询计划
,例如:
SELECT * FROM t1 LEFT JOIN t2 ONcondition_1
WHEREcondition_2
OR 0 = 1
优化器现在在准备期间看到0 = 1始终为false,使得
OR 0 = 1
冗余,并将其删除,留下:
SELECT * FROM t1 LEFT JOIN t2 ONcondition_1
在哪里condition_2
现在,优化器可以将查询重写为内部联接,如下所示:
SELECT * FROM t1 JOIN t2 WHEREcondition_1
ANDcondition_2
现在优化器可以在表
t2
之前
使用表
,
t1
如果这样做会导致更好的查询计划。
要提供有关表连接顺序的提示,请使用优化程序提示;
请参见
第8.9.3节“优化程序提示”
。
或者,使用
STRAIGHT_JOIN
;
请参见
第13.2.10节“SELECT语法”
。
但是,
STRAIGHT_JOIN
可能会阻止使用索引,因为它会禁用半连接转换;
请参见
第8.2.2.1节“使用半连接转换优化子查询,派生表,视图引用和公用表表达式”
。
FROM
在许多情况下,简化了查询子句中的
表表达式
。
在解析器阶段,具有右外连接操作的查询将转换为仅包含左连接操作的等效查询。 在一般情况下,执行转换以使此右连接:
(T1,...)RIGHT JOIN(T2,...)ON P(T1,...,T2,...)
成为等效的左连接:
(T2,...)LEFT JOIN(T1,...)ON P(T1,...,T2,...)
表单的所有内连接表达式
T1 INNER JOIN
T2 ON P(T1,T2)
都被列表替换
T1,T2
,
P(T1,T2)
作为
WHERE
条件的连接(或嵌入连接的连接条件,如果有的话)连接。
当优化器评估外连接操作的计划时,它仅考虑计划,对于每个此类操作,在内部表之前访问外部表。 优化器选项是有限的,因为只有这样的计划才能使用嵌套循环算法执行外连接。
考虑一下这个表单的查询,其中
R(T2)
大大缩小了表中匹配行的数量
T2
:
SELECT * T1 LEFT JOIN T2 ON P1(T1,T2) P(T1,T2)和R(T2)
如果查询是按写入的方式执行的,那么优化器别无选择,只能在限制
T1
较多的
表
之前
访问限制较少的
表
T2
,这可能会产生效率非常低的执行计划。
相反,如果
WHERE
条件为空拒绝
,MySQL会将查询转换为不带外连接操作的查询
。
(也就是说,它将外连接转换为内连接。)如果一个条件计算为
FALSE
或
UNKNOWN
为任何
NULL
为该操作生成的已完成行,则该
条件对于外连接操作被称为空拒绝
。
因此,对于此外连接:
T1 LEFT JOIN T2 ON T1.A = T2.A
诸如此类的条件被null拒绝,因为对于任何已完成的
NULL
行(
T2
列设置为
NULL
),
它们不能为真
:
T2.B不是空的 T2.B> 3 T2.C <= T1.C T2.B <2或T2.C> 1
诸如此类的条件不会被拒绝,因为它们对于已完成的
NULL
行
可能是真的
:
T2.B是空的 T1.B <3或T2.B不为空 T1.B <3或T2.B> 3
检查外部联接操作的条件是否为空拒绝的一般规则很简单:
它是形式
A IS NOT NULL
,其中
A
是任何内部表的属性
它是一个谓词,包含对内部表的引用,该内部表计算
UNKNOWN
其参数之一的时间
NULL
它是一个包含null-rejected条件作为合取的连接
它是零拒绝条件的分离
对于查询中的一个外部联接操作,条件可以为null,而对于另一个条件,则不为null-rejected。
在此查询中,
WHERE
对于第二个外部联接操作
,
条件为空 - 拒绝,但对于第一个外部联接操作,条件不为空 - 拒绝:
SELECT * FROM T1 LEFT JOIN T2 ON T2.A = T1.A 左接点T3接通T3.B = T1.B 在哪里T3.C> 0
如果
WHERE
查询中的外连接操作
的
条件为空 - 拒绝,则外连接操作将由内连接操作替换。
例如,在前面的查询中,第二个外部联接是空的拒绝,可以由内部联接替换:
SELECT * FROM T1 LEFT JOIN T2 ON T2.A = T1.A INNER JOIN T3 ON T3.B = T1.B 在哪里T3.C> 0
对于原始查询,优化程序仅评估与单个表访问顺序兼容的计划
T1,T2,T3
。
对于重写的查询,它还考虑访问顺序
T3,T1,T2
。
一个外连接操作的转换可以触发另一个外连接操作的转换。 因此,查询:
SELECT * FROM T1 LEFT JOIN T2 ON T2.A = T1.A LE3 JOIN T3 ON T3.B = T2.B 在哪里T3.C> 0
首先转换为查询:
SELECT * FROM T1 LEFT JOIN T2 ON T2.A = T1.A INNER JOIN T3 ON T3.B = T2.B 在哪里T3.C> 0
这相当于查询:
SELECT * FROM(T1 LEFT JOIN T2 ON T2.A = T1.A),T3 T3.C> 0且T3.B = T2.B
剩余的外连接操作也可以由内连接替换,因为条件
T3.B=T2.B
为空拒绝。
这导致查询没有外部联接:
SELECT * FROM(T1 INNER JOIN T2 ON T2.A = T1.A),T3 T3.C> 0且T3.B = T2.B
有时,优化器会成功替换嵌入式外连接操作,但无法转换嵌入外连接。 以下查询:
SELECT * FROM T1 LEFT JOIN (T2 LEFT JOIN T3 ON T3.B = T2.B) 在T2.A = T1.A 在哪里T3.C> 0
转换为:
SELECT * FROM T1 LEFT JOIN (T2 INNER JOIN T3 ON T3.B = T2.B) 在T2.A = T1.A 在哪里T3.C> 0
这只能重写为仍然包含嵌入外连接操作的表单:
SELECT * FROM T1 LEFT JOIN (T2,T3) ON(T2.A = T1.A和T3.B = T2.B) 在哪里T3.C> 0
在查询中转换嵌入式外连接操作的任何尝试都必须考虑嵌入外连接和
WHERE
条件的
连接
条件。
在此查询中,
WHERE
嵌入式外连接
的
条件不会被拒绝,但嵌入外连接的连接条件
T2.A=T1.A AND
T3.C=T1.C
为空拒绝:
SELECT * FROM T1 LEFT JOIN (T2 LEFT JOIN T3 ON T3.B = T2.B) 在T2.A = T1.A和T3.C = T1.C 在哪里T3.D> 0或T1.D> 0
因此,查询可以转换为:
SELECT * FROM T1 LEFT JOIN (T2,T3) 在T2.A = T1.A和T3.C = T1.C和T3.B = T2.B 在哪里T3.D> 0或T1.D> 0
当表很大并且没有存储在存储引擎的缓存中时,使用辅助索引上的范围扫描读取行可能导致对基表的许多随机磁盘访问。 通过磁盘扫描多范围读取(MRR)优化,MySQL尝试通过首先扫描索引并收集相关行的密钥来减少范围扫描的随机磁盘访问次数。 然后对键进行排序,最后使用主键的顺序从基表中检索行。 磁盘扫描MRR的动机是减少随机磁盘访问的数量,而是实现对基表数据的更顺序扫描。
多范围读取优化提供以下好处:
MRR使数据行能够按顺序而不是按随机顺序访问,具体取决于索引元组。 服务器获取一组满足查询条件的索引元组,根据数据行ID顺序对它们进行排序,并使用排序的元组按顺序检索数据行。 这使数据访问更有效,更便宜。
MRR允许批量处理需要通过索引元组访问数据行的操作的密钥访问请求,例如范围索引扫描和使用连接属性索引的等连接。 MRR迭代一系列索引范围以获得合格的索引元组。 随着这些结果的累积,它们用于访问相应的数据行。 在开始读取数据行之前,不必获取所有索引元组。
在虚拟生成列上创建的二级索引不支持MRR优化。
InnoDB
支持虚拟生成列上的二级索引。
以下方案说明了MRR优化何时有利:
场景A:MRR可
用于索引范围扫描和等连接操作的表
InnoDB
和
MyISAM
表。
索引元组的一部分累积在缓冲区中。
缓冲区中的元组按其数据行ID排序。
根据排序的索引元组序列访问数据行。
场景B:MRR可
NDB
用于多范围索引扫描的表或通过属性执行等连接时。
范围的一部分(可能是单键范围)累积在提交查询的中心节点上的缓冲区中。
范围被发送到访问数据行的执行节点。
访问的行被打包到包中并发送回中央节点。
收到的包含数据行的包放在缓冲区中。
从缓冲区读取数据行。
使用MRR时
Extra
,
EXPLAIN
输出中
的
列
显示
Using MRR
。
InnoDB
MyISAM
如果不需要访问完整的表行来生成查询结果
,
则不要使用MRR。
如果结果可以完全基于索引元组中的信息(通过
覆盖索引
)生成,则情况就是这样;
MRR没有任何好处。
两个
optimizer_switch
系统变量标志提供了使用MRR优化的接口。
该
mrr
标志控制是否启用MRR。
如果
mrr
是enabled(
on
),则该
mrr_cost_based
标志控制优化器是否尝试在使用和不使用MRR(
on
)
之间进行基于成本的选择,
或者尽可能使用MRR(
off
)。
默认情况下,
mrr
是
on
和
mrr_cost_based
是
on
。
请参见
第8.9.2节“可切换的优化”
。
对于MRR,存储引擎使用
read_rnd_buffer_size
系统变量
的值
作为其缓冲区可分配的内存量的指导。
引擎最多使用
read_rnd_buffer_size
字节数,并确定一次传递中要处理的范围数。
在MySQL中,可以使用批量密钥访问(BKA)连接算法,该算法使用对连接表的索引访问和连接缓冲区。 BKA算法支持内连接,外连接和半连接操作,包括嵌套外连接。 BKA的优点包括提高连接性能,因为更高效的表扫描。 此外,先前仅用于内连接的块嵌套循环(BNL)连接算法已扩展,可用于外连接和半连接操作,包括嵌套外连接。
以下部分讨论了连接缓冲区管理,它是原始BNL算法扩展,扩展BNL算法和BKA算法的基础。 有关半连接策略的信息,请参见 第8.2.2.1节“使用半连接转换优化子查询,派生表,视图引用和公用表表达式”
MySQL可以使用连接缓冲区不仅可以执行内部联接而无需对内部表进行索引访问,还可以执行子查询展平后出现的外部联接和半联接。 此外,当存在对内部表的索引访问时,可以有效地使用连接缓冲区。
当存储感兴趣的行列的值时,联接缓冲区管理代码稍微更有效地利用连接缓冲区空间:如果行的值为,则不在缓冲区中为行列分配额外的字节
NULL
,并且为任何值分配最小字节数该
VARCHAR
类型。
该代码支持两种类型的缓冲区,常规和增量。
假设使用连接缓冲区
B1
来连接表
t1
,
t2
并且
t3
使用连接缓冲区
将此操作的结果与表
连接
B2
:
常规连接缓冲区包含每个连接操作数的列。
如果
B2
是一个普通的加入缓冲液,每行
r
投入
B2
由行的列
r1
从
B1
和匹配行的有趣列
r2
从表
t3
。
增量连接缓冲区仅包含第二个连接操作数生成的表行中的列。
也就是说,它是第一个操作数缓冲区中一行的增量。
如果
B2
是增量加入缓冲液,它包含行的有趣列
r2
有链接到一起排
r1
的
B1
。
增量连接缓冲区始终相对于早期连接操作的连接缓冲区是增量的,因此第一个连接操作的缓冲区始终是常规缓冲区。
在刚刚给出的实例中,缓冲
B1
用来连接表
t1
和
t2
必须是常规缓冲区。
用于连接操作的增量缓冲区的每一行仅包含要连接的表中一行的有趣列。
这些列通过引用第一个连接操作数生成的表中匹配行的有趣列进行扩充。
增量缓冲区中的多行可以引用同一行,
r
其
行
的列存储在先前的连接缓冲区中,只要所有这些行都匹配行
r
。
增量缓冲区可以减少从先前连接操作使用的缓冲区中复制列的频率。 这提供了缓冲空间的节省,因为在一般情况下,由第一连接操作数产生的行可以由第二连接操作数产生的若干行匹配。 不必从第一个操作数生成一行的多个副本。 由于复制时间的减少,增量缓冲区还可以节省处理时间。
系统变量
的
block_nested_loop
和
batched_key_access
标志
optimizer_switch
控制优化器如何使用块嵌套循环和批量密钥访问连接算法。
默认情况下,
block_nested_loop
是
on
和
batched_key_access
是
off
。
请参见
第8.9.2节“可切换的优化”
。
也可以应用优化程序提示;
请参阅
块嵌套循环和批量密钥访问算法的优化程序提示
。
有关半连接策略的信息,请参见 第8.2.2.1节“使用半连接转换优化子查询,派生表,视图引用和公用表表达式”
MySQL BNL算法的原始实现被扩展为支持外连接和半连接操作。
当使用连接缓冲区执行这些操作时,为缓冲区中的每一行提供匹配标志。
如果使用连接缓冲区执行外连接操作,则检查第二个操作数生成的表的每一行是否与连接缓冲区中的每一行匹配。
找到匹配项后,将形成一个新的扩展行(原始行加上第二个操作数中的列),并通过其余的连接操作发送以进一步扩展。
此外,启用缓冲区中匹配行的匹配标志。
在检查了要连接的表的所有行之后,将扫描连接缓冲区。
缓冲区中未启用其匹配标志的每一行都由
NULL
补充
扩展
(
NULL
第二个操作数中每列的值),并由剩余的连接操作发送以进一步扩展。
系统变量
的
block_nested_loop
标志
optimizer_switch
控制优化器如何使用块嵌套循环算法。
默认情况下
block_nested_loop
是
on
。
请参见
第8.9.2节“可切换的优化”
。
也可以应用优化程序提示;
请参阅
块嵌套循环和批量密钥访问算法的优化程序提示
。
在
EXPLAIN
输出端,为表使用BNL的当被所指
Extra
值包含
Using join buffer (Block Nested
Loop)
与所述
type
值是
ALL
,
index
,或
range
。
有关半连接策略的信息,请参见 第8.2.2.1节“使用半连接转换优化子查询,派生表,视图引用和公用表表达式”
MySQL实现了一种连接表的方法,称为批量密钥访问(BKA)连接算法。 当存在对第二个连接操作数生成的表的索引访问时,可以应用BKA。 与BNL连接算法一样,BKA连接算法使用连接缓冲区来累积由连接操作的第一个操作数生成的行的有趣列。 然后,BKA算法构建密钥以访问要为缓冲区中的所有行加入的表,并将这些密钥批量提交到数据库引擎以进行索引查找。 键通过多量程读取(MRR)接口提交给引擎(参见 第8.2.1.10节“多量程读取优化”) )。 在提交密钥之后,MRR引擎功能以最佳方式在索引中执行查找,获取由这些密钥找到的连接表的行,并开始向匹配行提供BKA连接算法。 每个匹配行与对连接缓冲区中的行的引用相耦合。
使用BKA时,值
join_buffer_size
定义了每个请求到存储引擎的批量密钥的大小。
缓冲区越大,对连接操作的右侧表的顺序访问就越多,这可以显着提高性能。
要使用BKA,
必须将系统变量
的
batched_key_access
标志
optimizer_switch
设置为
on
。
BKA使用MRR,因此
mrr
标志也必须是
on
。
目前,MRR的成本估算过于悲观。
因此,也有必要对
mrr_cost_based
要
off
用于要使用的BKA。
以下设置启用BKA:
MySQL的> SET optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on';
MRR功能有两种执行方式:
第一种方案用于传统的基于磁盘的存储引擎,例如
InnoDB
和
MyISAM
。
对于这些引擎,通常将来自连接缓冲区的所有行的键一次提交给MRR接口。
特定于引擎的MRR函数对提交的密钥执行索引查找,从中获取行ID(或主键),然后根据BKA算法的请求逐个获取所有这些选定行ID的行。
返回的每一行都带有一个关联引用,该引用允许访问连接缓冲区中的匹配行。
MRR函数以最佳方式获取行:它们以行ID(主键)顺序获取。
这提高了性能,因为读取是按磁盘顺序而不是随机顺序。
第二种方案用于远程存储引擎,例如
NDB
。
来自连接缓冲区的一部分行的密钥包及其关联由MySQL服务器(SQL节点)发送到MySQL Cluster数据节点。
作为回报,SQL节点接收匹配行的包(或多个包)以及相应的关联。
BKA连接算法获取这些行并构建新的连接行。
然后,一组新的密钥被发送到数据节点,并且返回的包中的行用于构建新的连接行。
该过程将继续,直到将来自联接缓冲区的最后一个密钥发送到数据节点,并且SQL节点已接收并加入与这些密钥匹配的所有行。
对于第一种情况,保留一部分连接缓冲区以存储由索引查找选择的行ID(主键),并作为参数传递给MRR功能。
没有特殊的缓冲区来存储为连接缓冲区中的行构建的密钥。 相反,为缓冲区中的下一行构建键的函数作为参数传递给MRR函数。
在
EXPLAIN
输出中,当
Extra
值包含
Using join buffer (Batched Key
Access)
且
type
值为
ref
或
时,表示将BKA用于表
eq_ref
。
除了使用
optimizer_switch
系统变量来控制优化程序在会话范围内使用BNL和BKA算法之外,MySQL还支持优化程序提示,以便在每个语句的基础上影响优化程序。
请参见
第8.9.3节“优化程序提示”
。
要使用BNL或BKA提示为外部联接的任何内部表启用联接缓冲,必须为外部联接的所有内部表启用联接缓冲。
在连接处理中,前缀行是从连接中的一个表传递到下一个表的那些行。 通常,优化器尝试在连接顺序的早期放置具有低前缀计数的表,以保持行组合的数量不会快速增加。 在某种程度上,优化程序可以使用有关从一个表中选择并传递给下一个表的行的条件的信息,它可以更准确地计算行估计并选择最佳执行计划。
如果没有条件筛选,则表的前缀行计数基于
WHERE
子句根据优化程序选择的访问方法选择
的估计行数
。
条件筛选使优化器能够使用
WHERE
访问方法未考虑
的
子句
中的其他相关条件
,从而改进其前缀行计数估计。
例如,即使可能存在可用于从连接中的当前表中选择行的基于索引的访问方法,但在该表中可能还存在其他条件。
WHERE
可以过滤(进一步限制)传递给下一个表的合格行的估计的子句。
只有在以下情况下,条件才会影响过滤估算:
它指的是当前表。
它取决于连接序列中早期表的常量值或值。
访问方法尚未考虑它。
在
EXPLAIN
输出中,
rows
列指示所选访问方法的行估计,该
filtered
列反映条件筛选的效果。
filtered
值以百分比表示。
最大值为100,这意味着不会对行进行过滤。
值从100开始减少表示过滤量增加。
前缀行计数(估计从连接中的当前表传递到下一个的行数)是
rows
和
filtered
值
的乘积
。
也就是说,前缀行计数是估计的行计数,通过估计的过滤效果减少。
例如,如果
rows
是1000并且
filtered
是20%,则条件过滤将估计的行计数1000减少到前缀行计数1000×20%= 1000×.2 = 200。
请考虑以下查询:
选择 * 来自员工JOIN部门ON employee.dept_no = department.dept_no WHERE employee.first_name ='John' AND employee.hire_date BETWEEN'2018-01-01'EN'2018-06-01';
假设数据集具有以下特征:
该
employee
表有1024行。
该
department
表有12行。
两个表都有一个索引
dept_no
。
该
employee
表有一个索引
first_name
。
8行符合以下条件
employee.first_name
:
employee.first_name ='约翰'
150行符合以下条件
employee.hire_date
:
employee.hire_date BETWEEN'2018-01-01'和'2018-06-01'
1行满足两个条件:
employee.first_name ='约翰' AND employee.hire_date BETWEEN'2018-01-01'和'2018-06-01'
没有条件过滤,
EXPLAIN
产生如下输出:
+ ---- + ------------ + -------- + ------------------ + --- ------ + --------- + ------ + ---------- + | id | 表| 类型| possible_keys | 关键| ref | 行| 过滤| + ---- + ------------ + -------- + ------------------ + --- ------ + --------- + ------ + ---------- + | 1 | 员工| ref | name,h_date,dept | 名字| const | 8 | 100.00 | | 1 | 部门| eq_ref | 主要| 主要| dept_no | 1 | 100.00 | + ---- + ------------ + -------- + ------------------ + --- ------ + --------- + ------ + ---------- +
因为
employee
,
name
索引
上的访问方法会
获取与名称匹配的8行
'John'
。
没有进行过滤(
filtered
是100%),因此所有行都是下一个表的前缀行:前缀行计数是
rows
×
filtered
= 8×100%= 8。
通过条件过滤,优化器还考虑
WHERE
了访问方法未考虑
的
子句中的
条件
。
在这种情况下,优化器使用试探法来估计所述的16.31%的滤波效果
BETWEEN
的条件下
employee.hire_date
。
结果,
EXPLAIN
产生如下输出:
+ ---- + ------------ + -------- + ------------------ + --- ------ + --------- + ------ + ---------- + | id | 表| 类型| possible_keys | 关键| ref | 行| 过滤| + ---- + ------------ + -------- + ------------------ + --- ------ + --------- + ------ + ---------- + | 1 | 员工| ref | name,h_date,dept | 名字| const | 8 | 16.31 | | 1 | 部门| eq_ref | 主要| 主要| dept_no | 1 | 100.00 | + ---- + ------------ + -------- + ------------------ + --- ------ + --------- + ------ + ---------- +
现在前缀行计数为
rows
×
filtered
= 8×16.31%= 1.3,这更接近地反映了实际数据集。
通常,优化器不会为最后一个连接表计算条件过滤效果(前缀行数减少),因为没有下一个表要传递行。
发生异常
EXPLAIN
:要提供更多信息,将为所有已连接的表(包括最后一个表)计算过滤效果。
要控制优化程序是否考虑其他过滤条件,请使用
系统变量
的
condition_fanout_filter
标志
optimizer_switch
(请参见
第8.9.2节“可切换的优化”
)。
默认情况下启用此标志,但可以禁用此标志以禁止条件筛选(例如,如果发现特定查询在没有它的情况下产生更好的性能)。
如果优化器过高估计条件过滤的影响,则性能可能比不使用条件过滤时更差。 在这种情况下,这些技术可能有所帮助:
如果未对列建立索引,请对其进行索引,以便优化程序具有有关列值分布的一些信息,并可以改进其行估计值。
同样,如果没有可用的列直方图信息,则生成直方图(请参见 第8.9.6节“优化程序统计信息” )。
更改连接顺序。
完成此操作的方法包括连接顺序优化器提示(请参见
第8.9.3节“优化程序提示”
),
STRAIGHT_JOIN
紧跟在
SELECT
和
STRAIGHT_JOIN
连接运算符之后。
禁用会话的条件筛选:
SET optimizer_switch ='condition_fanout_filter = off';
或者,对于给定查询,使用优化程序提示:
SELECT / * + SET_VAR(optimizer_switch ='condition_fanout_filter = off')* / ...
常量值和列值之间的比较,其中常量值超出范围或相对于列类型的错误类型现在在查询优化期间而不是逐行处理而不是在执行期间处理一次。
可以以这种方式处理的比较是
>
,
>=
,
<
,
<=
,
<>
/
!=
,
=
和
<=>
。
考虑以下语句创建的表:
CREATE TABLE t(c TINYINT UNSIGNED NOT NULL);
所述
WHERE
查询条件
SELECT * FROM t WHERE c < 256
包含积分常数256,其超出范围为
TINYINT UNSIGNED
柱。
以前,这是通过将两个操作数视为较大的类型来处理的,但现在,由于任何允许的值
c
小于常量,
WHERE
表达式可以折叠为
WHERE 1
,以便将查询重写为
SELECT * FROM t WHERE
1
。
这使得优化器可以
WHERE
完全
删除
表达式。
如果列可以
c
为空(即,仅定义为
TINYINT UNSIGNED
),则将重写查询,如下所示:
SELECT * FROM t WHERE ti not NULL
与支持的MySQL列类型相比,对常量执行折叠,如下所示:
整数列类型。 将整数类型与以下类型的常量进行比较,如下所述:
整数值。
如果常量超出列类型的范围,则比较将折叠为
1
或
IS NOT NULL
,如已显示。
如果常量是范围边界,则比较折叠为
=
。
例如(使用与已定义相同的表):
MySQL的>EXPLAIN SELECT * FROM t WHERE c >= 255;
*************************** 1。排******************** ******* id:1 select_type:SIMPLE 表:t 分区:NULL 类型:全部 possible_keys:NULL key:NULL key_len:NULL ref:NULL 行:5 过滤:20.00 额外:使用在哪里 1排,1警告(0.00秒) MySQL的>SHOW WARNINGS;
*************************** 1。排******************** ******* 等级:注意 代码:1003 消息:/ *从`test``t`中选择#1 * / select`test``t``ti` AS`ti`,其中(`test``t``ti` = 255) 1排(0.00秒)
浮点或定点值。
如果常数是十进制类型之一(例如
DECIMAL
,
REAL
,
DOUBLE
,或
FLOAT
),并有一个非零小数部分,它可以是不相等的;
相应地折叠。
对于其他比较,根据符号向上或向下舍入为整数值,然后执行范围检查和处理,如已经描述的整数 - 整数比较。
如果
REAL
值太小而无法表示,
DECIMAL
则根据符号舍入为.01或-.01,然后作为a处理
DECIMAL
。
字符串类型。
尝试将字符串值解释为整数类型,然后在整数值之间处理比较。
如果失败,请尝试将值作为a处理
REAL
。
DECIMAL或REAL列。 将十进制类型与以下类型的常量进行比较,如下所述:
整数值。
对列值的整数部分执行范围检查。
如果没有折叠结果,将常量转换
DECIMAL
为与列值相同的小数位数,然后将其检查为a
DECIMAL
(参见下一个)。
DECIMAL或REAL值。 检查溢出(即,常量的整数部分中的数字是否多于列的十进制类型所允许的数字)。 如果是这样,折叠。
如果常量具有比列的类型更重要的小数位数,则截断常量。
如果比较运算符是
=
或
<>
折叠。
如果操作员是
>=
或
<=
,则由于截断而调整操作员。
例如,如果列的类型是
DECIMAL(3,1)
,则
SELECT * FROM t WHERE f >= 10.13
变为
SELECT * FROM t WHERE f >
10.1
。
如果常量的十进制数字少于列的类型,则将其转换为具有相同位数的常量。
对于
REAL
值的
下溢
(即,表示它的分数位数太少),将常量转换为十进制0。
字符串值。
如果该值可以解释为整数类型,则按此处理。
否则,尝试将其处理为
REAL
。
FLOAT或DOUBLE列。
或
与常量相比较的值处理如下:
FLOAT(
m
,n
)DOUBLE(
m
,n
)
如果值溢出列的范围,则折叠。
如果该值大于
n
小数,则截断,在折叠期间进行补偿。
对于
=
和
<>
比较,倍至
TRUE
,
FALSE
或
IS [NOT]
NULL
如前文所述;
对于其他运营商,调整运营商。
如果该值超过
m
整数位数,则折叠。
限制。 在以下情况下不能使用此优化:
使用
BETWEEN
或
进行比较
IN
。
随着
BIT
使用日期或时间类型的列或列。
在准备语句的准备阶段,尽管可以在优化阶段应用实际执行准备好的语句。 这是因为在语句准备期间,常数的值尚不清楚。
MySQL能够执行相同的优化
,它可以使用
。
例如,MySQL能使用索引和范围来搜索
与
。
col_name
IS
NULL
col_name
=
constant_value
NULL
IS
NULL
例子:
SELECT * FROMtbl_name
WHERE为key_col
NULL; SELECT * FROMtbl_name
WHEREkey_col
<=> NULL; SELECT * FROMtbl_name
WHEREkey_col
=const1
ORkey_col
=const2
OR为key_col
NULL;
如果
WHERE
子句包含
声明为的列
的
条件,则
表达式将被优化掉。
当列可能
无论如何
产生时
(例如,如果它来自a右侧的表),则
不会发生此优化
。
col_name
IS
NULL
NOT NULL
NULL
LEFT JOIN
MySQL还可以优化组合
,这是一种在已解析子查询中很常见的形式。
显示
何时使用此优化。
col_name
=
expr
OR
col_name
IS NULLEXPLAIN
ref_or_null
此优化可以处理
IS
NULL
任何关键部分。
假设列
a
和
b
表
上有索引,优化的查询的一些示例
t2
:
SELECT * FROM t1 WHERE t1.a = expr
OR t1.a IS NULL;
SELECT * FROM t1,t2 WHERE t1.a = t2.a OR t2.a IS NULL;
SELECT * FROM t1,t2
WHERE(t1.a = t2.a OR t2.a IS NULL)AND t2.b = t1.b;
SELECT * FROM t1,t2
在哪里t1.a = t2.a AND(t2.b = t1.b或t2.b IS NULL);
SELECT * FROM t1,t2
WHERE(t1.a = t2.a AND t2.a IS NULL AND ...)
或(t1.a = t2.a AND t2.a IS NULL AND ...);
ref_or_null
首先对引用键执行读操作,然后单独搜索具有
NULL
键值的
行
。
优化只能处理一个
IS
NULL
级别。
在以下查询中,MySQL仅对表达式使用键查找,
(t1.a=t2.a AND t2.a IS
NULL)
并且无法使用关键部分
b
:
SELECT * FROM t1,t2 WHERE(t1.a = t2.a AND t2.a IS NULL) 或(t1.b = t2.b AND t2.b IS NULL);
本节描述MySQL何时可以使用索引来满足
ORDER BY
子句,
无法使用索引时使用
的
filesort
操作,以及优化器提供的执行计划信息
ORDER BY
。
一个
ORDER BY
有和没有
LIMIT
可能以不同的顺序返回行,在讨论
第8.2.1.18,“LIMIT查询优化”
。
在某些情况下,MySQL可能会使用索引来满足一个
ORDER BY
子句,并避免执行
filesort
操作时
涉及的额外排序
。
ORDER
BY
只要索引的所有未使用部分和所有额外
ORDER BY
列都是
WHERE
子句
中的常量,
即使
索引与索引不完全匹配,
也可以使用
索引
。
如果索引不包含查询访问的所有列,则仅在索引访问比其他访问方法更便宜时才使用索引。
假设存在索引
,则以下查询可以使用索引来解析该
部分。
优化程序是否实际执行此操作取决于读取索引是否比表扫描更有效,如果还必须读取索引中不存在的列。
(
key_part1
,
key_part2
)ORDER BY
在此查询中,索引on
使优化器能够避免排序:
(
key_part1
,
key_part2
)
SELECT * FROM t1 ORDER BYkey_part1
,key_part2
;
但是,查询使用
SELECT *
,可以选择比
key_part1
和
更多的列
key_part2
。
在这种情况下,扫描整个索引并查找表行以查找不在索引中的列可能比扫描表并对结果进行排序更昂贵。
如果是这样,优化器可能不会使用索引。
如果
SELECT *
仅选择索引列,则将使用索引并避免排序。
如果
t1
是
InnoDB
表,则表主键隐式地是索引的一部分,并且索引可用于解析
ORDER BY
此查询:
SELECTpk
,key_part1
,key_part2
从T1 ORDER BYkey_part1
,key_part2
;
在此查询中,
key_part1
是常量,因此通过索引访问的所有行都是
key_part2
有序的,并且
如果
子句具有足够的选择性以使索引范围扫描比表扫描更便宜,
则
索引将
避免排序
:
(
key_part1
,
key_part2
)WHERE
SELECT * FROM t1 WHEREkey_part1
=constant
ORDER BYkey_part2
;
在接下来的两个查询中,是否使用索引类似于
DESC
之前
未
显示
的相同查询
:
SELECT * FROM t1 ORDER BYkey_part1
DESC,key_part2
DESC; SELECT * FROM t1 WHEREkey_part1
= DESCconstant
订购key_part2
;
a中的两列
ORDER BY
可以在相同方向(两个
ASC
或两个
DESC
)或相反方向(一个
ASC
,一个
DESC
)
排序
。
索引使用的条件是索引必须具有相同的同质性,但不必具有相同的实际方向。
如果查询混合
ASC
和
DESC
,优化器可以使用的列的索引;如果该指数还采用了相应的混合升序和降序列:
SELECT * FROM t1 ORC BYkey_part1
DESC,key_part2
ASC;
优化器可以在(
key_part1
,
key_part2
)
上使用索引
,
如果
key_part1
是降序并且
key_part2
正在升序。
它还可以在这些列上使用索引(使用向后扫描),如果它
key_part1
是递增的并且
key_part2
正在降序。
请参见
第8.3.13节“降序索引”
在接下来的两个查询中,
key_part1
将其与常量进行比较。
如果该
WHERE
子句具有足够的选择性以使索引范围扫描比表扫描更便宜,
则将使用该索引
:
SELECT * FROM t1 在哪里key_part1
> ASCconstant
订购key_part1
; SELECT * FROM t1 在哪里key_part1
<按DESCconstant
订购key_part1
;
在下一个查询中,
ORDER BY
没有命名
key_part1
,但所有选定的行都有一个常
key_part1
量值,因此仍然可以使用索引:
SELECT * FROM t1 WHEREkey_part1
=constant1
ANDkey_part2
>constant2
ORDER BYkey_part2
;
在某些情况下,MySQL
无法
使用索引来解析
ORDER BY
,尽管它仍然可以使用索引来查找与该
WHERE
子句
匹配的行
。
例子:
该查询用于
ORDER BY
不同的索引:
SELECT * FROM t1 ORDER BYkey1
,key2
;
该查询用于
ORDER BY
索引的非连续部分:
SELECT * FROM t1 WHEREkey2
=constant
ORDER BYkey1_part1
,key1_part3
;
用于获取行的索引与以下中使用的索引不同
ORDER BY
:
SELECT * FROM t1 WHEREkey2
=constant
ORDER BYkey1
;
该查询使用
ORDER BY
包含索引列名以外的术语的表达式:
SELECT * FROM t1 ORDER BY ABS(key
); SELECT * FROM t1 ORDER BY -key
;
该查询连接了许多表,并且其中的列
ORDER BY
不是来自用于检索行的第一个非常量表。
(这是
EXPLAIN
输出中没有
const
连接类型
的第一个表
。)
查询有不同的
ORDER BY
和
GROUP BY
表情。
只有该
ORDER BY
子句中
指定的列的前缀有一个索引
。
在这种情况下,索引不能用于完全解析排序顺序。
例如,如果仅
CHAR(20)
索引列
的前10个字节
,则索引无法区分超过第10个字节的值,并且
filesort
需要a。
索引不按顺序存储行。
例如,对于
表中
的
HASH
索引
,这是正确的
MEMORY
。
用于排序的索引的可用性可能受到列别名的使用的影响。
假设列
t1.a
已编入索引。
在此语句中,选择列表中列的名称为
a
。
它指的是
t1.a
,引用
a
也是
ORDER BY
如此,因此
t1.a
可以使用
索引
:
选择一个FROM t1 ORDER BY a;
在此语句中,选择列表中列的名称也是
a
,但它是别名。
它指的是
ABS(a)
,在引用
a
中
ORDER BY
,所以
t1.a
不能使用
索引
:
SELECT ABS(a)作为一个FROM t1 ORDER by a;
在以下语句中,
ORDER BY
引用的名称不是选择列表中列的名称。
但是在
t1
命名中
有一个列
a
,所以
可以使用
ORDER
BY
引用
t1.a
和索引
t1.a
。
(当然,生成的排序顺序可能与顺序完全不同
ABS(a)
。)
选择ABS(a)AS b FROM t1 ORDER by a;
以前(MySQL 5.7及更低版本),
GROUP BY
在某些条件下隐式排序。
在MySQL 8.0中,不再出现这种情况,因此
ORDER BY NULL
不再需要在最后
指定
抑制隐式排序(如前所述)。
但是,查询结果可能与以前的MySQL版本不同。
要生成给定的排序顺序,请提供一个
ORDER BY
子句。
如果索引不能用于满足
ORDER
BY
子句,则MySQL执行
filesort
读取表行并对其进行排序
的
操作。
A
filesort
构成查询执行中的额外排序阶段。
为了获得
filesort
操作的
内存
,从MySQL 8.0.12开始,优化器根据需要逐步分配内存缓冲区,直到
sort_buffer_size
系统变量
指示的大小
,而不是
sort_buffer_size
像MySQL 8.0之前那样预先
分配固定数量的
字节。 0.12。
这使用户可以设置
sort_buffer_size
更大的值来加速更大的排序,而不用担心小排序的过多内存使用。
(对于Windows上的多个并发排序,可能不会出现此优势,因为它具有较弱的多线程
malloc
。)
一个
filesort
操作使用临时磁盘文件作为必要的,如果结果集是太大,无法在内存中。
某些类型的查询特别适合于完全内存中的
filesort
操作。
例如,优化器可以
filesort
用来有效地在内存中处理
ORDER BY
以下形式的查询(和子查询)
,而无需临时文件
:
SELECT ... FROMsingle_table
... ORDER BYnon_index_column
[DESC] LIMIT [M
,]N
;
此类查询在Web应用程序中很常见,只显示较大结果集中的几行。 例子:
SELECT col1,... FROM t1 ... ORDER BY name LIMIT 10; SELECT col1,... FROM t1 ... ORDER BY RAND()LIMIT 15;
对于
未使用的
慢
ORDER BY
查询
filesort
,请尝试将
max_length_for_sort_data
系统变量
降低
到适合触发a的值
filesort
。
(将此变量的值设置得过高的症状是高磁盘活动和低CPU活动的组合。)
要提高
ORDER BY
速度,请检查是否可以让MySQL使用索引而不是额外的排序阶段。
如果无法做到这一点,请尝试以下策略:
增加
sort_buffer_size
变量值。
理想情况下,该值应足够大,以使整个结果集适合排序缓冲区(以避免写入磁盘和合并传递)。
请考虑存储在排序缓冲区中的列值的大小受
max_sort_length
系统变量值的影响。
例如,如果元组存储长字符串列的值并且您增加了值
max_sort_length
,则排序缓冲区元组的大小也会增加,并且可能需要您增加
sort_buffer_size
。
要监视合并传递的数量(合并临时文件),请检查
Sort_merge_passes
状态变量。
增加
read_rnd_buffer_size
变量值,以便一次读取更多行。
将
tmpdir
系统变量
更改
为指向具有大量可用空间的专用文件系统。
变量值可以列出以循环方式使用的几个路径;
您可以使用此功能将负载分散到多个目录中。
:
在Unix上
用冒号字符(
)
分隔路径,
;
在Windows上用
分号字符(
)
分隔
路径
。
路径应命名位于不同
物理
磁盘
上的文件系统中的目录
,而不是同一磁盘上的不同分区。
使用
EXPLAIN
(参见
第8.8.1节“使用EXPLAIN优化查询”
),可以检查MySQL是否可以使用索引来解析
ORDER
BY
子句:
此外,如果
filesort
执行a,则优化程序跟踪输出包括
filesort_summary
块。
例如:
“filesort_summary”:{ “行”:100, “examine_rows”:100, “number_of_tmp_files”:0, “peak_memory_used”:25192, “sort_mode”:“<sort_key,packed_additional_fields>” }
peak_memory_used
表示排序期间任何时候使用的最大内存。
这是一个值,但不一定与
sort_buffer_size
系统变量
的值一样大
。
在MySQL 8.0.12之前,输出显示
sort_buffer_size
,表示值
sort_buffer_size
。
(在MySQL 8.0.12之前,优化器总是
sort_buffer_size
为排序缓冲区
分配
字节。从8.0.12开始,优化器逐步分配排序缓冲区内存,从少量开始并根据需要添加更多,最多为
sort_buffer_size
字节。)
该
sort_mode
值提供有关排序缓冲区中元组内容的信息:
<sort_key, rowid>
:这表示排序缓冲区元组是包含原始表行的排序键值和行ID的对。
元组按排序键值排序,行ID用于从表中读取行。
<sort_key, additional_fields>
:这表示排序缓冲区元组包含排序键值和查询引用的列。
元组按排序键值排序,列值直接从元组中读取。
<sort_key,
packed_additional_fields>
:与前一个版本类似,但是附加列紧密地打包在一起而不是使用固定长度编码。
EXPLAIN
不区分优化器是否执行
filesort
内存。
filesort
在优化器跟踪输出中可以看到
内存的使用
。
寻找
filesort_priority_queue_optimization
。
有关优化程序跟踪的信息,请参阅
MySQL内部:跟踪优化程序
。
最满意的一般方式
GROUP BY
子句
是扫描整个表并创建一个新的临时表,其中每个组的所有行都是连续的,然后使用此临时表来发现组并应用聚合函数(如果有的话)。
在某些情况下,MySQL能够做得更好,并通过使用索引访问来避免创建临时表。
使用索引的最重要的前提条件
GROUP BY
是所有
GROUP
BY
列都引用来自同一索引的属性,并且索引按顺序存储其键(例如,对于
BTREE
索引而不是
HASH
索引)。
是否可以通过索引访问替换临时表的使用还取决于查询中使用索引的哪些部分,为这些部分指定的条件以及所选的聚合函数。
有两种方法可以
GROUP BY
通过索引访问
来执行
查询,如以下各节所述。
第一种方法将分组操作与所有范围谓词(如果有)一起应用。
第二种方法首先执行范围扫描,然后对生成的元组进行分组。
松散的索引扫描也可以
GROUP BY
在某些条件下使用。
请参见
跳过扫描范围访问方法
。
最有效的处理方法
GROUP
BY
是使用索引直接检索分组列。
使用此访问方法,MySQL使用某些索引类型的属性(例如,键
BTREE
)。
此属性允许在索引中使用查找组,而无需考虑索引中满足所有
WHERE
条件的
所有键
。
此访问方法仅考虑索引中的一小部分键,因此称为
松散索引扫描
。
当没有
子句包含范围谓词时(参见
连接类型
的讨论
)
WHERE
子句,则松散索引扫描会读取与组数一样多的密钥,这可能比所有密钥的数量小得多。
如果
WHERE
range
第8.8.1节“使用EXPLAIN优化查询”
,松散索引扫描查找满足范围条件的每个组的第一个键,并再次读取尽可能少的键。
在以下条件下可以这样做:
查询在一个表上。
该
GROUP BY
唯一名称构成的指数,没有其他列的最左边的前缀列。
(如果
GROUP BY
查询具有
DISTINCT
子句,则所有不同的属性引用形成索引的最左前缀的列。)例如,如果表
t1
具有索引
(c1,c2,c3)
,则在查询具有的情况下,松散索引扫描适用
GROUP BY c1,
c2
。
如果查询具有
GROUP BY c2, c3
(列不是最左边的前缀)或
GROUP BY c1, c2,
c4
(
c4
不在索引中),
则不适用
。
选择列表中使用的唯一聚合函数(如果有)是
MIN()
和
MAX()
,并且它们都引用同一列。
该列必须位于索引中,并且必须紧跟在列中的列之后
GROUP BY
。
索引的任何其他部分都不是
GROUP BY
查询中引用的
那些部分
必须是常量(也就是说,它们必须以与常量相等的方式引用),但参数
MIN()
或
MAX()
函数
除外
。
对于索引中的列,必须为完整列值编制索引,而不仅仅是前缀。
例如,使用
c1 VARCHAR(20), INDEX (c1(10))
,索引仅使用
c1
值
的前缀,
不能用于松散索引扫描。
如果“松散索引扫描”适用于查询,则
EXPLAIN
输出将显示
Using index for group-by
在
Extra
列中。
假设
idx(c1,c2,c3)
表上
有索引
t1(c1,c2,c3,c4)
。
松散索引扫描访问方法可用于以下查询:
SELECT c1,c2 FROM t1 GROUP BY c1,c2; SELECT DISTINCT c1,c2 FROM t1; SELECT c1,MIN(c2)FROM t1 GROUP BY c1; SELECT c1,c2 FROM t1 WHERE c1 <const
GROUP BY c1,c2; SELECT MAX(c3),MIN(c3),c1,c2 FROM t1 WHERE c2>const
GROUP BY c1,c2; SELECT c2 FROM t1 WHERE c1 <const
GROUP BY c1,c2; SELECT c1,c2 FROM t1 WHERE c3 =const
GROUP BY c1,c2;
由于给出的原因,使用此快速选择方法无法执行以下查询:
除了
已经支持
的
MIN()
和
MAX()
引用
之外,松散索引扫描访问方法还可以应用于选择列表中的其他形式的聚合函数引用
:
AVG(DISTINCT)
,
SUM(DISTINCT)
和
COUNT(DISTINCT)
支持。
AVG(DISTINCT)
并
SUM(DISTINCT)
采取一个论点。
COUNT(DISTINCT)
可以有多个列参数。
查询中
必须没有
GROUP BY
or
DISTINCT
子句。
前面描述的松散索引扫描限制仍然适用。
假设
idx(c1,c2,c3)
表上
有索引
t1(c1,c2,c3,c4)
。
松散索引扫描访问方法可用于以下查询:
SELECT COUNT(DISTINCT c1),SUM(DISTINCT c1)FROM t1; SELECT COUNT(DISTINCT c1,c2),COUNT(DISTINCT c2,c1)FROM t1;
紧密索引扫描可以是完整索引扫描,也可以是范围索引扫描,具体取决于查询条件。
如果不满足松散索引扫描的条件,则仍可以避免为
GROUP BY
查询
创建临时表
。
如果
WHERE
子句中
有范围条件,则
此方法仅读取满足这些条件的键。
否则,它执行索引扫描。
因为此方法读取
WHERE
子句
定义的每个范围中的所有键
,或者如果没有范围条件则扫描整个索引,因此称为
紧密索引扫描
。
使用紧密索引扫描时,仅在找到满足范围条件的所有键之后才执行分组操作。
为了使这个方法起作用,对于查询中的所有列,存在一个恒定的相等条件就足够了,该条件引用了密钥的部分之前或之间的部分
GROUP BY
密钥。
来自等式条件的常量填充
搜索关键字
中的任何
“
间隙
”
,以便可以形成索引的完整前缀。
然后,这些索引前缀可用于索引查找。
如果
GROUP BY
结果需要排序,并且可以形成作为索引前缀的搜索键,MySQL也避免了额外的排序操作,因为在有序索引中使用前缀进行搜索已经按顺序检索了所有键。
假设
idx(c1,c2,c3)
表上
有索引
t1(c1,c2,c3,c4)
。
以下查询不适用于前面描述的松散索引扫描访问方法,但仍可使用紧密索引扫描访问方法。
它有一个缺口
GROUP BY
,但它被条件覆盖
c2 =
'a'
:
SELECT c1,c2,c3 FROM t1 WHERE c2 ='a'GROUP BY c1,c3;
在
GROUP BY
不与钥匙的第一部分开始,但它提供了该部分恒定的条件:
SELECT c1,c2,c3 FROM t1 WHERE c1 ='a'GROUP BY c2,c3;
DISTINCT
ORDER
BY
在许多情况下
结合
需要临时表。
因为
DISTINCT
可以使用
GROUP
BY
,所以了解MySQL如何
使用
不属于所选列的列
ORDER BY
或
HAVING
子句。
请参见
第12.20.3节“GROUP BY的MySQL处理”
。
在大多数情况下,一个
DISTINCT
条款可以被视为一个特例
GROUP BY
。
例如,以下两个查询是等效的:
SELECT DISTINCT c1,c2,c3 FROM t1 在哪里c1>const
; SELECT c1,c2,c3 FROM t1 在哪里c1>const
GROUP BY c1,c2,c3;
由于这种等效性,适用于
GROUP BY
查询
的优化
也可以应用于带有
DISTINCT
子句的
查询
。
因此,有关
DISTINCT
查询
优化可能性的更多详细信息
,请参见
第8.2.1.16节“GROUP BY优化”
。
与之
结合
使用时
,MySQL会在找到
唯一行后
立即停止
。
LIMIT
row_count
DISTINCT
row_count
如果不使用查询中指定的所有表中的列,MySQL会在找到第一个匹配项后立即停止扫描所有未使用的表。
在下面的例子中,假设
t1
之前使用过
t2
(你可以检查
EXPLAIN
),MySQL
t2
在
t1
找到第一行时
停止从
(对于任何特定行
)
读取
t2
:
SELECT DISTINCT t1.a FROM t1,t2其中t1.a = t2.a;
如果只需要结果集中指定数量的行,请
LIMIT
在查询中
使用
子句,而不是获取整个结果集并丢弃额外数据。
MySQL有时会优化具有
子句且没有
子句
的查询
:
LIMIT
row_count
HAVING
如果您只选择几行
LIMIT
,MySQL在某些情况下使用索引,通常它更喜欢进行全表扫描。
如果你结合
使用
,MySQL会在找到
排序结果
的第一
行后
立即停止排序
,而不是整理整个结果。
如果使用索引完成排序,则速度非常快。
如果必须完成一个
文件排序,则选择与
没有该
子句
的查询匹配的所有行
,并在
找到
第一个之前对其中的大部分或全部进行排序
。
在找到初始行之后,MySQL不会对结果集的任何剩余部分进行排序。
LIMIT
row_count
ORDER BY
row_count
LIMIT
row_count
此行为的一种表现形式是,
ORDER BY
带有和不带
的
查询
LIMIT
可能会以不同的顺序返回行,如本节后面所述。
如果你结合
使用
,MySQL会在找到
唯一行后
立即停止
。
LIMIT
row_count
DISTINCT
row_count
在某些情况下,
GROUP BY
可以通过按顺序读取索引(或对索引进行排序),然后计算摘要直到索引值更改来解析a。
在这种情况下,
不计算任何不必要的
值。
LIMIT
row_count
GROUP BY
一旦MySQL向客户端发送了所需的行数,它就会中止查询,除非您使用
SQL_CALC_FOUND_ROWS
。
在这种情况下,可以使用检索行数
SELECT
FOUND_ROWS()
。
请参见
第12.15节“信息功能”
。
LIMIT 0
快速返回一个空集。
这对于检查查询的有效性非常有用。
它还可用于获取使用MySQL API的应用程序中的结果列类型,该API使结果集元数据可用。
使用
mysql
客户端程序,您可以使用该
--column-type-info
选项显示结果列类型。
如果服务器使用临时表来解析查询,则它使用该
子句计算需要多少空间。
LIMIT
row_count
如果未使用索引
ORDER BY
但是
LIMIT
也存在子句,则优化器可能能够避免使用合并文件并使用内存中
filesort
操作
对内存中的行进行排序
。
如果列中的多个行具有相同的值
ORDER
BY
,则服务器可以按任何顺序自由返回这些行,并且可能根据整体执行计划的不同而不同。
换句话说,这些行的排序顺序相对于无序列是不确定的。
影响执行计划的一个因素是
LIMIT
,因此
ORDER BY
使用和不
使用
查询
LIMIT
可能会返回不同顺序的行。
考虑这个查询,它按
category
列
排序,
但对于
id
和
rating
列是
不确定的
:
MySQL的> SELECT * FROM ratings ORDER BY category;
+ ---- + ---------- + -------- +
| id | 类别| 评级|
+ ---- + ---------- + -------- +
| 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 |
+ ---- + ---------- + -------- +
包括
LIMIT
可能影响每个
category
值
内的行的顺序
。
例如,这是一个有效的查询结果:
MySQL的> SELECT * FROM ratings ORDER BY category LIMIT 5;
+ ---- + ---------- + -------- +
| id | 类别| 评级|
+ ---- + ---------- + -------- +
| 1 | 1 | 4.5 |
| 5 | 1 | 3.2 |
| 4 | 2 | 3.5 |
| 3 | 2 | 3.7 |
| 6 | 2 | 3.5 |
+ ---- + ---------- + -------- +
在每种情况下,行都按
ORDER
BY
列
排序
,这是SQL标准所需的全部内容。
如果确保使用和不使用相同的行顺序很重要,请
LIMIT
在
ORDER BY
子句中
包含其他列
以使订单具有确定性。
例如,如果
id
值是唯一的,则可以
通过如下排序
category
按
id
顺序
显示
给定
值的
行
:
MySQL的>SELECT * FROM ratings ORDER BY category, id;
+ ---- + ---------- + -------- + | id | 类别| 评级| + ---- + ---------- + -------- + | 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 | 类别| 评级| + ---- + ---------- + -------- + | 1 | 1 | 4.5 | | 5 | 1 | 3.2 | | 3 | 2 | 3.7 | | 4 | 2 | 3.5 | | 6 | 2 | 3.5 | + ---- + ---------- + -------- +
MySQL函数在内部标记为确定性或非确定性。
如果给定参数的固定值,它可以为不同的调用返回不同的结果,那么函数是不确定的。
非确定性函数的例子:
RAND()
,
UUID()
。
如果函数被标记为非确定性的,
WHERE
则会为每一行(从一个表中选择时)或行组合(从多表连接中选择时)评估子句中
对它的引用
。
MySQL还根据参数类型,参数是表列还是常量值来确定何时评估函数。 只要该列更改值,就必须计算将表列作为参数的确定性函数。
非确定性函数可能会影响查询性能。
例如,某些优化可能不可用,或者可能需要更多锁定。
以下讨论使用
RAND()
但也适用于其他非确定性函数。
假设一个表
t
有这个定义:
CREATE TABLE t(id INT NOT NULL PRIMARY KEY,col_a VARCHAR(100));
考虑这两个查询:
SELECT * FROM t WHERE id = POW(1,2); SELECT * FROM t WHERE id = FLOOR(1 + RAND()* 49);
由于与主键的相等性比较,两个查询似乎都使用主键查找,但这仅适用于第一个:
不确定性的影响不仅限于
SELECT
陈述。
此
UPDATE
语句使用非确定性函数来选择要修改的行:
UPDATE t SET col_a = some_expr
WHERE id = FLOOR(1 + RAND()* 49);
据推测,目的是最多更新主键与表达式匹配的单个行。
但是,它可能会更新零行,一行或多行,具体取决于
id
列值和
RAND()
序列中
的值
。
刚才描述的行为对性能和复制有影响:
由于非确定性函数不会生成常量值,因此优化程序无法使用可能适用的策略,例如索引查找。 结果可能是表扫描。
InnoDB
可能会升级到范围键锁,而不是对一个匹配的行进行单行锁定。
不确定执行的更新对于复制是不安全的。
困难源于这样一个事实,即
RAND()
对表格的每一行评估一次函数。
要避免多项功能评估,请使用以下技术之一:
将包含非确定性函数的表达式移动到单独的语句中,将值保存在变量中。 在原始语句中,将表达式替换为对变量的引用,优化程序可将其视为常量值:
SET @keyval = FLOOR(1 + RAND()* 49);
UPDATE t SET col_a = some_expr
WHERE id = @keyval;
将随机值分配给派生表中的变量。
这个技术使变量在用于
WHERE
子句
中的比较之前被赋值一次
:
UPDATE / * + NO_MERGE(dt)* / t,(SELECT FLOOR(1 + RAND()* 49)AS r)AS dt
SET col_a = some_expr
WHERE id = dt.r;
如前所述,
WHERE
子句中
的非确定性表达式
可能会阻止优化并导致表扫描。
但是,
WHERE
如果其他表达式是确定性的
,则可以部分地优化该
子句。
例如:
SELECT * FROM t WHERE partial_key = 5 AND some_column = RAND();
如果优化器可以
partial_key
用来减少所选择的行集,
RAND()
则执行的次数会减少,这会减少非确定性对优化的影响。
窗口函数会影响优化程序考虑的策略:
如果子查询具有窗口函数,则禁用派生表合并子查询。 子查询始终具体化。
半连接不适用于窗口函数优化,因为半连接适用于子查询,
WHERE
并且
JOIN ...
ON
不能包含窗口函数。
优化器按顺序处理具有相同排序要求的多个窗口,因此可以在第一个窗口之后跳过排序。
优化器不会尝试合并可在单个步骤中评估的窗口(例如,当多个
OVER
子句包含相同的窗口定义时)。
解决方法是在
WINDOW
子句中
定义窗口
并引用子句中的窗口名称
OVER
。
未用作窗口函数的聚合函数在最外面的可能查询中聚合。
例如,在这个查询中,MySQL看到
COUNT(t1.b)
外部查询中不存在的东西,因为它放在
WHERE
子句中:
SELECT * FROM t1 WHERE t1.a =(SELECT COUNT(t1.b)FROM t2);
因此,MySQL在子查询中聚合,
t1.b
视为常量并返回行的计数
t2
。
WHERE
用
HAVING
结果
替换
错误:
MySQL的> SELECT * FROM t1 HAVING t1.a = (SELECT COUNT(t1.b) FROM t2);
错误1140(42000):在没有GROUP BY的聚合查询中,表达式为#1
SELECT列表包含非聚合列'test.t1.a'; 这是
与sql_mode = only_full_group_by不兼容
发生错误是因为
COUNT(t1.b)
可以存在
HAVING
,因此会使外部查询聚合。
窗口函数(包括用作窗口函数的聚合函数)没有前面的复杂性。 它们总是在编写它们的子查询中聚合,而不是在外部查询中聚合。
窗口函数评估可能受
windowing_use_high_precision
系统变量
值的影响,该值
确定是否在不损失精度的情况下计算窗口操作。
默认情况下,
windowing_use_high_precision
已启用。
对于某些移动帧聚合,可以应用反聚合函数来从聚合中移除值。 这可以提高性能,但可能会降低精度。 例如,将一个非常小的浮点值添加到一个非常大的值会导致非常小的值被 大值 “ 隐藏 ” 。 当稍后反转大值时,小值的效果会丢失。
由于反向聚合导致的精度损失仅是对浮点(近似值)数据类型的操作的因素。
对于其他类型,反向聚合是安全的;
这包括
DECIMAL
,允许小数部分,但是精确值类型。
为了更快地执行,MySQL在安全时始终使用反向聚合:
对于浮点值,反向聚合并不总是安全的,可能会导致精度损失。
默认是避免反向聚合,反向聚合较慢但保留精度。
如果允许牺牲速度的安全性,
windowing_use_high_precision
可以禁用以允许反向聚合。
对于非浮点数据类型,逆聚合始终是安全的,无论
windowing_use_high_precision
值
如何都使用
。
windowing_use_high_precision
对
MIN()
和
没有影响
MAX()
,在任何情况下都不使用反向聚合。
为的方差函数评价
STDDEV_POP()
,
STDDEV_SAMP()
,
VAR_POP()
,
VAR_SAMP()
,和它们的同义词,可以发生在优化模式或默认模式的评价。
优化模式可能会在最后有效数字中产生略微不同的结果。
如果允许这样的差异,
windowing_use_high_precision
可以禁用以允许优化模式。
因为
EXPLAIN
,窗口执行计划信息过于庞大,无法以传统输出格式显示。
要查看窗口信息,请使用
EXPLAIN
FORMAT=JSON
并查找
windowing
元素。
行构造函数允许同时比较多个值。 例如,这两个语句在语义上是等价的:
SELECT * FROM t1 WHERE(column1,column2)=(1,1); SELECT * FROM t1 WHERE column1 = 1 AND column2 = 1;
此外,优化器以相同的方式处理两个表达式。
如果行构造函数列未覆盖索引的前缀,则优化程序不太可能使用可用索引。
请考虑下表,其中包含一个主键
(c1, c2, c3)
:
CREATE TABLE t1( c1 INT,c2 INT,c3 INT,c4 CHAR(100), 主键(c1,c2,c3) );
在此查询中,该
WHERE
子句使用索引中的所有列。
但是,行构造函数本身不会覆盖索引前缀,结果是优化程序仅使用
c1
(
key_len=4
,大小
c1
):
MySQL的>EXPLAIN SELECT * FROM t1
WHERE c1=1 AND (c2,c3) > (1,1)\G
*************************** 1。排******************** ******* id:1 select_type:SIMPLE 表:t1 分区:NULL 类型:ref possible_keys:PRIMARY 关键:主要 key_len:4 ref:const 行:3 过滤:100.00 额外:使用在哪里
在这种情况下,使用等效的非构造函数表达式重写行构造函数表达式可能会导致更完整的索引使用。 对于给定的查询,行构造函数和等效的非构造函数表达式是:
(c2,c3)>(1,1) c2> 1 OR((c2 = 1)AND(c3> 1))
重写查询以使用非构造函数表达式会导致优化器使用index(
key_len=12
)
中的所有三列
:
MySQL的>EXPLAIN SELECT * FROM t1
WHERE c1 = 1 AND (c2 > 1 OR ((c2 = 1) AND (c3 > 1)))\G
*************************** 1。排******************** ******* id:1 select_type:SIMPLE 表:t1 分区:NULL 类型:范围 possible_keys:PRIMARY 关键:主要 key_len:12 ref:NULL 行:3 过滤:100.00 额外:使用在哪里
因此,为了获得更好的结果,请避免将行构造函数与
AND
/
OR
表达式
混合
。
使用其中一个。
在某些情况下,优化器可以将范围访问方法应用于
IN()
具有行构造函数参数的表达式。
请参阅
行构造函数表达式的范围优化
。
当MySQL使用
全表扫描
来解析查询
时
,输出
EXPLAIN
显示
ALL
在
type
列中
。
这通常在以下条件下发生:
该表非常小,执行表扫描比使用键查找更麻烦。 这对于行少于10行且行长度较短的表来说很常见。
索引列
的
ON
or
WHERE
子句
中没有可用的限制
。
您正在将索引列与常量值进行比较,并且MySQL已经计算(基于索引树)常量覆盖了表的一部分,并且表扫描会更快。 请参见 第8.2.1.1节“WHERE子句优化” 。
您正在使用具有低基数的键(许多行与键值匹配)通过另一列。 在这种情况下,MySQL假设通过使用密钥,它可能会执行许多密钥查找,并且表扫描会更快。
对于小型表,表扫描通常是合适的,并且性能影响可以忽略不计。 对于大型表,请尝试以下技术以避免优化程序错误地选择表扫描:
使用
更新的扫描表的键分布。
请参见
第13.7.3.1节“ANALYZE TABLE语法”
。
ANALYZE TABLE
tbl_name
使用
FORCE INDEX
的扫描表告诉MySQL该表扫描是非常昂贵相比,使用给定的指标:
SELECT * FROM t1,t2 FORCE INDEX(index_for_column
) 在哪里t1。col_name
= T2。col_name
;
请参见 第8.9.4节“索引提示” 。
启动
mysqld的
与
--max-seeks-for-key=1000
选项或使用
SET
max_seeks_for_key=1000
告诉优化器:假定任何按键扫描导致1000个以上的键搜索。
请参见
第5.1.8节“服务器系统变量”
。
MySQL查询优化器有不同的策略可用于评估子查询:
对于
IN
(或
=ANY
)子查询,优化器具有以下选项:
半连接
实体化
EXISTS
战略
对于
NOT IN
(或
<>ALL
)子查询,优化器具有以下选项:
实体化
EXISTS
战略
对于派生表,优化器具有以下选项(也适用于视图引用和公用表表达式):
将派生表合并到外部查询块中
将派生表实现为内部临时表
以下讨论提供了有关上述优化策略的更多信息。
使用子查询修改单个表
的限制
UPDATE
和
DELETE
语句是优化程序不使用半连接或实现子查询优化。
作为一种解决方法,尝试将它们重写为
使用连接而不是子查询的
多表
UPDATE
和
DELETE
语句。
半连接是一种准备时转换,它支持多种执行策略,例如表拉出,重复清除,首次匹配,松散扫描和物化。 优化程序使用半连接策略来改进子查询执行,如本节所述。
对于两个表之间的内部联接,连接从一个表返回一行,与另一个表中的匹配一样多次。
但是对于一些问题,唯一重要的信息是匹配,而不是匹配的数量。
假设有一些名为table的表
class
,
roster
并且分别列出课程课程和班级名单(每个班级注册的学生)中的班级。
要列出实际注册学生的课程,您可以使用此连接:
SELECT class.class_num,class.class_name 来自INNER JOIN名单 WHERE class.class_num = roster.class_num;
但是,结果会为每个注册的学生列出每个班级一次。 对于被问到的问题,这是不必要的重复信息。
假设这
class_num
是
class
表中
的主键,
可以通过使用重复抑制
SELECT
DISTINCT
,但是先生成所有匹配的行只是为了稍后消除重复,效率很低。
使用子查询可以获得相同的无重复结果:
SELECT class_num,class_name 从班级 WHERE class_num IN(SELECT class_num FROM roster);
在这里,优化器可以识别该
IN
子句要求子查询只从
roster
表中
返回每个类号的一个实例
。
在这种情况下,查询可以使用
半连接
;
也就是说,仅返回一个中的每一行的实例的操作
class
由按行匹配
roster
。
在MySQL 8.0.16及更高版本中,
EXISTS
通过将
EXISTS
属于
WHERE
条件或连接条件的
IN
子
查询转换为
子查询
,此策略也可以与
子查询
一起使用
,如下所示:
在哪里... EXISTS(SELECT ... FROM ...) - > WHERE ... 1 IN(SELECT 1 FROM ...)
在此之后,子查询操作可以作为半连接处理。
外部查询规范中允许外部联接和内部联接语法,表引用可以是基表,派生表,视图引用或公用表表达式。
在MySQL中,子查询必须满足这些条件才能作为半连接处理:
它必须是
出现在
or
子句
顶层
的
IN
(或
=ANY
)子查询
,可能作为
表达式中
的术语
。
例如:
WHERE
ON
AND
选择 ... 来自ot1,...... WHERE(oe1,...)IN(SELECT ie1,... FROM it1,... WHERE ...);
这里,
和
表示在该查询的外侧和内侧的部分表,和
与
表示参照列中的外和内表中的表达式。
ot_
i
it_
i
oe_
i
ie_
i
它不能包含
GROUP BY
or
HAVING
子句。
它不能隐式分组(它必须不包含聚合函数)。
它一定不能
ORDER BY
用
LIMIT
。
该语句不得
STRAIGHT_JOIN
在外部查询中
使用
连接类型。
该
STRAIGHT_JOIN
修改必须不存在。
外表和内表的数量必须小于连接中允许的最大表数。
子查询可以是相关的或不相关的。
DISTINCT
是允许的,
LIMIT
除非
ORDER BY
也使用。
如果子查询符合上述条件,MySQL会将其转换为半连接,并根据这些策略进行基于成本的选择:
将子查询转换为连接,或使用表pullout并将查询作为子查询表和外部表之间的内部联接运行。 表pullout将表从子查询拉出到外部查询。
重复的Weedout:运行半连接,就好像它是一个连接一样,并使用临时表删除重复的记录。
FirstMatch:当扫描内部表中的行组合并且存在给定值组的多个实例时,选择一个而不是全部返回它们。 这种“快捷方式”扫描并消除了不必要的行的产生。
LooseScan:使用索引扫描子查询表,该索引允许从每个子查询的值组中选择单个值。
将子查询实现为用于执行连接的索引临时表,其中索引用于删除重复项。 在将临时表与外部表连接时,索引也可能稍后用于查找; 如果没有,则扫描表格。 有关实现的更多信息,请参见 第8.2.2.2节“使用实现优化子查询” 。
可以使用以下
optimizer_switch
系统变量标志
启用或禁用其中每个策略
:
该
semijoin
标志控制是否使用半连接。
如果
semijoin
使能,
firstmatch
,
loosescan
,
duplicateweedout
,和
materialization
旗帜能够在允许的半连接策略更精细的控制。
如果
duplicateweedout
禁用半连接策略,则除非还禁用所有其他适用策略,否则不会使用该策略。
如果
duplicateweedout
禁用,有时优化程序可能会生成远非最佳的查询计划。
这是由于在贪婪搜索期间进行启发式修剪,这可以通过设置来避免
optimizer_prune_level=0
。
默认情况下启用这些标志。 请参见 第8.9.2节“可切换的优化” 。
优化程序最大限度地减少了视图和派生表的处理差异。
这会影响使用
STRAIGHT_JOIN
修饰符的
IN
查询
和带有
可以转换为半连接
的
子查询
的视图
。
以下查询说明了这一点,因为处理中的更改会导致转换发生更改,从而导致执行策略不同:
创建视图与AS 选择 * 从t1 IN(选择b 来自t2); SELECT STRAIGHT_JOIN * FROM t3 JOIN v ON t3.x = va;
优化器首先查看视图并将
IN
子查询转换为半连接,然后检查是否可以将视图合并到外部查询中。
由于
STRAIGHT_JOIN
外部查询中
的
修饰符会阻止半连接,因此优化程序会拒绝合并,从而导致使用实现表进行派生表评估。
EXPLAIN
output表示使用半连接策略如下:
对于扩展
EXPLAIN
输出,以下
SHOW
WARNINGS
显示
的文本显示
重写的查询,该查询显示半连接结构。
(参见
第8.8.3节“扩展EXPLAIN输出格式”
。)从中可以了解哪些表从半连接中拉出。
如果子查询转换为半连接,您将看到子查询谓词已消失,其表和
WHERE
子句已合并到外部查询连接列表和
WHERE
子句中。
对于重复Weedout临时表的使用是由指示
Start temporary
和
End
temporary
在
Extra
列。
未被拉出并且在
EXPLAIN
输出行
范围内的
表
,
Start temporary
并将
End temporary
它们
rowid
放在临时表中。
FirstMatch(
在
tbl_name
)Extra
列中表示连接快捷方式。
LooseScan(
在
m
..n
)Extra
列中表示使用LooseScan策略。
m
并且
n
是关键部件号。
用于实现的临时表用于
select_type
值为的
MATERIALIZED
行和
table
值为的
行
。
<subquery
N
>
优化程序使用实现来实现更有效的子查询处理。 实现通过生成子查询结果作为临时表(通常在内存中)来加速查询执行。 MySQL第一次需要子查询结果时,它会将结果实现为临时表。 在任何后续需要结果的时候,MySQL再次引用临时表。 优化器可以使用哈希索引对表进行索引,以使查找快速且廉价。 索引包含唯一值以消除重复项并使表更小。
子查询实现在可能的情况下使用内存中的临时表,如果表变得太大,则会回退到磁盘上的存储。 请参见 第8.4.4节“MySQL中的内部临时表使用” 。
如果未使用实现,则优化程序有时会将非相关子查询重写为相关子查询。
例如,以下
IN
子查询是不相关的(
where_condition
仅涉及列
t2
和不
包括列
t1
):
SELECT * FROM t1
在哪里t1.a IN(SELECT t2.b FROM t2 WHERE where_condition
);
优化器可能会将其重写为
EXISTS
相关子查询:
SELECT * FROM t1
WHERE EXISTS(从t2 WHERE where_condition
和t1.a = t2.b中选择t2.b);
使用临时表的子查询实现避免了这种重写,并且可以仅执行一次子查询,而不是每次执行外部查询一次。
对于要在MySQL中使用的子查询实现,
必须启用
optimizer_switch
系统变量
materialization
标志。
(参见
第8.9.2节,“切换优化”
)。随着
materialization
启用的标志,物化适用于子查询任何地方出现谓词(在选择列表中,
WHERE
,
ON
,
GROUP BY
,
HAVING
,或
ORDER BY
),对于属于任何这些用例谓词:
当没有外部表达式
oe_i
或内部表达式
ie_i
可为空
时,谓词具有此形式
。
N
是1或更大。
(oe_1
,oe_2
...,oe_N
)[NOT] IN(SELECTie_1
,i_2
,...,ie_N
...)
当存在单个外部表达式
oe
和内部表达式
时,谓词具有此形式
ie
。
表达式可以为空。
oe
[NOT] IN(选择ie
...)
谓词是
IN
或者
(
)
NOT
IN
的结果
具有与结果相同的含义
。
UNKNOWN
NULL
FALSE
以下示例说明了等效性
UNKNOWN
和
FALSE
谓词式评估
的要求如何
影响是否可以使用子查询实现。
假设
where_condition
只涉及列
t2
而不是
t1
子查询是不相关的。
此查询取决于具体化:
SELECT * FROM t1
在哪里t1.a IN(SELECT t2.b FROM t2 WHERE where_condition
);
在这里,没有关系是否
IN
断言返回
UNKNOWN
或
FALSE
。
无论哪种方式,
t1
查询结果中都不包含
行
。
不使用子查询实现的示例是以下查询,其中
t2.b
是可为空的列:
SELECT * FROM t1
WHERE(t1.a,t1.b)NOT IN(SELECT t2.a,t2.b FROM t2
在哪里where_condition
);
以下限制适用于子查询实现的使用:
内表达式和外表达式的类型必须匹配。 例如,如果两个表达式都是整数或两者都是十进制,则优化器可能能够使用实现,但如果一个表达式是整数而另一个表达式是十进制则不能。
内在表达不能是一个
BLOB
。
使用
EXPLAIN
with查询可以指示优化程序是否使用子查询实现:
与不使用实现的查询执行相比,
select_type
可能会更改
DEPENDENT SUBQUERY
为
SUBQUERY
。
这表明,对于每个外行将执行一次的子查询,实现使子查询只执行一次。
对于扩展
EXPLAIN
输出,以下显示的文本
SHOW
WARNINGS
包括
materialize
和
materialized-subquery
。
某些优化适用于使用
IN
(或
=ANY
)运算符测试子查询结果的比较。
本节讨论这些优化,特别是关于
NULL
价值所带来
的挑战
。
讨论的最后部分建议您如何帮助优化器。
考虑以下子查询比较:
outer_expr
IN(inner_expr
从...中选择subquery_where
)
MySQL的评估查询
“
从外到内。
“
也就是说,它首先获取外部表达式的值
outer_expr
,然后运行子查询并捕获它生成的行。
一个非常有用的优化是
“
通知
”
子查询,只有感兴趣的行是内部表达式
inner_expr
相等的行
outer_expr
。
这是通过将适当的相等性下推到子查询的
WHERE
子句中来实现的,以使其更具限制性。
转换后的比较如下所示:
EXISTS(选择1来自...在哪里subquery_where
和outer_expr
=inner_expr
)
转换后,MySQL可以使用下推的相等性来限制它必须检查以评估子查询的行数。
更一般地,
N
值与返回
N
-value行
的子查询
的比较
受到相同的转换。
如果
oe_i
并
ie_i
表示相应的外部和内部表达式值,则此子查询比较:
(oe_1
,...,oe_N
)IN (SELECTie_1
,...,ie_N
FROM ... WHEREsubquery_where
)
变为:
EXISTS(选择1来自...在哪里subquery_where
和oe_1
=ie_1
和...... ANDoe_N
=ie_N
)
为简单起见,以下讨论假设一对外部和内部表达值。
刚刚描述的转换有其局限性。
仅当我们忽略可能的
NULL
值时
它才有效
。
也就是说,
只要这两个条件都成立
,
“
下推
”
策略就会起作用:
当这些条件中的任何一个或两个都不成立时,优化就更复杂了。
假设
outer_expr
已知它是非
NULL
值,但子查询不会产生一个行
outer_expr
=
inner_expr
。
然后
评估如下:
outer_expr
IN (SELECT
...)
在这种情况下,查找行的方法
不再有效。
有必要找这样的行,但如果没有找到,还找行,其中
是
。
粗略地说,子查询可以转换为这样的东西:
outer_expr
=
inner_expr
inner_expr
NULL
EXISTS(选择1来自...在哪里subquery_where
和 (outer_expr
=inner_expr
OR为inner_expr
空))
需要评估额外
IS
NULL
条件是MySQL具有
ref_or_null
访问方法的原因:
MySQL的>EXPLAIN
SELECT
outer_expr
IN (SELECT t2.maybe_null_keyFROM t2, t3 WHERE ...)
FROM t1;
*************************** 1。排******************** ******* id:1 select_type:PRIMARY 表:t1 ... *************************** 2.排******************** ******* id:2 select_type:DEPENDENT SUBQUERY 表:t2 type:ref_or_null possible_keys:maybe_null_key key:maybe_null_key key_len:5 ref:func 行:2 额外:使用在哪里; 使用索引 ...
在
unique_subquery
和
index_subquery
子查询,具体的访问方法也有
“
或
NULL
”
变体。
附加
OR ... IS NULL
条件使得查询执行稍微复杂一些(并且子查询中的一些优化变得不适用),但通常这是可以容忍的。
的情况更糟糕的时候
outer_expr
可能
NULL
。
根据SQL解释
NULL
为
“
未知值
”,
应评估为:
NULL IN (SELECT
inner_expr
...)
为了进行适当的评估,必须能够检查是否
SELECT
产生了任何行,因此
不能将其下推到子查询中。
这是一个问题,因为除非可以推迟相等,否则许多真实世界的子查询变得非常慢。
outer_expr
=
inner_expr
从本质上讲,必须有不同的方法来执行子查询,具体取决于
outer_expr
。
优化选择超速SQL合规性,所以它占的可能性
outer_expr
可能是
NULL
:
如果
outer_expr
是
NULL
,要评估以下表达式,有必要执行
SELECT
以确定它是否产生任何行:
NULL IN(SELECTinner_expr
FROM ... WHEREsubquery_where
)
有必要在
SELECT
这里
执行原件
,没有任何前面提到的那种下推等式。
另一方面,当
outer_expr
不是
时
NULL
,这个比较是绝对必要的:
outer_expr
IN(inner_expr
从...中选择subquery_where
)
转换为使用下推条件的表达式:
EXISTS(选择1来自...在哪里subquery_where
和outer_expr
=inner_expr
)
如果没有这种转换,子查询将会很慢。
为了解决是否将条件压入子查询的困境,条件包含在 “ 触发器 ” 函数中。 因此,表达式如下:
outer_expr
IN(inner_expr
从...中选择subquery_where
)
被转换成:
EXISTS(SELECT 1 FROM ... WHEREsubquery_where
和trigcond(outer_expr
=inner_expr
))
更一般地说,如果子查询比较基于几对外部和内部表达式,则转换采用此比较:
(oe_1
,...,oe_N
)IN(SELECTie_1
,...,ie_N
FROM ... WHEREsubquery_where
)
并将其转换为此表达式:
EXISTS(SELECT 1 FROM ... WHEREsubquery_where
和trigcond(oe_1
=ie_1
) 和...... AND trigcond(oe_N
=ie_N
) )
每个
都是一个特殊函数,其值为:
trigcond(
X
)
X
当
“
链接
”的
外表达
oe_i
不是
NULL
TRUE
当
“
链接
”的
外表达
oe_i
是
NULL
触发器函数
不是
您创建的类型的触发器
CREATE
TRIGGER
。
包含在
trigcond()
函数
内的等式
不是查询优化器的第一类谓词。
大多数优化都无法处理可能在查询执行时打开和关闭的谓词,因此他们认为任何
未知函数都会被忽略。
这些优化可以使用触发的等式:
trigcond(
X
)
参考优化:
可用于构建
,
或
表访问。
trigcond(
X
=Y
[OR Y
IS NULL])ref
eq_ref
ref_or_null
基于索引查找的子查询执行引擎:
可用于构造
或
访问。
trigcond(
X
=Y
)unique_subquery
index_subquery
表条件生成器:如果子查询是多个表的连接,则会尽快检查触发条件。
当优化器使用触发条件创建某种基于索引查找的访问时(如前面列表的前两项),它必须具有关闭条件时的情况的回退策略。
此回退策略始终相同:执行全表扫描。
在
EXPLAIN
输出中,回退显示
Full scan on NULL key
在
Extra
列中:
MySQL的>EXPLAIN SELECT t1.col1,
t1.col1 IN (SELECT t2.key1 FROM t2 WHERE t2.col2=t1.col2) FROM t1\G
*************************** 1。排******************** ******* id:1 select_type:PRIMARY 表:t1 ... *************************** 2.排******************** ******* id:2 select_type:DEPENDENT SUBQUERY 表:t2 type:index_subquery possible_keys:key1 key:key1 key_len:5 ref:func 行:2 额外:使用在哪里; 全键扫描NULL键
如果您运行
EXPLAIN
后跟
SHOW
WARNINGS
,则可以看到触发条件:
*************************** 1。排******************** ******* 等级:注意 代码:1003 消息:选择`test` .t1` .col1` AS`col1`, <in_optimizer>(`test`.`t1`.`col1`, t2中的<exists>(<index_lookup>(<cache>(`test` .t1` .col1`) 在key1上检查NULL 其中(`test`.`t2`.`col2` =`test`.`t1`.`col2`),其具有 trigcond(<is_not_null_test>(`test` .t2` .key1`)))))AS `t1.col1 IN(从t2选择t2.key1,其中t2.col2 = t1.col2)` 来自`test``t1`
触发条件的使用具有一些性能影响。
甲
NULL IN (SELECT ...)
现在表达可能会导致全表扫描(这是慢)时,它以前没有。
这是为正确结果支付的价格(触发条件策略的目标是提高合规性,而不是速度)。
对于多表子查询,执行
NULL IN
(SELECT ...)
速度特别慢,因为连接优化器不会针对外部表达式进行优化
NULL
。
它假设
NULL
左侧的
子查询评估
非常罕见,即使有统计数据表明不是这样。
另一方面,如果外部表达式可能
NULL
但实际上从未实际存在,则不存在性能损失。
为了帮助查询优化器更好地执行查询,请使用以下建议:
声明一个列就像
NOT NULL
它确实一样。
通过简化列的条件测试,这也有助于优化器的其他方面。
如果您不需要区分
NULL
来自
FALSE
子查询的结果,你可以很容易地避免慢的执行路径。
替换看起来像这样的比较:
outer_expr
IN(inner_expr
从...中选择)
用这个表达式:
(outer_expr
IS NOT NULL)AND(outer_expr
IN(SELECTinner_expr
FROM ...))
然后
NULL IN (SELECT ...)
永远不会被评估,因为
AND
一旦表达式结果清楚,
MySQL就会停止评估
部分。
另一种可能的重写:
EXISTS(inner_expr
从...中选择) WHEREinner_expr
=outer_expr
)
当你需要分不清这将适用于
NULL
从
FALSE
子查询结果,在这种情况下,你可能真的想
EXISTS
。
在
subquery_materialization_cost_based
所述的标志
optimizer_switch
系统变量使得能够在子查询物化和之间的选择控制
IN
-到-
EXISTS
子查询变换。
请参见
第8.9.2节“可切换的优化”
。
优化器可以使用两种策略(也适用于视图引用和公用表表达式)处理派生表引用:
将派生表合并到外部查询块中
将派生表实现为内部临时表
例1:
SELECT * FROM(SELECT * FROM t1)AS derived_t1;
通过合并派生表
derived_t1
,该查询的执行类似于:
SELECT * FROM t1;
例2:
选择 * FROM t1 JOIN(SELECT t2.f1 FROM t2)AS derived_t2 ON t1.f2 = derived_t2.f1 在哪里t1.f1> 0;
通过合并派生表
derived_t2
,该查询的执行类似于:
SELECT t1。*,t2.f1 FROM t1 JOIN t2 ON t1.f2 = t2.f1 在哪里t1.f1> 0;
具有实现,
derived_t1
并
derived_t2
在每个查询中被视为单独的表。
优化器以相同的方式处理派生表,视图引用和公用表表达式:它尽可能避免不必要的实现,这样可以将条件从外部查询推送到派生表,并生成更高效的执行计划。 (有关示例,请参见 第8.2.2.2节“使用实现优化子查询” 。)
如果合并将导致外部查询块引用超过61个基表,则优化程序将选择实现。
ORDER BY
如果这些条件都为真
,则优化器将
派生表或视图引用中
的
子句
传播
到外部查询块:
外部查询未分组或聚合。
外部查询不指定
DISTINCT
,
HAVING
或
ORDER BY
。
外部查询将此派生表或视图引用作为
FROM
子句中
的唯一源
。
否则,优化器会忽略该
ORDER
BY
子句。
以下方法可用于影响优化程序是否尝试将派生表,视图引用和公用表表达式合并到外部查询块中:
该
MERGE
和
NO_MERGE
优化器提示可以使用。
假设没有其他规则阻止合并,它们适用。
请参见
第8.9.3节“优化程序提示”
。
同样,您可以使用
系统变量
的
derived_merge
标志
optimizer_switch
。
请参见
第8.9.2节“可切换的优化”
。
默认情况下,启用该标志以允许合并。
禁用该标志可防止合并并避免
ER_UPDATE_TABLE_USED
错误。
该
derived_merge
标志也适用于不包含
ALGORITHM
子句的
视图
。
因此,如果
ER_UPDATE_TABLE_USED
使用等效于子查询的表达式的视图引用发生错误,则添加
ALGORITHM=TEMPTABLE
到视图定义会阻止合并并优先于该
derived_merge
值。
可以通过在子查询中使用任何阻止合并的构造来禁用合并,尽管这些构造对实现的影响并不明确。 对于派生表,公用表表达式和视图引用,阻止合并的构造是相同的:
如果优化器选择实现策略而不是合并派生表,它将按如下方式处理查询:
优化器推迟派生表实现,直到在查询执行期间需要其内容。 这提高了性能,因为延迟实现可能导致根本不必执行此操作。 考虑将派生表的结果连接到另一个表的查询:如果优化器首先处理该另一个表并发现它不返回任何行,则不需要进一步执行连接,并且优化器可以完全跳过实现派生表的实现。
在查询执行期间,优化器可以向派生表添加索引以加速从中检索行。
EXPLAIN
对于
SELECT
包含派生表
的
查询,
请考虑以下
语句
:
EXPLAIN SELECT * FROM(SELECT * FROM t1)AS derived_t1;
优化程序通过延迟派生表来避免实现派生表,直到
SELECT
执行
期间需要结果为止
。
在这种情况下,不执行查询(因为它出现在
EXPLAIN
语句中),因此永远不需要结果。
即使对于执行的查询,派生表实现的延迟也可能使优化器完全避免实现。 发生这种情况时,执行实现所需的时间会更快地执行查询。 请考虑以下查询,该查询将派生表的结果连接到另一个表:
选择 * FROM t1 JOIN(SELECT t2.f1 FROM t2)AS derived_t2 ON t1.f2 = derived_t2.f1 在哪里t1.f1> 0;
如果优化进程
t1
首先并且
WHERE
子句产生空结果,则连接必须为空并且不需要实现派生表。
对于派生表需要实现的情况,优化器可以向实现表添加索引以加速对其的访问。
如果这样的索引允许
ref
访问表,则可以大大减少查询执行期间读取的数据量。
请考虑以下查询:
选择 * FROM t1 JOIN(SELECT DISTINCT f1 FROM t2)AS derived_t2 ON t1.f1 = derived_t2.f1;
优化器在列上构造索引
f1
,
derived_t2
如果这样做将允许使用
ref
最低成本执行计划
的
访问。
添加索引后,优化程序可以将具体化派生表视为具有索引的常规表,并且与生成的索引类似。
与没有索引的查询执行成本相比,索引创建的开销可以忽略不计。
如果
ref
访问会导致比其他访问方法更高的成本,则优化程序不会创建任何索引并且不会丢失任何内容。
对于优化程序跟踪输出,合并的派生表或视图引用不会显示为节点。 只有其基础表出现在顶部查询的计划中。
对于公用表表达式(CTE),派生表的具体化也是如此。 此外,以下考虑因素特别适用于CTE。
如果CTE由查询实现,则查询将实现一次,即使查询多次引用它。
递归CTE始终具体化。
如果CTE具体化,优化器会自动添加相关索引,如果它估计索引将加速顶级语句对CTE的访问。 这类似于派生表的自动索引,除了如果多次引用CTE,优化器可以创建多个索引,以最合适的方式加速每个引用的访问。
的
MERGE
和
NO_MERGE
优化器提示可以应用到的CTE。
顶级语句中的每个CTE引用都可以有自己的提示,允许选择性地合并或实现CTE引用。
以下语句使用提示来指示
cte1
应合并并
cte2
应实现:
WITH cte1 AS(SELECT a,b FROM table1), cte2 AS(SELECT c,d FROM table2) SELECT / * + MERGE(cte1)NO_MERGE(cte2)* / cte1.b,cte2.d 从cte1加入cte2 WHERE cte1.a = cte2.c;
在
ALGORITHM
对条款
CREATE
VIEW
没有任何影响物化
WITH
前款
SELECT
视图定义语句。
请考虑以下声明:
CREATE ALGORITHM = {TEMPTABLE | MERGE}查看v1 AS ... ...选择...
该
ALGORITHM
值仅影响而
SELECT
不是
WITH
子句的实现。
在MySQL 8.0.16之前,如果
internal_tmp_disk_storage_engine=MYISAM
使用磁盘上临时表实现CTE的任何尝试都发生错误,因为对于CTE,用于磁盘内部临时表的存储引擎不可能
MyISAM
。
从MySQL 8.0.16开始,这不再是一个问题,因为
TempTable
现在总是
InnoDB
用于磁盘内部临时表。
如前所述,CTE如果具体化,即使多次引用,也会实现一次。
要指示一次性实现,优化程序跟踪输出包含
creating_tmp_table
一次或多次出现的事件
reusing_tmp_table
。
CTE与派生表类似,
materialized_from_subquery
节点遵循引用。
这对于多次引用的CTE来说是正确的,因此不存在重复的
materialized_from_subquery
节点(这会给人一种子查询多次执行的印象,并产生不必要的详细输出)。
只有一个对CTE的引用有一个完整的
materialized_from_subquery
节点,其中包含子查询计划的描述。
其他引用具有减少的
materialized_from_subquery
节点。
同样的想法适用
EXPLAIN
于
TRADITIONAL
格式
输出
:未显示其他引用的子查询。
监视数据库的应用程序可能会频繁使用
INFORMATION_SCHEMA
表。
要最有效地为这些表编写查询,请使用以下一般准则:
尝试仅查询
INFORMATION_SCHEMA
作为数据字典表的视图的表。
尝试仅查询静态元数据。 选择列或使用动态元数据的检索条件以及静态元数据会增加处理动态元数据的开销。
INFORMATION_SCHEMA
查询中
数据库和表名的比较行为
可能与您的预期不同。
有关详细信息,请参见
第10.8.7节“在INFORMATION_SCHEMA搜索中使用排序规则”
。
这些
INFORMATION_SCHEMA
表实现为数据字典表的视图,因此对它们的查询从数据字典中检索信息:
CHARACTER_SETS CHECK_CONSTRAINTS COLLATIONS COLLATION_CHARACTER_SET_APPLICABILITY 列 活动 FILES INNODB_COLUMNS INNODB_DATAFILES INNODB_FIELDS INNODB_FOREIGN INNODB_FOREIGN_COLS INNODB_INDEXES INNODB_TABLES INNODB_TABLESPACES INNODB_TABLESPACES_BRIEF INNODB_TABLESTATS KEY_COLUMN_USAGE 参数 PARTITIONS REFERENTIAL_CONSTRAINTS RESOURCE_GROUPS 例程 SCHEMATA 统计 TABLES TABLE_CONSTRAINTS TRIGGERS VIEWS VIEW_ROUTINE_USAGE VIEW_TABLE_USAGE
某些类型的值(即使对于非视图
INFORMATION_SCHEMA
表)也可以通过数据字典中的查找来检索。
这包括诸如数据库和表名,表类型和存储引擎之类的值。
某些
INFORMATION_SCHEMA
表包含提供表统计信息的列:
STATISTICS.CARDINALITY TABLES.AUTO_INCREMENT TABLES.AVG_ROW_LENGTH TABLES.CHECKSUM TABLES.CHECK_TIME TABLES.CREATE_TIME TABLES.DATA_FREE TABLES.DATA_LENGTH TABLES.INDEX_LENGTH TABLES.MAX_DATA_LENGTH TABLES.TABLE_ROWS TABLES.UPDATE_TIME
这些列表示动态表元数据; 也就是说,随着表格内容的变化而变化的信息。
默认情况下,MySQL的检索从这些列的缓存值
mysql.index_stats
和
mysql.table_stats
这比直接从存储引擎检索统计数据更有效,当列查询字典表。
如果缓存的统计信息不可用或已过期,MySQL将从存储引擎中检索最新的统计信息,并将它们缓存在
mysql.index_stats
和
mysql.table_stats
字典表中。
后续查询将检索缓存的统计信息,直到缓存的统计信息过期。
该
information_schema_stats_expiry
会话变量定义的时间段之前缓存统计到期。
默认值为86400秒(24小时),但时间段可以延长至一年。
要随时更新给定表的缓存值,请使用
ANALYZE
TABLE
。
在这些情况下,
查询统计信息列不会在
mysql.index_stats
和
mysql.table_stats
字典表中
存储或更新统计信息
:
缓存的统计信息尚未过期。
何时
information_schema_stats_expiry
设置为0。
当服务器开始
read_only
,
super_read_only
,
transaction_read_only
,或
innodb_read_only
模式。
当查询还提取性能架构数据时。
information_schema_stats_expiry
是一个会话变量,每个客户端会话可以定义自己的到期值。
从存储引擎检索并由一个会话缓存的统计信息可供其他会话使用。
如果
innodb_read_only
启用
了
系统变量,则
ANALYZE
TABLE
可能会失败,因为它无法更新数据字典中使用的统计表
InnoDB
。
对于
ANALYZE
TABLE
更新密钥分发的操作,即使操作更新表本身(例如,如果它是
MyISAM
表)
,也可能发生故障
。
要获取更新的分布统计信息,请设置
information_schema_stats_expiry=0
。
对于
INFORMATION_SCHEMA
作为数据字典表视图实现的表,基础数据字典表上的索引允许优化器构建有效的查询执行计划。
要查看优化程序所做的选择,请使用
EXPLAIN
。
要同时查看服务器用于执行
INFORMATION_SCHEMA
查询的查询,请
SHOW
WARNINGS
立即
使用
以下内容
EXPLAIN
。
请考虑此语句,该语句标识
utf8mb4
字符集的
排序规则
:
MySQL的>SELECT COLLATION_NAME
FROM INFORMATION_SCHEMA.COLLATION_CHARACTER_SET_APPLICABILITY
WHERE CHARACTER_SET_NAME = 'utf8mb4';
+ ---------------------------- + | COLLATION_NAME | + ---------------------------- + | utf8mb4_general_ci | | utf8mb4_bin | | utf8mb4_unicode_ci | | utf8mb4_icelandic_ci | | utf8mb4_latvian_ci | | utf8mb4_romanian_ci | | utf8mb4_slovenian_ci | ...
服务器如何处理该语句?
要了解,请使用
EXPLAIN
:
MySQL的>EXPLAIN SELECT COLLATION_NAME
FROM INFORMATION_SCHEMA.COLLATION_CHARACTER_SET_APPLICABILITY
WHERE CHARACTER_SET_NAME = 'utf8mb4'\G
*************************** 1。排******************** ******* id:1 select_type:SIMPLE 表:cs 分区:NULL 类型:const possible_keys:PRIMARY,名字 关键:名称 key_len:194 ref:const 行:1 过滤:100.00 额外:使用索引 *************************** 2.排******************** ******* id:1 select_type:SIMPLE 表:col 分区:NULL 类型:ref possible_keys:character_set_id key:character_set_id key_len:8 ref:const 行:68 过滤:100.00 额外:NULL 2行,1个警告(0.01秒)
要查看用于满足该语句的查询,请使用
SHOW
WARNINGS
:
MySQL的> SHOW WARNINGS\G
*************************** 1。排******************** *******
等级:注意
代码:1003
消息:/ *选择#1 * /选择`mysql` .col` .name` AS`COLLATION_NAME`
来自`mysql``character_sets``cs`
加入`mysql``collations``col`
where((``mysql` .col``character_set_id` ='45')
和('utf8mb4'='utf8mb4'))
如所指示
SHOW
WARNINGS
,该服务器处理该查询上
COLLATION_CHARACTER_SET_APPLICABILITY
为对查询
character_sets
和
collations
在数据字典表
mysql
系统数据库。
监视数据库的应用程序可能会频繁使用性能架构表。
要最有效地为这些表编写查询,请利用它们的索引。
例如,包含一个
WHERE
子句,
该
子句根据与索引列中特定值的比较来限制检索到的行。
大多数性能架构表都有索引。
不包含通常包含很少行或不太可能经常查询的表的表。
性能模式索引使优化器可以访问除全表扫描之外的执行计划。
这些索引还可以提高相关对象的性能,例如
sys
使用这些表的架构视图。
要查看给定的Performance Schema表是否具有索引及其含义,请使用
SHOW
INDEX
或
SHOW
CREATE TABLE
:
MySQL的>SHOW INDEX FROM performance_schema.accounts\G
*************************** 1。排******************** ******* 表:帐户 非独特:0 Key_name:ACCOUNT Seq_in_index:1 Column_name:USER 排序规则:NULL 基数:NULL Sub_part:NULL 打包:NULL 空:是的 Index_type:HASH 评论: Index_comment: 可见:是的 *************************** 2.排******************** ******* 表:帐户 非独特:0 Key_name:ACCOUNT Seq_in_index:2 Column_name:HOST 排序规则:NULL 基数:NULL Sub_part:NULL 打包:NULL 空:是的 Index_type:HASH 评论: Index_comment: 可见:是的 MySQL的>SHOW CREATE TABLE performance_schema.rwlock_instances\G
*************************** 1。排******************** ******* 表:rwlock_instances 创建表:CREATE TABLE`rwlock_instances`( `NAME` varchar(128)NOT NULL, `OBJECT_INSTANCE_BEGIN` bigint(20)unsigned NOT NULL, `WRITE_LOCKED_BY_THREAD_ID` bigint(20)unsigned DEFAULT NULL, `READ_LOCKED_BY_COUNT` int(10)unsigned NOT NULL, PRIMARY KEY(`OBJECT_INSTANCE_BEGIN`), KEY`NAME`(`NAME`), KEY`WRITE_LOCKED_BY_THREAD_ID`(`WRITE_LOCKED_BY_THREAD_ID`) )ENGINE = PERFORMANCE_SCHEMA DEFAULT CHARSET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci
要查看Performance Schema查询的执行计划以及它是否使用任何索引,请使用
EXPLAIN
:
MySQL的>EXPLAIN SELECT * FROM performance_schema.accounts
WHERE (USER,HOST) = ('root','localhost')\G
*************************** 1。排******************** ******* id:1 select_type:SIMPLE 表:帐户 分区:NULL 类型:const possible_keys:ACCOUNT key:ACCOUNT key_len:278 ref:const,const 行:1 过滤:100.00 额外:NULL
该
EXPLAIN
输出表明优化器使用
accounts
表
ACCOUNT
包括索引
USER
和
HOST
列。
性能模式索引是虚拟的:它们是性能模式存储引擎的构造,不使用内存或磁盘存储。 性能模式将索引信息报告给优化器,以便它可以构建有效的执行计划。 性能模式反过来使用有关要查找内容的优化器信息(例如,特定键值),以便它可以执行有效的查找而无需构建实际的索引结构。 此实现提供了两个重要的好处:
它完全避免了经常更新的表通常会产生的维护成本。
它在查询执行的早期阶段减少了检索的数据量。 对于索引列的条件,性能模式仅有效地返回满足查询条件的表行。 如果没有索引,性能模式将返回表中的所有行,要求优化程序稍后针对每一行评估条件以生成最终结果。
性能模式索引是预定义的,不能删除,添加或更改。
性能模式索引与哈希索引类似。 例如:
它们仅用于使用
=
或
<=>
运算符的
相等比较
。
它们是无序的。
如果查询结果必须具有特定的行排序特征,请包含
ORDER
BY
子句。
有关哈希索引的其他信息,请参见 第8.3.9节“B树和哈希索引的比较” 。
这部分解释了如何加快数据更改语句:
INSERT
,
UPDATE
,和
DELETE
。
传统的OLTP应用程序和现代Web应用程序通常会执行许多小数据更改操作,其中并发性至关重要。
数据分析和报告应用程序通常会同时运行影响多行的数据更改操作,其中主要考虑因素是写入大量数据并使索引保持最新的I / O.
对于插入和更新大量数据(在行业中称为
“
提取 - 转换 - 加载
”
),有时您使用模仿其效果的其他SQL语句或外部命令
INSERT
,
UPDATE
和
DELETE
陈述。
要优化插入速度,请将许多小型操作组合到一个大型操作中。 理想情况下,您进行单个连接,一次发送许多新行的数据,并将所有索引更新和一致性检查延迟到最后。
插入行所需的时间由以下因素决定,其中数字表示大致比例:
连接:(3)
向服务器发送查询:(2)
解析查询:(2)
插入行:(1×行的大小)
插入索引:(1×索引数)
结束:(1)
这没有考虑打开表的初始开销,每个并发运行的查询都会执行一次。
N
假设B树索引,
表的大小会减慢日志插入索引的速度
。
您可以使用以下方法来加速插入:
如果要同时从同一客户端插入多行,请使用
INSERT
包含多个
VALUES
列表的
语句
一次
插入多行。
与使用单独的单行
INSERT
语句
相比,这要快得多(在某些情况下要快很多倍)
。
如果要将数据添加到非空表中,则可以调整
bulk_insert_buffer_size
变量以使数据插入更快。
请参见
第5.1.8节“服务器系统变量”
。
从文本文件加载表时,请使用
LOAD
DATA
。
这通常比使用
INSERT
语句
快20倍
。
请参见
第13.2.7节“LOAD DATA语法”
。
利用列具有默认值的事实。 仅在要插入的值与默认值不同时才显式插入值。 这减少了MySQL必须执行的解析并提高了插入速度。
有关
表的
特定提示
,
请参见
第8.5.5节“InnoDB表的批量数据加载”
InnoDB
。
有关
表的
特定提示
,
请参见
第8.6.2节“MyISAM表的批量数据加载”
MyISAM
。
更新语句优化为类似于
SELECT
具有写入的额外开销的查询。
写入速度取决于要更新的数据量和更新的索引数。
未更改的索引不会更新。
获得快速更新的另一种方法是延迟更新,然后在以后连续执行许多更新。 如果锁定表,一次执行多个更新比一次执行多个更新要快得多。
对于
MyISAM
使用动态行格式
的
表,将行更新为更长的总长度可能会拆分该行。
如果经常这样做,
OPTIMIZE
TABLE
偶尔
使用非常重要
。
请参见
第13.7.3.4节“OPTIMIZE TABLE语法”
。
删除
MyISAM
表中
各行所需的时间
与索引数完全成比例。
要更快地删除行,可以通过增加
key_buffer_size
系统变量
来增加密钥缓存的大小
。
请参见
第5.1.1节“配置服务器”
。
要删除
MyISAM
表中的
所有行
,
要快于
。
截断操作不是事务安全的;
在活动事务或活动表锁定过程中尝试一个错误时发生错误。
请参见
第13.1.37节“TRUNCATE TABLE语法”
。
TRUNCATE TABLE
tbl_name
DELETE FROM
tbl_name
您的权限设置越复杂,所有SQL语句的开销就越大。
简化
GRANT
语句
建立的特权
使MySQL能够在客户端执行语句时减少权限检查开销。
例如,如果您不授予任何表级或列级权限,则服务器无需检查
tables_priv
和
columns_priv
表
的内容
。
同样,如果您对任何帐户都没有资源限制,则服务器不必执行资源计数。
如果您的语句处理负载非常高,请考虑使用简化的授权结构来减少权限检查开销。
本节列出了一些用于提高查询处理速度的其他技巧:
如果您的应用程序发出多个数据库请求以执行相关更新,则将语句组合到存储例程中可以帮助提高性能。 同样,如果您的应用程序基于多个列值或大量数据计算单个结果,则将计算组合到UDF(用户定义的函数)可以帮助提高性能。 然后,由此产生的快速数据库操作可供其他查询,应用程序甚至用不同编程语言编写的代码重用。 有关更多信息 , 请参见 第24.2节“使用存储的例程” 和 第29.4节“向MySQL添加新函数” 。
要解决
ARCHIVE
表
中发生的任何压缩问题
,请使用
OPTIMIZE
TABLE
。
请参见
第16.5节“ARCHIVE存储引擎”
。
如果可能,将报告分类为 “ 实时 ” 或 “ 统计 ” ,其中统计报告所需的数据仅从定期从实时数据生成的汇总表中创建。
如果您的数据不符合行列表结构,则可以将数据打包并存储到
BLOB
列中。
在这种情况下,您必须在应用程序中提供打包和解压缩信息的代码,但这可能会节省I / O操作以读取和写入相关值集。
使用Web服务器,将图像和其他二进制资产存储为文件,路径名存储在数据库中而不是文件本身。 大多数Web服务器在缓存文件方面比数据库内容更好,因此使用文件通常更快。 (尽管在这种情况下您必须自己处理备份和存储问题。)
如果您需要非常高的速度,请查看低级MySQL接口。
例如,通过
直接
访问MySQL
InnoDB
或
MyISAM
存储引擎,与使用SQL接口相比,可以大幅提高速度。
复制可以为某些操作提供性能优势。 您可以在复制服务器之间分发客户端检索以分割负载。 为避免在进行备份时减慢主服务器的速度,可以使用从服务器进行备份。 请参见 第17章, 复制 。
提高操作性能的最佳方法
SELECT
是在查询中测试的一个或多个列上创建索引。
索引条目的作用类似于表行的指针,允许查询快速确定哪些行与
WHERE
子句
中的条件匹配
,并检索这些行的其他列值。
可以索引所有MySQL数据类型。
尽管为查询中使用的每个可能列创建索引很有诱惑力,但不必要的索引会浪费空间并浪费时间让MySQL确定要使用哪些索引。 索引还会增加插入,更新和删除的成本,因为必须更新每个索引。 您必须找到适当的平衡,以使用最佳索引集实现快速查询。
索引用于快速查找具有特定列值的行。 如果没有索引,MySQL必须从第一行开始,然后读取整个表以查找相关行。 表越大,成本越高。 如果表中有相关列的索引,MySQL可以快速确定要在数据文件中间寻找的位置,而无需查看所有数据。 这比按顺序读取每一行要快得多。
大多数MySQL索引(
PRIMARY KEY
,
UNIQUE
,
INDEX
和
FULLTEXT
)存储在
B树
。
例外:空间数据类型的索引使用R树;
MEMORY
表也支持
哈希索引
;
InnoDB
使用反向列表作为
FULLTEXT
索引。
通常,使用索引,如以下讨论中所述。
第8.3.9节“B树和哈希索引的比较”
MEMORY
中描述了
哈希索引特有的特性(如
表中所
用
)
。
MySQL使用索引进行这些操作:
WHERE
快速
查找与
子句
匹配的行
。
消除行的考虑。 如果在多个索引之间有选择,MySQL通常使用找到最小行数(最具 选择性的 索引)的索引。
如果表具有多列索引,则优化程序可以使用索引的任何最左前缀来查找行。
例如,如果你有一个三列索引上
(col1, col2, col3)
,你有索引的搜索功能
(col1)
,
(col1, col2)
以及
(col1, col2,
col3)
。
有关更多信息,请参见
第8.3.6节“多列索引”
。
在执行连接时从其他表中检索行。
如果声明它们的类型和大小相同,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
) FROMtbl_name
WHEREkey_part1
= 10;
如果对可用索引的最左前缀(例如,
)
进行排序或分组,则对表进行排序或分组
。
如果后面跟着所有关键部分
,则按相反顺序读取密钥。
(或者,如果索引是降序索引,
则按
正向顺序读取密钥。)请参见
第8.2.1.15节“ORDER BY优化”
,
第8.2.1.16节“GROUP BY优化”
和
第8.3.13节“降序指数“
。
ORDER BY
key_part1
,
key_part2
DESC
在某些情况下,可以优化查询以在不咨询数据行的情况下检索值。 (为查询提供所有必要结果的 索引 称为 覆盖索引 。)如果查询仅使用表中包含某些索引的列,则可以从索引树中检索所选值以获得更快的速度:
SELECTkey_part3
FROMtbl_name
WHEREkey_part1
= 1
对于小型表或报表查询处理大多数或所有行的大型表的查询,索引不太重要。 当查询需要访问大多数行时,顺序读取比通过索引更快。 顺序读取可以最大限度地减少磁盘搜索,即使查询不需要所有行也是如此。 有关 详细信息 , 请参见 第8.2.1.22节“避免全表扫描” 。
表的主键表示您在最重要的查询中使用的列或列集。
它具有关联的索引,以实现快速查询性能。
查询性能受益于
NOT NULL
优化,因为它不能包含任何
NULL
值。
使用
InnoDB
存储引擎,表数据在物理上进行组织,以根据主键或列进行超快速查找和排序。
如果您的表很大且很重要,但没有明显的列或列集用作主键,则可以创建一个单独的列,其中包含自动增量值以用作主键。 使用外键连接表时,这些唯一ID可用作指向其他表中相应行的指针。
MySQL允许
SPATIAL
在
NOT NULL
几何值列
上
创建
索引
(请参见
第11.5.10节“创建空间索引”
)。
优化程序检查
SRID
索引列
的
属性,以确定要用于比较的空间参考系统(SRS),并使用适合SRS的计算。
(在MySQL 8.0之前,优化器
SPATIAL
使用笛卡尔计算
执行
索引值的
比较
;如果列包含具有非笛卡尔SRID的值,则此类操作的结果是未定义的。)
要使比较正常工作,
SPATIAL
索引中的
每个列都
必须受SRID限制。
也就是说,列定义必须包含显式
SRID
属性,并且所有列值必须具有相同的SRID。
优化程序
SPATIAL
仅
考虑
SRID限制列的索引:
限制为笛卡尔SRID的列的索引启用笛卡尔边界框计算。
限制为地理SRID的列的索引启用地理边界框计算。
优化程序忽略
SPATIAL
没有
SRID
属性的
列上的索引
(因此不受SRID限制)。
MySQL仍然维护着这样的索引,如下所示:
他们对表的修改(更新
INSERT
,
UPDATE
,
DELETE
,等等)。
更新发生就像索引是笛卡尔坐标一样,即使该列可能包含笛卡尔和地理值的混合。
它们仅用于向后兼容(例如,在MySQL 5.7中执行转储和在MySQL 8.0中恢复的能力)。
由于
SPATIAL
非SRID限制的列上的索引对优化器没用,因此应修改每个此类列:
验证列中的所有值是否具有相同的SRID。
要确定几何列中包含的SRID
col_name
,请使用以下查询:
SELECT DISTINCT ST_SRID(col_name
)FROMtbl_name
;
如果查询返回多行,则该列包含多个SRID。 在这种情况下,请修改其内容,以使所有值具有相同的SRID。
重新定义列以具有显式
SRID
属性。
重新创建
SPATIAL
索引。
如果一个表有很多列,并且您查询了许多不同的列组合,那么将频率较低的数据拆分为每个都有几列的单独表可能会很有效,并通过复制数字ID将它们与主表关联起来。主表中的列。 这样,每个小表都可以有一个主键来快速查找其数据,您可以使用连接操作查询所需的列集。 根据数据的分布方式,查询可能会执行较少的I / O并占用较少的高速缓存,因为相关列在磁盘上打包在一起。 (为了最大限度地提高性能,查询尝试从磁盘中读取尽可能少的数据块;
最常见的索引类型涉及单个列,在数据结构中存储该列的值的副本,允许快速查找具有相应列值的行。
B树数据结构可以让索引快速查找特定值,一组值,或值的范围,对应于运营商,如
=
,
>
,
≤
,
BETWEEN
,
IN
,等等,一在
WHERE
子句。
每个存储引擎定义每个表的最大索引数和最大索引长度。 请参见 第15章, InnoDB存储引擎 和 第16章, 备用存储引擎 。 所有存储引擎每个表至少支持16个索引,总索引长度至少为256个字节。 大多数存储引擎都有更高的限制
有关列索引的其他信息,请参见 第13.1.15节“CREATE INDEX语法” 。
使用
字符串列的索引规范中的语法,可以创建仅使用
列
的前
几个字符
的索引
。
以这种方式仅索引列值的前缀可以使索引文件更小。
索引
或
列时,
必须
为索引指定前缀长度。
例如:
col_name
(N
)N
BLOB
TEXT
CREATE TABLE test(blob_col BLOB,INDEX(blob_col(10)));
对于
InnoDB
使用
REDUNDANT
或
COMPACT
行格式的
表,
前缀最长可达767个字节
。
对于
InnoDB
使用
DYNAMIC
或
COMPRESSED
行格式的
表,
前缀长度限制为3072字节
。
对于MyISAM表,前缀长度限制为1000个字节。
前缀限制以字节为单位,而在前缀长度
CREATE
TABLE
,
ALTER
TABLE
和
CREATE
INDEX
语句被解释为非二进制串类型的字符数(
CHAR
,
VARCHAR
,
TEXT
二进制串类型()和字节数
BINARY
,
VARBINARY
,
BLOB
)。
在为使用多字节字符集的非二进制字符串列指定前缀长度时,请考虑这一点。
如果搜索项超过索引前缀长度,则索引用于排除不匹配的行,并检查剩余的行以查找可能的匹配项。
有关索引前缀的其他信息,请参见 第13.1.15节“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
列中
显示全文查询
;
在这种情况下,在以后的执行期间不需要访问表。
MySQL可以创建复合索引(即多列索引)。 索引最多可包含16列。 对于某些数据类型,您可以索引列的前缀(请参见 第8.3.5节“列索引” )。
MySQL可以对测试索引中所有列的查询使用多列索引,或者只测试第一列,前两列,前三列等的查询。 如果在索引定义中以正确的顺序指定列,则单个复合索引可以加速同一表上的多种查询。
多列索引可以视为排序数组,其行包含通过连接索引列的值创建的值。
作为复合索引的替代方法,您可以 根据其他列的信息 引入 “ 散列 ” 列。 如果此列很短,相当独特且已编制索引,则它可能比 许多列上 的 “ 宽 ” 索引 更快 。 在MySQL中,使用这个额外的列很容易:
SELECT * FROMtbl_name
WHEREhash_col
= MD5(CONCAT(val1
,val2
)) ANDcol1
=val1
ANDcol2
=val2
;
假设一个表具有以下规范:
CREATE TABLE测试( id INT NOT NULL, last_name CHAR(30)NOT NULL, first_name CHAR(30)NOT NULL, PRIMARY KEY(id), INDEX名称(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'或first_name ='Michael';
假设您发出以下内容
SELECT
语句:
SELECT * FROMtbl_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 * FROMtbl_name
WHERE col1 =val1
; SELECT * FROMtbl_name
WHERE col1 =val1
AND col2 =val2
; SELECT * FROMtbl_name
WHERE col2 =val2
; SELECT * FROMtbl_name
WHERE col2 =val2
AND col3 =val3
;
如果存在索引
(col1, col2, col3)
,则只有前两个查询使用索引。
第三和第四个查询确实包括索引的列,但不使用索引来进行查找,因为
(col2)
和
(col2, col3)
不是的最左边的前缀
(col1, col2, col3)
。
始终检查所有查询是否确实使用了您在表中创建的索引。
使用该
EXPLAIN
语句,如
第8.8.1节“使用EXPLAIN优化查询”中所述
。
存储引擎收集有关表的统计信息以供优化程序使用。 表统计信息基于值组,其中值组是具有相同键前缀值的一组行。 出于优化程序的目的,一个重要的统计数据是平均值组大小。
MySQL使用以下方式的平均值组大小:
估计每次
ref
访问
必须读取的行数
估计部分连接将产生多少行; 也就是说,此表单的操作将产生的行数:
(...)JOINtbl_name
ONtbl_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
all时
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
方法。
了解B树和哈希数据结构有助于预测不同查询在索引中使用这些数据结构的不同存储引擎上的执行情况,特别是对于
MEMORY
允许您选择B树或哈希索引
的
存储引擎。
A B树索引可以在使用表达式中使用的对列的比较
=
,
>
,
>=
,
<
,
<=
,或
BETWEEN
运营商。
LIKE
如果参数to
LIKE
是一个不以通配符开头的常量字符串,则
索引也可用于
比较
。
例如,以下
SELECT
语句使用索引:
SELECT * FROMtbl_name
WHEREkey_col
LIKE'Patrick%'; SELECT * FROMtbl_name
WHEREkey_col
LIKE'Pat%_ck%';
在第一个语句中,仅
考虑
具有行的行
。
在第二个语句中,仅
考虑
具有行的行
。
'Patrick'
<=
key_col
<
'Patricl''Pat' <=
key_col
< 'Pau'
以下
SELECT
语句不使用索引:
SELECT * FROMtbl_name
WHEREkey_col
LIKE'%Patrick%'; SELECT * FROMtbl_name
WHEREkey_col
LIKEother_col
;
在第一个语句中,
LIKE
值以通配符开头。
在第二个语句中,该
LIKE
值不是常量。
如果您使用
且
超过三个字符,MySQL使用
Turbo Boyer-Moore算法
初始化字符串的模式,然后使用此模式更快地执行搜索。
... LIKE
'%
string
%'string
使用
雇用索引
的搜索
是否
col_name
IS
NULLcol_name
已编制索引。
不跨越
子句中
所有
AND
级别的
任何索引
WHERE
不用于优化查询。
换句话说,为了能够使用索引,必须在每个
AND
组中
使用索引的前缀
。
以下
WHERE
子句使用索引:
...... WHEREindex_part1
= 1 ANDindex_part2
= 2 ANDother_column
= 3 / *index
= 1 ORindex
= 2 * / ...... WHEREindex
= 1或A = 10 ANDindex
= 2 / *优化如“index_part1
='hello'”* / ...... WHEREindex_part1
='你好'ANDindex_part3
= 5 / *可以使用索引index1
但不能使用index2
或index3
* / ...... WHEREindex1
= 1 ANDindex2
= 2 ORindex1
= 3 ANDindex3
= 3;