WHERE子句中的字段顺序是否影响MySQL的性能?

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

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

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

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

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

SQL被devise成声明性语言,而不是程序性语言。 所以查询优化器在决定如何应用它们时应该考虑where子句谓词的顺序。

我可能会过度简化以下关于SQL查询优化器的讨论。 我在一年前写了这些话(这真是太好玩了!)。 如果您真的想深入了解现代查询优化,请参阅O'Reilly的Dan Tow的SQL Tuning 。

在一个简单的SQL查询优化器中,SQL语句首先被编译成关系代数运算树。 这些操作都将一个或多个表作为input,并生成另一个表作为输出。 扫描是从数据库中读取表格的顺序扫描。 sorting产生一个sorting表。 select根据某些select条件生成一个表格,其行从另一个表格中select。 项目生成一个只有另一个表的某些列的表。 交叉产品需要两张表格,并生成一个输出表格,其中包含了每一行可能的配对。

令人困惑的是,SQL SELECT子句被编译成一个关系代数项目 ,而WHERE子句变成了一个关系代数Select 。 FROM子句变成一个或多个连接 ,每个连接占用两个表并生成一个表。 还有其他的关系代数操作涉及集合,交集,差异和成员,但让我们保持简单。

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

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

在500个部门中有5,000名员工,执行一个未优化的树会盲目地产生一个员工和一个部门(一个交叉产品 )的所有可能的组合,然后只select一个需要的组合。 员工扫描将产生一个5000个logging表, 扫描部门将产生一个500个logging表,这两个表的交叉产品将产生一个250万个logging表,并且Select on E.id将采用这个250万个logging表和丢弃所有的一个,被通缉的纪录。

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

所以查询优化器遍历树并应用各种优化。 一个是将每个select分解为一个select链,一个用于每个原始select的最高级别条件,以及一个select 。 (这被称为“连接范式”)。然后,单个较小的select符在树中移动,并与其他关系代数运算合并,形成更有效的select。

在上面的例子中,优化器首先将E.id = 123456上的Select放在昂贵的Cross Product操作之下。 这意味着交叉产品只产生500行(每个员工和一个部门的组合)。 然后,顶级select E.dept_id = D.dept_id过滤出499个不需要的行。 不错。

如果员工ID字段上有一个索引,那么优化器可以将员工扫描与E.id = 123456上的select组合 ,以形成快速索引查找 。 这意味着只有一个员工行从磁盘而不是5000读入内存。 事情在好转。

最后的主要优化是在E.dept_id = D.dept_id上selectSelect并将其与Cross产品组合 。 这将其转化为关系代数Equijoin操作。 这本身并没有多大的作用。 但是,如果Department.dept_id上有一个索引,那么提供Equijoin的下级顺序Scan部门可以变成一个非常快速的索引查找我们的一个员工的部门logging。

较less的优化涉及推动项目操作。 如果查询的顶层只需要E.name和D.name,并且条件需要E.id,E.dept_id和D.dept_id,则扫描操作不需要与其他所有其他列,在查询执行期间节省空间。 我们已经把一个可怕的缓慢的查询转化为两个索引查找,而不是其他的。

让我们回到最初的问题,假设你有:

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

未经优化的关系代数树在执行时会扫描5000名员工,并生成特拉华州的126个年龄大于21岁的员工。查询优化器对数据库中的值也有一些粗略的想法。 它可能知道E.state专栏有14个公司有位置的地方,还有一些关于E.age分布的地方。 所以首先看看是否有字段被索引。 如果E.state是,那么使用该索引就可以根据上一次计算出的统计数据挑出查询处理器怀疑在特拉华州的less数雇员。 如果只有E.age,那么查询处理器可能认为这不值得,因为所有雇员中的96%年龄在22岁以上。 因此,如果E.state被索引,我们的查询处理器就会中断Select并将E.state ='Delaware'与Scan结合起来 ,将其变成一个更高效的索引扫描

在这个例子中,我们说E.state和E.age没有索引。 合并select操作发生在员工的顺序“扫描”之后。 在“ select”中哪个条件首先完成会有什么不同? 可能不是很多。 查询处理器可能会将它们保留在SQL语句中的原始顺序中,或者可能会更复杂一些,并查看预期的开销。 从统计数据中可以看出,E.state ='Delaware'的条件应该更具有select性,所以它会先改变条件,那么只有126条大于21的比较而不是5000条。 或者可能会意识到,string相等比较比整数比较昂贵得多,并且仅保留订单。

无论如何,这一切都是非常复杂的,你的语法条件顺序是不太可能有所作为。 除非你有一个真正的性能问题,并且你的数据库供应商使用条件顺序作为提示,否则我不会担心。

它不应该在你的小例子。 查询优化器应该做正确的事情。 您可以通过在查询的前面添加explain来确认。 MySQL会告诉你它是如何连接在一起的,它需要search多less行来完成连接。 例如:

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

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

大多数查询优化器使用条件出现的顺序作为提示。 如果一切都是平等的,他们将遵循这个顺序。

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

  • 第二个领域有一个索引,第一个没有
  • 有统计数字表明场2更具select性
  • 第二个字段更容易search( varchar(max) vs int

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