WHERE 子句中的字段顺序会影响 MySQL 的性能吗?

Posted

技术标签:

【中文标题】WHERE 子句中的字段顺序会影响 MySQL 的性能吗?【英文标题】:Does the order of fields in a WHERE clause affect performance in MySQL? 【发布时间】:2011-05-01 10:46:24 【问题描述】:

我在一个表中有两个索引字段 - typeuserid(单个索引,而不是复合索引)。

types 的字段值非常有限(假设它只有 0 或 1),因此 50% 的表记录具有相同的type。另一方面,userid 值来自一个更大的集合,因此具有相同userid 的记录数量很少。

这些查询中的任何一个是否会比另一个运行得更快:

select * from table where type=1 and userid=5
select * from table where userid=5 and type=1

另外,如果两个字段都没有被索引,它会改变行为吗?

【问题讨论】:

Does order of where clauses matter in SQL的可能重复 【参考方案1】:

SQL 被设计为一种声明性语言,而不是一种程序性语言。因此查询优化器在确定如何应用它们时应该考虑 where 子句谓词的顺序。

我可能会过度简化以下关于 SQL 查询优化器的讨论。一年前,我按照这些思路写了(这很有趣!)。如果您真的想深入研究现代查询优化,请参阅来自 O'Reilly 的 Dan Tow 的 SQL Tuning。

在一个简单的 SQL 查询优化器中,SQL 语句首先被编译成一个关系代数操作树。这些操作都将一个或多个表作为输入,并生成另一个表作为输出。 Scan 是从数据库中读取表的顺序扫描。 排序 生成一个排序表。 Select 生成一个表,其行是根据某些选择条件从另一个表中选择的。 Project 生成一个表,其中仅包含另一个表的某些列。 Cross Product 采用两个表并生成一个输出表,该表由它们的行的每个可能的配对组成。

令人困惑的是,SQL SELECT 子句被编译成一个关系代数Project,而 WHERE 子句变成一个关系代数Select。 FROM 子句变成一个或多个Joins,每个加入两个表并产生一个表。还有其他关系代数运算涉及集合并集、交集、差集和隶属关系,但让我们保持简单。

这棵树确实需要优化。例如,如果您有:

select E.name, D.name 
from Employee E, Department D 
where E.id = 123456 and E.dept_id = D.dept_id

在 500 个部门的 5,000 名员工中,执行未优化的树会盲目地产生一个员工和一个部门的所有可能组合(交叉产品),然后选择需要的一种组合。 Employee的Scan会产生一个5000条记录的表,Department的Scan会产生一个500条记录的表,这两个表的Cross Product将生成一个包含 2,500,000 条记录的表,而 E.id 上的 Select 将采用该 2,500,000 条记录表并丢弃除一条之外的所有记录,即所需的记录。

[当然,真正的查询处理器会尽量不在内存中实现所有这些中间表。]

因此,查询优化器会遍历树并应用各种优化。一种是将每个 Select 分解成一个 Selects 链,每个原始 Select 的***条件都有一个,这些条件和-ed在一起。 (这称为“合取范式”。)然后各个较小的 Selects 在树中移动并与其他关系代数运算合并以形成更有效的运算。

在上面的示例中,优化器首先将 E.id = 123456 上的 Select 推到昂贵的 Cross Product 操作之下。这意味着 跨产品 只生成 500 行(该员工和一个部门的每个组合一个)。然后 E.dept_id = D.dept_id 的顶层 Select 过滤掉 499 个不需要的行。还不错。

如果Employee的id字段有索引,那么优化器可以结合Employee的Scan和E.id = 123456上的Select形成一个快速索引查找。这意味着只有一个 Employee 行从磁盘读取到内存中,而不是 5,000 行。情况正在好转。

最后的主要优化是在 E.dept_id = D.dept_id 上取 Select 并将其与 Cross Product 结合。这将其转换为关系代数 Equijoin 操作。这本身并没有多大作用。但是如果 Department.dept_id 上有一个索引,那么提供 Equijoin 的 Department 的较低级别顺序 Scan 可以变成一个非常快速的索引 Lookup 我们一名员工的部门记录。

