【问题标题】:How do I speed up a SQL query that uses a BETWEEN condition?如何加快使用 BETWEEN 条件的 SQL 查询?
【发布时间】:2012-10-03 08:47:26
【问题描述】:

我有一张如下所示的表格:

ID    StartRange    EndRange
----------------------------
 1        1            3
 2        4            8
 3        9           12

以此类推,这样就有超过 500 万条记录。最后一条记录如下所示:

ID        StartRange    EndRange
---------------------------------
5235976   9894727374   9894727378

换句话说,StartRangeEndRange 对于每条记录永远不会重叠。

我需要做一个查询,找到与范围匹配的数字的对应 ID:

SELECT ID FROM BigTable WHERE '5000000' BETWEEN StartRange AND EndRange;

很遗憾,此查询需要几秒钟才能完成。我需要对其进行优化,使其花费最少的执行时间。我做了一些研究,似乎添加索引没有帮助,因为它仅适用于数字恰好是 StartRangeEndRange 值,但不是介于两者之间。

有没有人可以使用任何提示或技巧来缩短执行时间?理想情况下,如果可能的话,我希望它在 1 秒以下。

【问题讨论】:

  • 您当前的表架构是什么? (SHOW CREATE TABLE your_table 的输出)
  • 你也可以发布DESCRIBE SELECT ID FROM BigTable WHERE '5000000' BETWEEN StartRange AND EndRange的输出吗?这将显示您的索引(x|ces)是否被使用。
  • 当然,输出是这样的:1 SIMPLE BigTable ALL ID,StartRange,EndRange 5522123 Using where
  • 如果您创建@Jocelyn 的回答中描述的索引,您会得到相同的结果,还是现在使用密钥?我跑了一个小测试用例,确实按照explain使用了key。
  • 我刚刚试过了,EXPLAIN 的输出是一样的。

标签: mysql sql optimization


【解决方案1】:

我在使用 IP 地址范围表时遇到了类似的问题,下面的内容确实为我解决了问题。您需要一个至少在 StartRange 上的索引。

SELECT ID
FROM BigTable
INNER JOIN
  (SELECT MAX(StartRange) AS start
   FROM BigTable
   WHERE StartRange <= @Target) AS s
ON StartRange = s.start
WHERE EndRange >= @Target;

【讨论】:

  • 自您发布此答案已有 6 年多。事实证明,我来这里是为了寻找一种快速搜索 IP 地址范围(来自 GeoLite)的方法。这个答案将查找速度提高了 10 倍。谢谢!!
【解决方案2】:

为您的表添加一个复合索引。该索引必须由StartRangeEndRange 字段组成:

ALTER TABLE `BigTable` ADD INDEX ( `StartRange` , `EndRange` );

然后在查询中使用EXPLAIN 来检查是否使用了新索引:

EXPLAIN SELECT ID FROM BigTable WHERE '5000000' BETWEEN StartRange AND EndRange;

输出显示 MySQL 无法在此查询中使用新索引。然后你可以重写你的初始查询:

SELECT ID FROM BigTable WHERE StartRange>='5000000' AND EndRange<='5000000'
                            OR EndRange>='5000000' AND StartRange<='5000000'

此新查询将返回与您的初始查询相同的结果。好消息是EXPLAIN

EXPLAIN SELECT ID FROM BigTable WHERE StartRange>='5000000' AND EndRange<='5000000'
                            OR EndRange>='5000000' AND StartRange<='5000000'

现在的输出显示 MySQL 能够使用新索引。

【讨论】:

    【解决方案3】:

    即使该值与StartRangeEndRange 不匹配,索引也应该可以很好地处理此查询。

    【讨论】:

    • 我同意。它应该会显着加快速度。
    • 我为StartRangeEndRange添加了一个索引,但似乎没有任何速度提升(一个简单的查询仍然需要大约10秒)。
    • 那么请按照 Jocelyn 和 willoller 的建议进行调试。
    【解决方案4】:

    索引不会加速这个查询。索引可用于 BETWEEN 搜索,但只能用于“正确”的搜索(例如 StartRange BETWEEN 10000 AND 20000)。

    为了加快这个查询,你将不得不使用一些诡计。

    首先,如果范围表是静态的或没有快速增长,并且范围值确实是整数,则可以生成一个额外的表,其中包含从最低 StartRange 到最高 EndRange 的所有值以及匹配的 id。然后您可以搜索您需要的确切值。

    或者,计算 EndRange - StartRange 的最大值并将其称为 MaxRange。在 StartRange 上创建索引并将查询更改为:

     SELECT ID FROM BigTable 
        WHERE StartRange BETWEEN ('5000000' - MaxRange) AND '5000000' 
          AND '5000000' BETWEEN StartRange AND EndRange;
    

    现在,第一个 BETWEEN 子句可索引的并且应该返回少量的行。然后,第二个 BETWEEN 子句将仅应用于那一小部分行。显然,这取决于您是否能够提前计算 MaxRange 的安全值。希望该范围有一些实际的最大可能值可以告诉您这个数字。

    【讨论】:

    • +1 有趣...您是否碰巧有一个很好的资源来深入讨论这个案例?似乎有时使用了索引,但只使用了第一部分,在最坏的情况下,它最终还是会查看每一行?还是别的什么?
    • 感谢拉里的回答。我们将进行调查,看看这是否对我们有帮助。第一种方法可能不实用,因为它会创建一个包含超过 90 亿行的表。
    • 那肯定会有很多行。顺便说一句,SQL 可能不是解决这个问题的最佳方法。你有没有考虑过像二分搜索这样的东西?您可以从表中执行此操作,或者根据环境维护此特定搜索的内存结构。
    猜你喜欢
    • 2018-09-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多