【问题标题】:What is the "filtered" column in MySQL EXPLAIN telling me, and how can I make use of it?MySQL EXPLAIN 中的“过滤”列告诉我什么,我该如何使用它?
【发布时间】:2017-05-22 22:24:58
【问题描述】:

MySQL 5.7 documentation 声明:

filtered 列表示将按表条件过滤的表行的估计百分比。也就是说,rows 显示检查的估计行数,rows × filtered / 100 显示将与以前的表连接的行数。

为了更好地理解这一点,我在使用MySQL Sakila Sample Database 的查询中进行了尝试。有问题的表具有以下结构:

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

此表的示例数据集共有 1,000 行,所有行都将 release_year 设置为 2006。使用 MySQL 文档中的公式:

rows x filtered / 100 = "将与以前的表连接的行数

所以,

1,000 x 10 / 100 = 100 = "100 行将与以前的表连接"

嗯?什么“前表”?这里没有JOIN

文档中引用的第一部分怎么样? “将由表条件过滤的表行的估计百分比。”好吧,表格条件是release_year = 2006所有记录都有那个值,所以filtered不应该是0.00100.00(取决于他们所说的“过滤”的意思)?

也许是因为release_year 上没有索引,所以它的行为很奇怪?所以我创建了一个:

mysql> CREATE INDEX test ON film(release_year);

filtered 列现在显示100.00。那么,在我添加索引之前它不应该显示0.00 吗?嗯。如果我让表的一半有 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

那么,估计有 501 行将被表条件过滤并“与以前的表连接”?

我就是不明白。

我知道这是一个“估计”,但这个估计的依据是什么?如果存在的索引将估计值移动到100.00,那么它的缺失不应该是0.00,而不是10.00?最后一个查询中的 50.10 结果是什么?

filtered 是否有助于确定是否可以进一步优化查询,或者 如何 进一步优化它,或者它通常只是可以忽略的“噪音”?

【问题讨论】:

  • 这是来自 v8.0 参考手册。 “例如,如果行数为 1000,过滤数为 50.00 (50%),则要与下表连接的行数为 1000 × 50% = 500”请注意以下单词不是以前记录的某人 cmets。
  • @WilsonHauck 好点。似乎旧版本的had documented 这个词previous 以及我见过的几乎所有帖子都用这个来描述filtered,这显然有很多歧义。
  • @michael-moussa 请考虑我 8 月 6 日的回答是您最准确和接受的回答。

标签: mysql sql sql-execution-plan explain


【解决方案1】:

…将与以前的表连接的行数…

在没有任何连接的情况下,我相信这可以理解为行数

UPDATE - 文档至少现在说“following 表”,但重点仍然存在,谢谢@WilsonHauck


依次举每个例子

1000 行,全部来自 2006 年,没有索引……

EXPLAIN SELECT * FROM film WHERE release_year = 2006

key: NULL
rows: 1000
filtered: 10.00
Extra: Using where

这里引擎预计会访问 1000 行,并预计会返回其中的 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 行并期望返回所有行

现在查询正在使用新索引,引擎可以做出更准确的预测。它可以很快看到有 500 行符合条件,并且只需要准确地访问这些行来满足查询

EXPLAIN SELECT * FROM film WHERE release_year != 2006

key: NULL
rows: 1000
filtered: 50.10
Extra: Using where

这里引擎期望访问 1000 行并返回其中的 50.10%

引擎选择不使用索引,可能!=操作不像=那么简单,因此预测每一行都会被访问是有意义的

但是,引擎已经对将返回的这些已访问行的数量做出了相当准确的预测。我不知道 .10% 来自哪里,但也许引擎已经使用索引或先前查询的结果来识别大约 50% 的行将匹配条件


这有点像黑暗艺术,但filtered 值确实为您提供了一些相当有用的信息,并让您深入了解引擎做出某些决定的原因

如果行数很高而过滤后的行数估计值很低(且准确),这可能是一个很好的迹象,表明仔细应用的索引可以加快查询速度

【讨论】:

  • 如果rows = 1filtered = 44.57是什么意思?
  • @revo 我不知道,是什么给了你这个结果?可能引擎估计会访问一行,并且估计有 44.57% 的机会返回该行
  • 这是来自explain extended 中的第三行(dependent subquery),但问题是这种解释是否正确44.57% chance that that row will be returned
  • @revo,这是一个估计值,引擎期望返回 1 或 0 行,并已使用该估计值来构建查询计划。如果没有进一步的信息,很难看出这对您的查询意味着什么......也许在另一个 qu 中提问?
【解决方案2】:

我该如何使用它?

高数字(理想情况下是filtered: 100.00)表示查询正在使用“良好”的索引,否则索引将无用。

