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 子句是线性的。如何优化的主要内容,如果未能解决你的问题,请参考以下文章