MySQL EXPLAIN 中的 "filtered" 列告诉我什么,我该如何使用它?
What is the "filtered" column in MySQL EXPLAIN telling me, and how can I make use of it?
The filtered
column indicates an estimated percentage of table rows that will be filtered by the table condition. That is, rows
shows the estimated number of rows examined and rows × filtered / 100
shows the number of rows that will be joined with previous tables.
为了更好地理解这一点,我使用 MySQL Sakila Sample Database 在查询中进行了尝试。有问题的 table 具有以下结构:
mysql> SHOW CREATE TABLE film \G
*************************** 1. row ***************************
Table: film
Create Table: CREATE TABLE `film` (
`film_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(255) NOT NULL,
`description` text,
`release_year` year(4) DEFAULT NULL,
`language_id` tinyint(3) unsigned NOT NULL,
`original_language_id` tinyint(3) unsigned DEFAULT NULL,
`rental_duration` tinyint(3) unsigned NOT NULL DEFAULT '3',
`rental_rate` decimal(4,2) NOT NULL DEFAULT '4.99',
`length` smallint(5) unsigned DEFAULT NULL,
`replacement_cost` decimal(5,2) NOT NULL DEFAULT '19.99',
`rating` enum('G','PG','PG-13','R','NC-17') DEFAULT 'G',
`special_features` set('Trailers','Commentaries','Deleted Scenes','Behind the Scenes') DEFAULT NULL,
`last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`film_id`),
KEY `idx_title` (`title`),
KEY `idx_fk_language_id` (`language_id`),
KEY `idx_fk_original_language_id` (`original_language_id`),
CONSTRAINT `fk_film_language` FOREIGN KEY (`language_id`) REFERENCES `language` (`language_id`) ON UPDATE CASCADE,
CONSTRAINT `fk_film_language_original` FOREIGN KEY (`original_language_id`) REFERENCES `language` (`language_id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1001 DEFAULT CHARSET=utf8
这是查询的 EXPLAIN
计划:
mysql> EXPLAIN SELECT * FROM film WHERE release_year=2006 \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: film
partitions: NULL
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 1000
filtered: 10.00
Extra: Using where
这个 table 的样本数据集总共有 1,000 行,并且所有行的 release_year
都设置为 2006。使用 MySQL 文档中的公式:
rows x filtered / 100
= "将与之前的 tables
连接的行数
所以,
1,000 x 10 / 100 = 100
= "100 行将与前面的 table 行合并"
嗯?什么"previous table"?这里没有 JOIN
。
文档中引用的第一部分怎么样? "Estimated percentage of table rows that will be filtered by the table condition." 嗯,table 条件是 release_year = 2006
, 所有 记录都有那个值,所以 filtered
不应该是 0.00
或 100.00
(取决于 "filtered" 的含义)?
可能是因为 release_year
上没有索引所以它表现得很奇怪?所以我创建了一个:
mysql> CREATE INDEX test ON film(release_year);
filtered
列现在显示 100.00
。那么,在我添加索引之前它不应该显示 0.00
吗?嗯。如果我让 table 的一半 release_year
是 2006,而另一半不是?
mysql> UPDATE film SET release_year=2017 ORDER BY RAND() LIMIT 500;
Query OK, 500 rows affected (0.03 sec)
Rows matched: 500 Changed: 500 Warnings: 0
现在 EXPLAIN
看起来像这样:
mysql> EXPLAIN SELECT * FROM film WHERE release_year=2006 \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: film
partitions: NULL
type: ref
possible_keys: test
key: test
key_len: 2
ref: const
rows: 500
filtered: 100.00
Extra: Using index condition
而且,由于我决定进一步混淆自己:
mysql> EXPLAIN SELECT * FROM film WHERE release_year!=2006 \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: film
partitions: NULL
type: ALL
possible_keys: test
key: NULL
key_len: NULL
ref: NULL
rows: 1000
filtered: 50.10
Extra: Using where
因此,table 条件和 "joined with previous tables"?
将过滤估计 501 行
根本看不懂
我知道这是一个 "estimate",但这个估计是基于什么?如果存在索引将估计值移动到 100.00
,它不存在时不应该是 0.00
,而不是 10.00
?最后一个查询的 50.10
结果是什么?
filtered
是否对确定查询是否可以进一步优化有用,或者 如何 进一步优化它,或者通常只是 "noise" 可以忽略吗?
所以你必须写其中一个才能完全理解,但估计不是基于内容而是基于关于内容和统计数据的元数据。
让我给你一个具体的虚构示例 我并不是说任何 sql 平台都在做我在这里描述的这只是一个例子:
You have a table with 1000 rows and max value for year column is 2010 and min value for year column is 2000 -- without any other information you can "guess" that where year = 2007 will take 10% of all items assuming an average distribution.
在这种情况下,它将 return 1000 和 10。
回答你的最后一个问题 filtered
如果(如上所示)你只有一个 "default" 值会把所有东西都扔掉——你可能会决定使用 say null 而不是默认设置可以让您的查询执行得更好。或者您可能会看到统计数据需要更频繁地出现在您的表格中,因为范围变化很大。这在很大程度上取决于给定的平台和您的数据模型。
…number of rows that will be joined with previous tables…
在没有任何连接的情况下,我相信这可以用来表示行数
UPDATE - 文档,至少现在,说“following tables”但是重点仍然存在,谢谢@WilsonHauck
依次举出你的每一个例子
1000 行,全部来自 2006 年,没有索引…
EXPLAIN SELECT * FROM film WHERE release_year = 2006
key: NULL
rows: 1000
filtered: 10.00
Extra: Using where
此处引擎预计访问 1000 行,并预计 return 其中约 10%
由于查询没有使用索引,预测每一行都将被检查是有意义的,但不幸的是,过滤后的估计是不准确的。我不知道引擎是如何做出这个预测的,但因为它不知道所有的行都来自 2006 年(直到它检查它们)..这不是世界上最疯狂的事情
也许在没有进一步信息的情况下,引擎期望任何简单的 =
条件将结果集减少到可用行的 10%
1000 行,2006 年的一半,索引…
EXPLAIN SELECT * FROM film WHERE release_year = 2006
key: test
rows: 500
filtered: 100.00
Extra: Using index condition
此处引擎期望访问 500 行并期望 return 所有这些
现在查询正在使用新索引,引擎可以做出更准确的预测。它可以很快看到 500 行符合条件,并且只需要访问这些就可以满足查询
EXPLAIN SELECT * FROM film WHERE release_year != 2006
key: NULL
rows: 1000
filtered: 50.10
Extra: Using where
此处引擎希望访问 1000 行,return其中的 50.10%
引擎选择不使用索引,也许 !=
操作在这种情况下不像 =
那么简单,因此预测每一行都将是有意义的去过
但是,该引擎已经相当准确地预测了这些访问过的行中有多少将被 returned。我不知道 .10% 是从哪里来的,但也许引擎已经使用索引或先前查询的结果来识别大约 50% 的行将匹配条件
这有点黑暗,但 filtered
值确实为您提供了一些相当有用的信息,并让您深入了解引擎做出某些决定的原因
如果行数高而过滤行估计值低(且准确),这可能是一个很好的迹象,表明仔细应用索引可以加快查询速度
我发现 "filtered" 列没有用。
EXPLAIN
(今天)使用粗略的统计数据推导出它显示的许多数字。 "Filtered" 是他们有多糟糕的一个例子。
为了更深入地了解数字,运行 EXPLAIN FORMAT=JSON SELECT ...
这在 MySQL 的较新版本中将为每个可能的执行计划提供 "cost"。因此,它会为您提供有关它考虑了哪些选项以及所选计划的 "cost basis" 的线索。不幸的是,它使用一个常量来获取一行——没有给出该行是来自磁盘还是已经被缓存的权重。
事后可以通过 STATUS
"Handler%" 值得出更精确的工作完成量度。我在 http://mysql.rjweb.org/doc.php/index_cookbook_mysql .
中讨论了这一点,以及简单的优化技术
直方图存在于8.0和10.0;他们将提供更高的精度。它们可能有助于使 "filtered" 有点用处。
来自今天 url 上现有的 5.7 文档
https://dev.mysql.com/doc/refman/5.7/en/explain-output.html
已过滤(JSON 名称:已过滤)
筛选的列表示将按 table 条件筛选的 table 行的估计百分比。最大值为 100,这意味着没有发生行过滤。值从 100 开始减少表示过滤量增加。 rows 显示检查的估计行数,rows × filtered 显示将与以下 table 连接的行数。例如rows为1000,filtered为50.00(50%),则下面的table要连接的行数为1000×50%=500。
how can I make use of it?
高数字(最好是 filtered: 100.00
)表示查询正在使用 "good" 索引,否则索引将毫无用处。
考虑一个 table 和 deleted_at TIMESTAMP NULL
列(软删除),上面没有索引,并且 99% 的行包含 NULL
(未删除)。现在使用
这样的查询
SELECT * FROM my_table WHERE deleted_at IS NULL
你可能会看到
filtered: 99.00
在这种情况下,deleted_at
上的索引将无用,因为第二次查找的开销(在聚簇索引中查找过滤的行)。在最坏的情况下,如果优化器决定使用索引,索引甚至可能会损害性能。
但是如果您使用
查询 "deleted" 行
SELECT * FROM my_table WHERE deleted_at IS NOT NULL
你应该得到类似
的东西
filtered: 1.00
低数字表示查询可以从索引中受益。如果您现在在 (deleted_at)
上创建索引,EXPLAIN 将向您显示
filtered: 100.00
我会说:任何 >= 10% 的值都不值得创建索引。至少对于单列条件。
一个不同的故事,当你有多个列的条件时,比如
WHERE a=1 AND b=2
假设 table 中有 100 万行并且两列的基数均为 10(每列包含 10 个不同的值)随机分布,在 (a)
上有一个索引,引擎将分析 100K 行( 10% 由于 a
上的索引)和 return 10K 行(10% 的 10% 由于 b
上的条件)。 EXPLAIN 应该会显示 rows: 100000, filtered: 10.00
。在这种情况下,将 (a)
上的单列索引扩展到 (a, b)
上的复合索引应该可以将查询时间缩短 10 倍。EXPLAIN 会向您展示 rows: 10000, filtered: 100.00
.
但是 - 这更多的是一种理论。原因:我经常看到 filtered: 100.00
而不是 1.00
,至少对于低基数列和至少在 MariaDB 上是这样。 MySQL 可能有所不同(我现在无法测试),但您的示例显示了类似的行为(10.00
而不是 100.00
)。
实际上我不记得 filtered
值什么时候帮助过我。我首先要看的是:table 的顺序(如果它是 JOIN)、使用的键、使用的键长度和检查的行数。
The
filtered
column indicates an estimated percentage of table rows that will be filtered by the table condition. That is,rows
shows the estimated number of rows examined androws × filtered / 100
shows the number of rows that will be joined with previous tables.
为了更好地理解这一点,我使用 MySQL Sakila Sample Database 在查询中进行了尝试。有问题的 table 具有以下结构:
mysql> SHOW CREATE TABLE film \G
*************************** 1. row ***************************
Table: film
Create Table: CREATE TABLE `film` (
`film_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
`title` varchar(255) NOT NULL,
`description` text,
`release_year` year(4) DEFAULT NULL,
`language_id` tinyint(3) unsigned NOT NULL,
`original_language_id` tinyint(3) unsigned DEFAULT NULL,
`rental_duration` tinyint(3) unsigned NOT NULL DEFAULT '3',
`rental_rate` decimal(4,2) NOT NULL DEFAULT '4.99',
`length` smallint(5) unsigned DEFAULT NULL,
`replacement_cost` decimal(5,2) NOT NULL DEFAULT '19.99',
`rating` enum('G','PG','PG-13','R','NC-17') DEFAULT 'G',
`special_features` set('Trailers','Commentaries','Deleted Scenes','Behind the Scenes') DEFAULT NULL,
`last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`film_id`),
KEY `idx_title` (`title`),
KEY `idx_fk_language_id` (`language_id`),
KEY `idx_fk_original_language_id` (`original_language_id`),
CONSTRAINT `fk_film_language` FOREIGN KEY (`language_id`) REFERENCES `language` (`language_id`) ON UPDATE CASCADE,
CONSTRAINT `fk_film_language_original` FOREIGN KEY (`original_language_id`) REFERENCES `language` (`language_id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1001 DEFAULT CHARSET=utf8
这是查询的 EXPLAIN
计划:
mysql> EXPLAIN SELECT * FROM film WHERE release_year=2006 \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: film
partitions: NULL
type: ALL
possible_keys: NULL
key: NULL
key_len: NULL
ref: NULL
rows: 1000
filtered: 10.00
Extra: Using where
这个 table 的样本数据集总共有 1,000 行,并且所有行的 release_year
都设置为 2006。使用 MySQL 文档中的公式:
rows x filtered / 100
= "将与之前的 tables
所以,
1,000 x 10 / 100 = 100
= "100 行将与前面的 table 行合并"
嗯?什么"previous table"?这里没有 JOIN
。
文档中引用的第一部分怎么样? "Estimated percentage of table rows that will be filtered by the table condition." 嗯,table 条件是 release_year = 2006
, 所有 记录都有那个值,所以 filtered
不应该是 0.00
或 100.00
(取决于 "filtered" 的含义)?
可能是因为 release_year
上没有索引所以它表现得很奇怪?所以我创建了一个:
mysql> CREATE INDEX test ON film(release_year);
filtered
列现在显示 100.00
。那么,在我添加索引之前它不应该显示 0.00
吗?嗯。如果我让 table 的一半 release_year
是 2006,而另一半不是?
mysql> UPDATE film SET release_year=2017 ORDER BY RAND() LIMIT 500;
Query OK, 500 rows affected (0.03 sec)
Rows matched: 500 Changed: 500 Warnings: 0
现在 EXPLAIN
看起来像这样:
mysql> EXPLAIN SELECT * FROM film WHERE release_year=2006 \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: film
partitions: NULL
type: ref
possible_keys: test
key: test
key_len: 2
ref: const
rows: 500
filtered: 100.00
Extra: Using index condition
而且,由于我决定进一步混淆自己:
mysql> EXPLAIN SELECT * FROM film WHERE release_year!=2006 \G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: film
partitions: NULL
type: ALL
possible_keys: test
key: NULL
key_len: NULL
ref: NULL
rows: 1000
filtered: 50.10
Extra: Using where
因此,table 条件和 "joined with previous tables"?
将过滤估计 501 行根本看不懂
我知道这是一个 "estimate",但这个估计是基于什么?如果存在索引将估计值移动到 100.00
,它不存在时不应该是 0.00
,而不是 10.00
?最后一个查询的 50.10
结果是什么?
filtered
是否对确定查询是否可以进一步优化有用,或者 如何 进一步优化它,或者通常只是 "noise" 可以忽略吗?
所以你必须写其中一个才能完全理解,但估计不是基于内容而是基于关于内容和统计数据的元数据。
让我给你一个具体的虚构示例 我并不是说任何 sql 平台都在做我在这里描述的这只是一个例子:
You have a table with 1000 rows and max value for year column is 2010 and min value for year column is 2000 -- without any other information you can "guess" that where year = 2007 will take 10% of all items assuming an average distribution.
在这种情况下,它将 return 1000 和 10。
回答你的最后一个问题 filtered
如果(如上所示)你只有一个 "default" 值会把所有东西都扔掉——你可能会决定使用 say null 而不是默认设置可以让您的查询执行得更好。或者您可能会看到统计数据需要更频繁地出现在您的表格中,因为范围变化很大。这在很大程度上取决于给定的平台和您的数据模型。
…number of rows that will be joined with previous tables…
在没有任何连接的情况下,我相信这可以用来表示行数
UPDATE - 文档,至少现在,说“following tables”但是重点仍然存在,谢谢@WilsonHauck
依次举出你的每一个例子
1000 行,全部来自 2006 年,没有索引…
EXPLAIN SELECT * FROM film WHERE release_year = 2006
key: NULL
rows: 1000
filtered: 10.00
Extra: Using where
此处引擎预计访问 1000 行,并预计 return 其中约 10%
由于查询没有使用索引,预测每一行都将被检查是有意义的,但不幸的是,过滤后的估计是不准确的。我不知道引擎是如何做出这个预测的,但因为它不知道所有的行都来自 2006 年(直到它检查它们)..这不是世界上最疯狂的事情
也许在没有进一步信息的情况下,引擎期望任何简单的 =
条件将结果集减少到可用行的 10%
1000 行,2006 年的一半,索引…
EXPLAIN SELECT * FROM film WHERE release_year = 2006
key: test
rows: 500
filtered: 100.00
Extra: Using index condition
此处引擎期望访问 500 行并期望 return 所有这些
现在查询正在使用新索引,引擎可以做出更准确的预测。它可以很快看到 500 行符合条件,并且只需要访问这些就可以满足查询
EXPLAIN SELECT * FROM film WHERE release_year != 2006
key: NULL
rows: 1000
filtered: 50.10
Extra: Using where
此处引擎希望访问 1000 行,return其中的 50.10%
引擎选择不使用索引,也许 !=
操作在这种情况下不像 =
那么简单,因此预测每一行都将是有意义的去过
但是,该引擎已经相当准确地预测了这些访问过的行中有多少将被 returned。我不知道 .10% 是从哪里来的,但也许引擎已经使用索引或先前查询的结果来识别大约 50% 的行将匹配条件
这有点黑暗,但 filtered
值确实为您提供了一些相当有用的信息,并让您深入了解引擎做出某些决定的原因
如果行数高而过滤行估计值低(且准确),这可能是一个很好的迹象,表明仔细应用索引可以加快查询速度
我发现 "filtered" 列没有用。
EXPLAIN
(今天)使用粗略的统计数据推导出它显示的许多数字。 "Filtered" 是他们有多糟糕的一个例子。
为了更深入地了解数字,运行 EXPLAIN FORMAT=JSON SELECT ...
这在 MySQL 的较新版本中将为每个可能的执行计划提供 "cost"。因此,它会为您提供有关它考虑了哪些选项以及所选计划的 "cost basis" 的线索。不幸的是,它使用一个常量来获取一行——没有给出该行是来自磁盘还是已经被缓存的权重。
事后可以通过 STATUS
"Handler%" 值得出更精确的工作完成量度。我在 http://mysql.rjweb.org/doc.php/index_cookbook_mysql .
直方图存在于8.0和10.0;他们将提供更高的精度。它们可能有助于使 "filtered" 有点用处。
来自今天 url 上现有的 5.7 文档 https://dev.mysql.com/doc/refman/5.7/en/explain-output.html
已过滤(JSON 名称:已过滤)
筛选的列表示将按 table 条件筛选的 table 行的估计百分比。最大值为 100,这意味着没有发生行过滤。值从 100 开始减少表示过滤量增加。 rows 显示检查的估计行数,rows × filtered 显示将与以下 table 连接的行数。例如rows为1000,filtered为50.00(50%),则下面的table要连接的行数为1000×50%=500。
how can I make use of it?
高数字(最好是 filtered: 100.00
)表示查询正在使用 "good" 索引,否则索引将毫无用处。
考虑一个 table 和 deleted_at TIMESTAMP NULL
列(软删除),上面没有索引,并且 99% 的行包含 NULL
(未删除)。现在使用
SELECT * FROM my_table WHERE deleted_at IS NULL
你可能会看到
filtered: 99.00
在这种情况下,deleted_at
上的索引将无用,因为第二次查找的开销(在聚簇索引中查找过滤的行)。在最坏的情况下,如果优化器决定使用索引,索引甚至可能会损害性能。
但是如果您使用
查询 "deleted" 行SELECT * FROM my_table WHERE deleted_at IS NOT NULL
你应该得到类似
的东西filtered: 1.00
低数字表示查询可以从索引中受益。如果您现在在 (deleted_at)
上创建索引,EXPLAIN 将向您显示
filtered: 100.00
我会说:任何 >= 10% 的值都不值得创建索引。至少对于单列条件。
一个不同的故事,当你有多个列的条件时,比如
WHERE a=1 AND b=2
假设 table 中有 100 万行并且两列的基数均为 10(每列包含 10 个不同的值)随机分布,在 (a)
上有一个索引,引擎将分析 100K 行( 10% 由于 a
上的索引)和 return 10K 行(10% 的 10% 由于 b
上的条件)。 EXPLAIN 应该会显示 rows: 100000, filtered: 10.00
。在这种情况下,将 (a)
上的单列索引扩展到 (a, b)
上的复合索引应该可以将查询时间缩短 10 倍。EXPLAIN 会向您展示 rows: 10000, filtered: 100.00
.
但是 - 这更多的是一种理论。原因:我经常看到 filtered: 100.00
而不是 1.00
,至少对于低基数列和至少在 MariaDB 上是这样。 MySQL 可能有所不同(我现在无法测试),但您的示例显示了类似的行为(10.00
而不是 100.00
)。
实际上我不记得 filtered
值什么时候帮助过我。我首先要看的是:table 的顺序(如果它是 JOIN)、使用的键、使用的键长度和检查的行数。