较小的优化涉及将 项目 操作向下推。如果查询的顶层只需要 E.name 和 D.name,并且条件需要 E.id、E.dept_id 和 D.dept_id,那么 Scan 操作就不必使用所有其他列构建中间表,在查询执行期间节省空间。我们已经将一个非常慢的查询变成了两个索引查找,仅此而已。

进一步了解原始问题,假设您有:

select E.name 
from Employee E 
where E.age > 21 and E.state = 'Delaware'

未优化的关系代数树在执行时将扫描 5,000 名员工,并生成例如特拉华州 21 岁以上的 126 名员工。查询优化器还对数据库中的值有一些粗略的了解。它可能知道 E.state 列包含公司所在的 14 个州,以及有关 E.age 分布的信息。因此,首先它会查看是否对任一字段进行了索引。如果 E.state 是,则使用该索引仅根据其最后计算的统计信息来挑选查询处理器怀疑在特拉华州的少数员工是有意义的。如果只有 E.age,查询处理器可能会认为不值得,因为 96% 的员工年龄在 22 岁及以上。因此,如果 E.state 被索引,我们的查询处理器会中断 Select 并将 E.state = 'Delaware' 与 Scan 合并以将其转换为更有效的 Select em>索引扫描。

假设在此示例中 E.state 和 E.age 上没有索引。组合的Select 操作发生在Employee 的顺序“扫描”之后。 Select 中的哪个条件先完成有区别吗?可能不是很多。查询处理器可能会将它们保留在 SQL 语句中的原始顺序中,或者它可能会更复杂一些并查看预期的费用。从统计数据中,它会再次发现 E.state = 'Delaware' 条件应该更具选择性,因此它会反转条件并首先执行此操作,这样只有 126 次 E.age > 21 次比较,而不是 5,000 次.或者它可能会意识到字符串相等比较比整数比较昂贵得多,并且不考虑顺序。

无论如何,这一切都非常复杂,您的句法条件顺序不太可能产生影响。除非您遇到真正的性能问题并且您的数据库供应商使用条件顺序作为提示,否则我不会担心。

【讨论】:

【参考方案2】:

大多数查询优化器使用条件出现的顺序作为提示。如果其他一切都相同,则它们将遵循该顺序。

但是,许多事情可以覆盖它:

第二个字段有索引而第一个没有 有统计数据表明字段 2 更具选择性 第二个字段更容易搜索(varchar(max) vs int

因此(对于所有 SQL 优化问题都是如此)除非您观察到性能问题,否则最好为了清晰而不是(想象的)性能进行优化。

【讨论】:

进行这样的优化不需要任何成本,所以如果重要的话为什么不呢。那么有什么更好的方法——先输入用户名,然后再输入? @serg: 95% 的情况下 mysql 会正确选择 userid,4% 会选择 typeid;对于剩下的 1%,你可以把 typeid 放在第一位 :) @Andomar 在旧线程中发帖。你从哪里得到这些数字 95% vs 4% vs 1%?还是你只是编造的? @Andomar - 如何优化和如何不优化的好例子。【参考方案3】:

它不应该在你的小例子中。查询优化器应该做正确的事。您可以通过在查询前面添加explain 来确定。 MySQL 会告诉你它是如何将事物连接在一起的,以及它需要搜索多少行才能进行连接。例如:

explain select * from table where type=1 and userid=5

如果它们没有被索引,它可能会改变行为。

【讨论】:

以上是关于WHERE 子句中的字段顺序会影响 MySQL 的性能吗?的主要内容,如果未能解决你的问题,请参考以下文章

mysql中的五子查询

MySQL重要知识点

SQL 更新中 WHERE 子句的速度影响

7_mysql查询之where子句

MySQL 查询语句

调用视图的查询中的 where 子句是不是会影响视图本身的过滤器?