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

Are MySQL where clauses linear. How to optimise

假设我有一个包含大约 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 子句的标准按顺序删除行?

单独的 table 可以通过两种方式访问​​ - 完整 table 扫描,扫描索引 - 选择行 ID - 然后扫描 table 的那些行。当 table 上存在多个索引时,将仅使用 1,因为它可以直接访问 table 上的行。当访问 table 行时,将评估附加的 where 条件。

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

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

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

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

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

"composite"(多列)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

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

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

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

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

"linear" 的反义词是 "logarithmetic" 或 "quadradic"(等等)。但是,这些不适用于 BTree 索引,所以我不明白你的问题在哪里。

索引指南:http://mysql.rjweb.org/doc.php/index_cookbook_mysql

您可以想象这里讨论的 3 列索引和 3 部分 WHEREs 连接在一起。那就是 WHERE blah = 1234truefalse,索引在 categorydisplayarchived 上。现在它就像一个 "single" 列索引用于单个 WHERE 测试。

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

与此同时,INDEX(archived) 实际上毫无用处。当索引中的 "flag" 时,优化器通常会说 "Why bother looking through the index; I'll just have to bounce back and forth between the index's BTree and the data's BTree; I may as well simply scan straight through the data (and toss rows he does not want)." 更重要的是,对于原始查询 [=62],INDEX(archived), INDEX(display), INDEX(category) 不如 INDEX(archived, display, category) 有用=].一次只使用一个索引(通常)。

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

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

这是一个演示:我用 512 行填充了一个 table,并设置了只有几行有 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 列上没有索引。因此查询将执行 table-扫描,检查每一行。

现在我使用 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 秒,但是 运行ning SHOW PROCESSLIST 表明它将保持 运行ning:

+----+-----------------+-----------+------+---------+--------+------------------------+---------------------------------------------+
| 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                            |
+----+-----------------+-----------+------+---------+--------+------------------------+---------------------------------------------+