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
在我在这里列出的示例中,处理过程如下:
- 向下钻取包含索引的 BTree 到
WHERE
给定的起点。
- 通过索引向前线性扫描。
- 对于每个条目,进入数据的 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 知道 AND
是 commutative.
但更直接地回答您原来的问题: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 |
+----+-----------------+-----------+------+---------+--------+------------------------+---------------------------------------------+
假设我有一个包含大约 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
在我在这里列出的示例中,处理过程如下:
- 向下钻取包含索引的 BTree 到
WHERE
给定的起点。 - 通过索引向前线性扫描。
- 对于每个条目,进入数据的 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 知道 AND
是 commutative.
但更直接地回答您原来的问题: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 |
+----+-----------------+-----------+------+---------+--------+------------------------+---------------------------------------------+