是否可以优化使用“<>”运算符的查询?
Posted
技术标签:
【中文标题】是否可以优化使用“<>”运算符的查询?【英文标题】:Is it possible to optimize a query that uses the '<>' operator? 【发布时间】:2009-07-28 20:37:01 【问题描述】:这是a previous question的后续。
如何优化此查询使其不执行全表扫描?
SELECT Employee.name FROM Employee WHERE Employee.id <> 1000;
.
explain SELECT Employee.name FROM Employee WHERE Employee.id <> 1000;
+----+-------------+-------------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------------+------+---------------+------+---------+------+------+-------------+
| 1 | SIMPLE | Employee | ALL | PRIMARY | NULL | NULL | NULL | 5000 | Using where |
+----+-------------+-------------+------+---------------+------+---------+------+------+-------------+
(Empoyee.id
是主键,以防不清楚。)
【问题讨论】:
确保 Employee.id 上有索引 您要返回除 1 之外的所有行吗?如果是这样,几乎没有任何理由使用任何索引。引擎可能会决定不使用索引,例如行数相当低。 【参考方案1】:对name和id有一个覆盖索引,它应该能够使用索引完成查询。这可能会更快,因为整个索引很有可能已经在内存中,而表扫描更有可能需要进入磁盘。
由于 where 子句的选择性低(不存在),您可能需要提供提示以使数据库使用您的索引。我是一个 sql server 人,所以我不确定 mysql 中提示索引所需的语法,或者即使 mysql 能够以这种方式利用覆盖索引。
也就是说,我怀疑您能否获得很大的改进:您将返回每一行,但只有一行。您应该期望它需要扫描表。
【讨论】:
+1:谢谢,这是我知道的,但没有建立联系……经验为王!好的,我现在看到了您的编辑:我认为它至少可以提供帮助,拥有足够智能的 DB-Engine。您将得到的是“全索引扫描”而不是“全表扫描”。由于 2 列的索引大部分时间都小于整个表,因此它会更快(不在内存中时也是如此!)。 返回除一个之外的所有行听起来像是一场灾难,它将进行表扫描,或者至少进行一次可能几乎一样糟糕的全索引扫描(取决于架构和数据) @Mark:为什么大家都在恐慌,当他们在这里讨论全表扫描时?想一想——必须返回什么?除一个以外的所有员工姓名。您至少需要阅读此信息——并且阅读此信息 + 另一列(id,也需要用于比较)。我想说,在关系模型中没有更好的解决方案。当然,你总是可以为这个非常特殊的问题考虑专门的模型——但这是否合理???【参考方案2】:有很多东西可以尝试,这取决于数据库引擎选择如何解析它,真的。一些选项:
select employee.name from employee where employee.id not in (1000);
你也可以尝试一个小于然后大于的联合。
但是在您给出的具体示例中(对于您的实际情况可能太简单了),表扫描不一定是坏事。如果必须返回除一条之外的所有记录,则使用索引实际上可能会更慢。
【讨论】:
除了礼貌之外,给出拒绝投票的理由有助于进一步讨论。只是投票不会。 @Yishai,对不起,你是对的......我应该说的是:当数据库看到它必须读取超过20-30%的包含表中行的页面时,它认识到遍历索引(索引中的每个级别需要一个 I/O 操作)会更加昂贵,并且它只是进行表扫描 我以为我在上一段中说过。我的回答是假设该问题旨在代表特定的技术挑战(不等于索引字段)而不是特定示例。很多时候,为了简单起见,关于 SO 的问题并不是真实情况。【参考方案3】:在传统数据库中,你不能!
当然,您可以省略所有具有给定 ID 的员工(当它是键或具有索引时)——但通常您仍将拥有表的绝大部分。所以使用索引可能会使事情复杂化,因此 fts 通常是更快的选择。
当您拥有专门的数据库时,您可以存储彼此相邻的所有员工的姓名。
编辑:我现在看到了乔尔的另一个答案。是的,这可能是一种方式,因为实际上您的特殊索引现在是一种存储部分内容的特殊形式。好的数据库可以在索引内容覆盖所需的列时使用它——这相当不错。当然,您最终会进行所谓的“全索引扫描”(但通常比全表扫描快得多)。
【讨论】:
事实上,这根本不会得到任何改善,因为使用索引会降低性能,而不是提高性能,因为表中的每条记录都必须从磁盘读取,但只有一条。如果查询优化器愚蠢到使用索引,(幸好它比这更聪明)它必须添加额外的 IO 操作来读取所有 IO 读取之上的索引页,这是执行完整表扫描所必需的. @Charles:你解释了,我想用更少的话来表达。但是有一个例外,Joel 谈到了——他是对的,至少对于某些数据库而言(并非所有人都能找到这种优化)。【参考方案4】:您无能为力,不会提高性能。在这种情况下,数据库必须执行完整的表扫描,因为您要求每条记录保存一个。在此之上读取索引中的每一页只会降低性能。幸运的是,即使您添加了索引,数据库也足够聪明,可以忽略它...
编辑以解决@Juergens 评论。 于尔根,你对覆盖指数的看法是对的,但这里有相互矛盾的影响。在这样的场景中对索引的任何使用在某种意义上都会产生不良影响......查询引擎可能必须为索引中的每个级别以及需要检查的每一行执行一次 I/O 操作。例如,如果索引中有 5 个级别和 100 万行,那将是 500 万次 I/O 操作,而进行完整的表扫描则只有 100 万次 I/O。这就是为什么在这种情况下,大多数查询优化器会忽略任何可用的索引并进行表扫描。 (除非您强制它使用带有提示的索引)唯一的缓解因素是查询所需的每个属性是否都在索引中(覆盖索引)并且磁盘上每页的索引行数足够小于每页表行数,以抵消必须为查询返回的每一行遍历索引的每一级的负面影响。
【讨论】:
你在一方面是对的。但是当索引覆盖所有需要的列时,仍然有足够智能的数据库来完成从索引中的查询——因为这样可以节省对特定数据库条目的访问。足够智能的数据库引擎可以使用 (id, name) 的索引来避免表访问——因此只执行全索引扫描。 您对需要 IO 的概念是错误的。这不是每条记录的单独 IO 操作。这是每个 page 的单独 IO 操作。因为我们要设计索引,所以无论如何它都会是 1:1,而且这个索引很可能一开始就保存在内存中,所以可能根本没有任何磁盘 IO。至少,索引可能小于表(假设他没有告诉我们的列更多),因此要读取的总页数更少。以上是关于是否可以优化使用“<>”运算符的查询?的主要内容,如果未能解决你的问题,请参考以下文章