MySQL EXPLAIN 中的 "filtered" 列告诉我什么,我该如何使用它?

What is the "filtered" column in MySQL EXPLAIN telling me, and how can I make use of it?

MySQL 5.7 documentation 状态:

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.00100.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)、使用的键、使用的键长度和检查的行数。