考虑一个带有deleted_at TIMESTAMP NULL 列(软删除)但没有索引的表,并且99% 的行包含NULL(未删除)。现在有一个类似的查询

SELECT * FROM my_table WHERE deleted_at IS NULL

你可能会看到

filtered: 99.00

在这种情况下,deleted_at 上的索引将无用,因为第二次查找的开销(在聚集索引中查找过滤的行)。在最坏的情况下,如果优化器决定使用索引,甚至可能会损害性能。

但是,如果您使用

查询“已删除”行
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

假设表中有 1M 行,并且两列的基数为 10(每列包含 10 个不同的值)随机分布,索引在 (a) 上,引擎将分析 100K 行(10% 由于 @ 上的索引987654333@) 并返回 10K 行(由于b 的条件,10% 的 10%)。解释应该告诉你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 值何时帮助过我。我首先要看的是:表的顺序(如果是 JOIN)、使用的键、使用的键长度和检查的行数。

【讨论】:

    【解决方案3】:

    来自 url 上的现有 5.7 文档 https://dev.mysql.com/doc/refman/5.7/en/explain-output.html

    已过滤(JSON 名称:已过滤)

    过滤后的列表示将按表格条件过滤的表格行的估计百分比。最大值为 100,表示未过滤行。从 100 开始减小的值表示过滤量增加。 rows 显示检查的估计行数, rows × filters 显示将与下表连接的行数。例如rows为1000,filtered为50.00(50%),则与下表join的行数为1000×50%=500。

    【讨论】:

    • 请注意最后一句的结尾是 FOLLOWING,而不是某些 cmets 中记录的先前。
    【解决方案4】:

    我发现“过滤”列没用。

    EXPLAIN(今天)使用粗略的统计数据来推导出它显示的许多数字。 “过滤”是它们有多糟糕的一个例子。

    要更深入地了解数字,请运行 EXPLAIN FORMAT=JSON SELECT ... 在较新版本的 MySQL 中,这将为每个可能的执行计划提供“成本”。因此,它为您提供了有关它考虑了哪些选项以及所选计划的“成本基础”的线索。不幸的是,它使用一个常量来获取一行 - 没有考虑该行是来自磁盘还是已经缓存。

    事后可以通过STATUS“Handler%”值得出更精确的工作完成指标。我在http://mysql.rjweb.org/doc.php/index_cookbook_mysql 中讨论了这一点,以及简单的优化技术。

    直方图存在于 8.0 和 10.0 中;他们将提供更高的精度。它们可能有助于使“过滤”变得有用。

    【讨论】:

      【解决方案5】:

      因此,您必须编写其中之一才能完全理解,但估计不是基于内容,而是基于内容和统计数据的元数据。

      让我给你一个具体的虚构示例,我并不是说任何 sql 平台都可以执行我在这里描述的操作,这只是一个示例:

      您有一个包含 1000 行的表,年份列的最大值是 2010,年份列的最小值是 2000 - 如果没有任何其他信息,您可以“猜测” where year = 2007 将占所有项目的 10%,假设平均分布。

      在这种情况下,它将返回 1000 和 10。

      回答你的最后一个问题filtered 可能很有用,如果(如上所示)你只有一个“默认”值会抛出所有问题——你可能决定使用说 null 而不是默认值来获取你的查询表现得更好。或者您可能会看到需要更频繁地在您的表上运行统计信息,因为范围变化很​​大。这在很大程度上取决于给定的平台和您的数据模型。

      【讨论】:

      • 您的示例对于假设的组成数据库是有意义的,但并不能真正帮助我理解 MySQL 是如何做到的或它的含义。 release_year 列可以为空,并且没有默认值。该表中的 1,000 行现在都有release_year = 2006,但无论我在查询中使用什么值(2006、2016、IS NULL= NULL 等),我仍然得到filtered: 10.00。我得到的唯一变化是如果我做!=,这会导致同样神秘的filtered: 90.00
      • @MichaelMoussa -- 我可以想出一个为什么它会给出这些结果的原因,或者我们可以查看源代码来找出答案。但这对我来说似乎是一个无用的练习。
      • @MichaelMoussa -- 我只是想确认一下 -- 你在这张桌子上做了ANALYZE TABLE,对吗? (dev.mysql.com/doc/refman/5.7/en/analyze-table.html)。如果你还没有,那么它所做的一切都不是基于对表格内容的分析,而是某种“默认”值。
      • @MichaelMoussa,如果引擎估计通用 = 条件将返回 10% 的行,那么它估计 != 条件将返回 90% 的行是有道理的
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-12-22
      • 1970-01-01
      • 2010-11-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-06-16
      相关资源
      最近更新 更多