【问题标题】:Prohibit MySQL from using full table scan on a query禁止 MySQL 对查询使用全表扫描
【发布时间】:2009-10-24 07:51:14
【问题描述】:

当使用索引找不到结果时,有什么方法可以禁止 MySQL 执行全表扫描?

例如这个查询:

SELECT *
FROM a
WHERE (X BETWEEN a.B AND a.C) 
ORDER BY a.B DESC 
LIMIT 1;

只有当 X 满足条件并且至少返回 1 行时才有效,但是如果表中的任何数据都不能满足条件,则会执行全扫描,这可能会非常昂贵。

我不想优化这个特定的查询,这只是一个例子。

在此查询中解释 X 在或超出范围:

id select_type table type possible_keys key key_len ref rows filtered Extra
1 SIMPLE a range long_ip  long_ip 8 \N 116183 100.00 Using where

状态变量显示更好的信息。对于 X 超出范围:

Handler_read_prev 84181
Key_read_requests 11047

范围内:

Handler_read_key 1
Key_read_requests 12

如果有办法阻止 Handler_read_prev 超过 1 就好了。

更新。我不能接受我自己的答案,因为它并不能真正回答问题(不过,HANDLER 是一个很棒的功能)。在我看来,没有通用的方法可以阻止 MySQL 进行完整扫描。虽然像 key='X' 这样的简单条件将被视为“不可能在哪里”,但像 BETWEEN 这样更复杂的条件则不会。

【问题讨论】:

  • 我假设您对 (B, C) 对有一个索引,而不是每列单独,对吧?可能是索引统计有误,你有没有试过在表上运行ANALYZE
  • 是的,但是“索引统计信息有误”是什么意思?我不是想优化这个查询,我只是想问有没有办法禁止 MySQL 执行全表扫描。
  • 查询优化器依赖于数据统计。它根据统计数据确定成本并选择最优化的方式。如果统计信息关闭,成本估算就会出错,它不会使用最佳方式获取数据。我这么说只是因为在具有新创建索引的大表上,即使查询没有返回结果,它也会使用索引。
  • @HeavyWave - Mea culpa maxima - 永远不要在即将出门时匆忙发布答案!
  • 您能否发布您的表和索引定义以及查询的 EXPLAIN 输出?

标签: sql mysql query-optimization


【解决方案1】:

您可以编写一个“完全覆盖”的子查询,它只使用索引中可用的数据。根据返回的主键,您可以在主表中查找行。

以下查询被 (id)、(B,id) 和 (C,id) 上的索引完全覆盖:

select *
from a
where id in (
    select id
    from a 
    where x <= C
    and id in (
        select id
        from a
        where B <= X 
    )
)
limit 1

每个 SELECT 使用一个索引:(B,id) 上最里面的索引;中间 SELECT 使用 (C,id) 上的索引,外部 SELECT 使用主键。

【讨论】:

  • LIMIT 和 IN 不受支持,此外这个查询会非常慢,而且我看不出我的查询是如何被索引覆盖的。事实上,问题是,当 MySQL 没有在索引中找到答案时如何停止它(它应该首先这样做)。
  • 您对子查询中的限制是正确的,将其移至外部查询。当没有找到结果时,我提出的查询不会进行表扫描,因为中间选择将为空。应该挺快的,你试过了吗?
  • 是的,它在 10 mb 表上运行超过一秒。 2 个内部查询返回数千行。
  • 如果您创建了索引,它应该尽可能快。如果有额外的限制,你可以做得更好。例如,如果 B 和 C 在行之间从不重叠,您可以搜索 X 下方的第一个 B。那将非常快。
  • 你试过了吗? MySQL 5 上的任何索引都非常慢。
【解决方案2】:

这是我最后想到的:

HANDLER a OPEN;
HANDLER a READ BC <= (X);
HANDLER a CLOSE;

BC 是键名 (B,C)。如果我们按 B DESC 对表进行排序,那么结果保证等于

SELECT *
FROM a
WHERE (X BETWEEN a.B AND a.C) 
ORDER BY a.B DESC 
LIMIT 1;

现在如果 X 不在表 a 的范围内,我们只需要检查 a.C 是否大于 X,如果不是,那么 X 肯定在范围之外,我们不需要进一步查看.

虽然这不是很优雅,您必须在每次插入或更新时重新使用表。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-10-22
    • 2020-05-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-01-31
    • 1970-01-01
    相关资源
    最近更新 更多