为什么向我的查询添加 WHERE 语句(在具有索引的列上)会使我的 运行 时间从几秒增加到几分钟?

Why does adding a WHERE statement (on a column with an index) to my query increase my run time from seconds to minutes?

我的问题是 MySQL 中的这个查询:

select 
    SUM(OrderThreshold < @LOW_COST) as LOW_COUNT,
    SUM(OrderThreshold > @HIGH_COST) as HIGH_COUNT
FROM parts
-- where parttypeid = 1

where 取消注释时,我的 运行 时间跳了 4.5 秒到 341 秒。 table 中总共有大约 2100 万条记录。

我的 EXPLAIN 看起来像这样,这似乎表明它正在使用我在 PartTypeId.

上的索引
id  select_type table   type    possible_keys   key         key_len ref rows    Extra
1   SIMPLE      parts   ref     PartTypeId      PartTypeId  1       const       11090057    

我使用此查询创建了 table:

CREATE TABLE IF NOT EXISTS parts (
    Id INTEGER NOT NULL PRIMARY KEY, 
    PartTypeId TINYINT NOT NULL, 
    OrderThreshold INTEGER NOT NULL, 
    PartName VARCHAR(500), 
    INDEX(Id),
    INDEX(PartTypeId),
    INDEX(OrderThreshold),
);

没有 WHERE returns

的查询
LOW_COUNT   HIGH_COUNT
3570        3584

使用 where 结果如下所示:

LOW_COUNT   HIGH_COUNT
2791        2147

添加仅查看一列的 where 语句时,如何提高查询性能以将 运行 时间减少到秒(而不是分钟)范围内?

尝试

select SUM(OrderThreshold < @LOW_COST) as LOW_COUNT,
       SUM(OrderThreshold > @HIGH_COST) as HIGH_COUNT
from parts 
where parttypeid = 1
and OrderThreshold not between @LOW_COST and @HIGH_COST

select count(*) as LOW_COUNT, null as HIGH_COUNT
from parts 
where parttypeid = 1
and OrderThreshold < @LOW_COST
union all
select null, count(*) 
from parts 
where parttypeid = 1
and OrderThreshold > @HIGH_COST

您接受的答案没有解释您的原始查询出了什么问题:

select SUM(OrderThreshold < @LOW_COST) as LOW_COUNT,
       SUM(OrderThreshold > @HIGH_COST) as HIGH_COUNT
from parts
where parttypeid = 1;

正在使用索引查找结果,但有很多行 parttypeid = 1。我猜测每个数据页可能至少有一个这样的行。这意味着正在获取所有行,但它们被乱序读取。这比只进行完整的 table 扫描(如在第一个查询中)要慢。换句话说,正在读取所有数据页,但索引增加了额外的开销。

正如 Juergen 指出的那样,更好的查询形式将条件移动到 where 子句中:

select SUM(OrderThreshold < @LOW_COST) as LOW_COUNT,
       SUM(OrderThreshold > @HIGH_COST) as HIGH_COUNT
from parts
where parttypeid = 1 AND
      (OrderThreshold < @LOW_COST OR OrderThreshold > @HIGH_COST)

(我更喜欢这种形式,因为 where 条件与 case 条件匹配。)对于此查询,您需要 parts(parttypeid, OrderThreshold) 上的索引。在这种情况下,我不确定 MySQL 优化器,但最好写成:

select 'Low' as which, count(*) as CNT
from parts
where parttypeid = 1 AND
      OrderThreshold < @LOW_COST
union all
select 'High', count(*) as CNT
from parts
where parttypeid = 1 AND
      OrderThreshold > @HIGH_COST;

在这种情况下,每个子查询肯定应该使用索引。 (如果你想让它们排成两列,有几种方法可以实现,但我猜这不是那么重要。)

不幸的是,没有 where 子句的查询的最佳索引是 parts(OrderThreshold)。这与上面的索引不同。