MySQL where 子句是线性的。如何优化

Posted

技术标签:

【中文标题】MySQL where 子句是线性的。如何优化【英文标题】:Are MySQL where clauses linear. How to optimise 【发布时间】:2019-11-30 18:54:31 【问题描述】:

假设我有一个包含大约 50,000 种产品的产品数据库,为后端系统和网站提供数据,就网站而言,有些是实时的,有些是存档的,有些是“关闭的”(仅在后面可用出于某种原因结束管理员)。

对网站的查询可能如下所示:

SELECT name, category, price FROM products WHERE category=‘1234’

(显然极其简化)

现在如上所述,我只想要那些未存档且切换显示在网站上的内容。

SELECT name, category, price FROM products WHERE category=‘1234’ AND display=true AND archived=false 

这显然会奏效。

我故意不提索引。我知道在上面的示例中,我的“类别”列是否被索引会对查询速度产生很大影响,但这不是我的问题。

假设我知道该数据库中的 50,000 种产品中约有一半是旧新闻、存档项目,我的问题是:

是:

SELECT name, category, price FROM products WHERE archived=false AND category=‘1234’ AND display=true 

比我之前写的查询更快的查询?

我的想法是,如果 mysql 在“archived=false”上立即从查询中删除 25,000 个产品,甚至在考虑这些产品属于哪个类别之前,它可能会更快(当然假设索引为“archived”)

因此我的标题是“MySQL Where 子句是否是线性的”——它是否按照 WHERE 子句的标准按顺序消除行?

【问题讨论】:

简短回答:条件的顺序无关紧要。优化器将选择它认为是“最佳”的那个。但是 - 索引很重要。但是 - 正如你所写 - 这不是你的问题。 好酷。我将不再担心子句的顺序,只要确保所有需要的东西都被索引! 【参考方案1】:

重新排列WHERE 子句的ANDed 组件对性能没有影响。

拥有一个“复合”(多列)INDEX 可能很重要。在这种情况下,顺序可能非常重要。

在您的简单示例中,

WHERE category='1234'
  AND display=true
  AND archived=false

最佳索引是INDEX(category, display, archived) 并且索引中的任何排序都同样好

然而,与

WHERE category > '1234'
  AND display=true
  AND archived=false

现在最优索引是

INDEX(display, archived,   -- in either order
      category)            -- range last

在我在这里列出的示例中,处理过程如下:

    向下钻取包含索引的 BTree 到 WHERE 给出的起点。 通过索引线性向前扫描。 对于每个条目,进入数据的 BTree 以找到 name, category, price

如果您只有INDEX(category, ...)WHERE category > ...,它将忽略INDEX 中的其他两列。这会降低索引的效率——读取并抓取几行,读取但跳过几行,等等。

CATEGORY IN (123, 234, 345) 是另一回事。在这种情况下,处理可能会跳过索引。这比“阅读但跳过”要好,但不如简单地阅读和使用每个条目。

“线性”的反义词是“对数”或“二次”(等等)。但是,这些不适用于 BTree 索引,所以我不明白你的问题是什么。

关于索引的食谱:http://mysql.rjweb.org/doc.php/index_cookbook_mysql

您可以将此处讨论的 3 列索引和 3 部分 WHEREs 想象为连接在一起。那就是 WHERE blah = 1234truefalse 与 categorydisplayarchived 上的索引。现在它就像针对单个 WHERE 测试使用的“单个”列索引。

如果索引和 WHERE 的列数不同,讨论会变得更加复杂。

同时,INDEX(archived) 几乎毫无用处。当索引中的“标志”时,优化器通常会说“为什么要查看索引;我只需要在索引的 BTree 和数据的 BTree 之间来回切换;我还不如直接扫描数据(并折腾他不想要的行)。”更重要的是,INDEX(archived), INDEX(display), INDEX(category) 不如 INDEX(archived, display, category) 有用对于原始查询。一次只使用一个索引(通常)。

【讨论】:

【参考方案2】:

可以通过 2 种方式单独访问表 - 全表扫描、扫描索引 - 选择行 ID - 然后扫描表的这些行。当表上存在多个索引时,将只使用 1,因为这可以直接访问表上的行。访问表行时将评估其他 where 条件。

在没有任何索引的情况下 - where 条件根据比较的成本是线性的,这比表访问小得多,所以它并不重要(除非你有一个非常复杂的函数调用)。

存在 1 个索引时 - 该索引的效率(其大小、基数和密度)决定成本。其他条件比较是线性的,但成本又更小,所以没关系(除非你有一个非常复杂的函数调用)。

如果存在多个索引 - 将选择最有效的索引。

获取 SQL 的计划,这将显示它将如何执行。我通常会专注于为您的案例建立一个有效的索引。对于小表,我不会建立任何索引,而是让全盘扫描发生。

【讨论】:

【参考方案3】:

正如其他答案所说,您应该创建索引来优化,而不是依赖于 WHERE 子句中的术语顺序。 MySQL 的优化器知道如何重新排序术语以匹配索引中列的顺序。换句话说,MySQL 知道AND 是commutative。

但要更直接地回答您最初的问题:MySQL 也知道如何简化布尔表达式。

这是一个演示:我用 512 行填充了一个表,并设置了几行以具有 display=true

mysql> select count(*) from mytable;
+----------+
| count(*) |
+----------+
|      512 |
+----------+
1 row in set (0.01 sec)

mysql> select count(*) from mytable where display = true;
+----------+
| count(*) |
+----------+
|        3 |
+----------+
1 row in set (0.03 sec)

display 列上没有此测试的索引。因此查询将进行表扫描,检查每一行。

现在我使用 sleep() 函数通过布尔表达式进行查询。如果 MySQL 不做捷径,它将评估每一行的sleep(),并花费 512 秒。如果它使用捷径,它将仅对第一项为真的行评估 sleep()

mysql> select count(*) from mytable where display = true and sleep(1);
+----------+
| count(*) |
+----------+
|        0 |
+----------+
1 row in set (3.01 sec)

有趣——即使我们颠倒术语的顺序,MySQL 仍然是捷径。显然,它知道在评估其他表达式之前先评估行数据。

mysql> select count(*) from mytable where sleep(1) and display=true;
+----------+
| count(*) |
+----------+
|        0 |
+----------+
1 row in set (3.01 sec)

没有 display=true 的术语,它只是等待。我不会让它运行完整的 512 秒,但运行 SHOW PROCESSLIST 表明它将继续运行:

+----+-----------------+-----------+------+---------+--------+------------------------+---------------------------------------------+
| Id | User            | Host      | db   | Command | Time   | State                  | Info                                        |
+----+-----------------+-----------+------+---------+--------+------------------------+---------------------------------------------+
|  9 | root            | localhost | test | Query   |     82 | User sleep             | select count(*) from mytable where sleep(1) |
| 11 | root            | localhost | NULL | Query   |      0 | starting               | show processlist                            |
+----+-----------------+-----------+------+---------+--------+------------------------+---------------------------------------------+

【讨论】:

这很有趣!谢谢。

以上是关于MySQL where 子句是线性的。如何优化的主要内容,如果未能解决你的问题,请参考以下文章

Mysql在where子句中优化子查询

如何使用数千个 WHERE 子句优化 SQL 查询

如何在 WHERE 子句中优化使用 COALESCE()?

如何使用动态数组使用where子句查询mysql数据库

MySQL 对于千万级的大表要怎么优化

查询优化(MySql/Sql):将函数移出 where 子句