【问题标题】:Utilize indexes for MySQL subqueries or joins over ranges为 MySQL 子查询或跨范围连接使用索引
【发布时间】:2020-05-19 08:47:37
【问题描述】:

我有一个请求列表及其各自的 IP 地址(约 200 万行)。我正在尝试在 non-overlappingcomplete 的 IP 范围列表(约 1200 万行)列表上做一个简单的 JOIN。我已经用ip_from b_tree 升序和ip_to b_tree 升序索引了IP范围。

我尝试了几种技术来管理这两个表中的数据,但到目前为止都证明效率很低。

我尝试了常规的JOINJOIN,IP 范围为maximum difference,并使用了子查询。使用EXPLAIN,他们都显示有possible_keys,但没有使用它们。我试过使用FORCE INDEX 没有任何运气。

Regular select 分别显示SELECT * FROM ip_ranges WHERE INET_ATON(<some ip>) <= ip_to LIMIT 1; 的 IP 查找大约需要 2 毫秒,而请求表每 200 次查找大约需要 16 毫秒。

这是我当前的查询。仅仅因为索引没有被充分利用,这需要大约 30 秒才能返回任何结果:

SELECT 
rs.fingerprint,
rs.ip,
ipr.country_code,
ipr.country_name,
ipr.region,
ipr.city,
ipr.isp_name,
ipr.domain_name,
ipr.usage_type
FROM requests AS rs
JOIN ip_ranges AS ipr ON INET_ATON(rs.ip) BETWEEN ipr.ip_from AND ipr.ip_to
LIMIT 10;

那么,有什么方法可以针对 MySQL 进行优化吗?还是我应该只使用 Python 为每个请求单独调用数据库? (在 SQL 之外手动加入它们)。

更新:

我现在尝试将每个 IP 地址转换为它们各自的数字格式,存储在名为 ip_numericDECIMAL(39) 列中,如下面的答案中所建议的那样。 39 也用于支持 IPv6 地址。数据库仍然不会使用索引键进行范围查找。

【问题讨论】:

  • @TheImpaler 您能否详细说明“左侧综合征”。 rs.ip 是一个 IP 地址,我需要数字版本来加入范围。
  • 一种优化方法是存储和索引 aton 值(或 ntoa 值)。顺便说一句,没有 ORDER BY 的 LIMIT 没有多大意义。
  • 如果没有ORDER BY,您会从LIMIT 获得一组不可预测的10 行;这是你所期望的吗?还是您打算对所有 2M 行执行此查询?
  • @RickJames 是的,我使用限制 10 只是为了避免必须查询所有 2M 行以进行基准测试。
  • @CompSci - 请注意,根据优化机会,这 3 种情况的行为可能会有很大不同:1) 只是 LIMIT,2) ORDER BY + LIMIT,3) 两者都不是。要获得“真实”的答案,您需要提出“真实”的问题。

标签: mysql sql ip query-optimization


【解决方案1】:

您可以向表和索引添加一个虚拟列:

ALTER TABLE requests ADD ip_numeric bigint GENERATED ALWAYS AS (INET_ATON(ip)) virtual;

CREATE INDEX ip_numeric_ind ON requests (ip_numeric)

然后在您的查询中使用它:

SELECT 
rs.fingerprint,
rs.ip,
ipr.country_code,
ipr.country_name,
ipr.region,
ipr.city,
ipr.isp_name,
ipr.domain_name,
ipr.usage_type
FROM requests AS rs
JOIN ip_ranges AS ipr ON ip_numeric BETWEEN ipr.ip_from AND ipr.ip_to
LIMIT 10;

【讨论】:

  • 是否可以根据rs.ip_version使虚拟列有条件?
  • 如果您的ip-column 可以同时包含 IPv4 和 IPv6 地址,您应该改用INET6_ATON-function。它将返回 IPv4 和 IPv6 地址的二进制版本。
【解决方案2】:

因为无法在 FUNCTION RESULT(您的 INET_ATON 的 IP 地址)上优化连接,所以它不会利用索引。

要更正此问题,我将执行以下操作... 在插入请求文件之前应用地址的 INET_ATON()。这样,IP 地址在文件中就已经是其格式正确的标准。对 IP_Ranges(从和到)执行相同的操作,以便它们也具有预先确认的正确格式一致性。

然后,在将“介于”应用于测试之前,不必每次都对 ip 上的连接进行评估/转换。

反馈

列上的索引,而不是函数...没有具体的文档,仅凭经验。该索引基于 COLUMN 的值。如果要加入函数结果,则必须根据每条记录的原始列运行该结果。因此,通过存储 IP 的预先计算的最终值,您现在拥有格式正确的地址,并且索引可以直接在该地址上运行,无需再进行转换。同样,当使用 from/to 地址填充 JOIN TO 表时,您现在将数据预先强制为最终格式以进行比较。

很像日期索引。只需索引日期字段,而不是月/年。然后,当您运行查询并且想要上个月的内容时,您不会执行一个月( someDateColumn )= 10 和一年( someDateColumn )= 2019。您只需执行 someDateColumn >= '2019-10-01'和 someDateColumn

【讨论】:

  • 哇,我不知道函数结果问题。你能指导我到哪里我可以在 MySQL 文档中阅读更多关于它的信息吗?目前我没有必要的项目,但我会在测试后尽快回复有关此解决方案的反馈。
  • @CompSci,在回答中看到反馈太多,无法发表评论,其他人更容易看到原因。
  • 参见维基百科中的“sargable”。
  • @RickJames,以前从未听说过这个词,只是知道这不是查询的最佳选择。谢谢
  • 尝试将我所有的 IP 转换为数值。索引仍然没有运气......它们也必须是完全相同的类型吗?使用DECIMAL(39)ip_numeric 并尝试根据IP 版本加入DECIMAL(10)DECIMAL(39`)。
【解决方案3】:

如果您可以保证 from..to 对不重叠,则有一种方法可以显着加快此类表的速度。它涉及构建一个只有ip_from 的表,知道ip_to 比下一行的ip_from 小一。

讨论,包括 IPv4 和 IPv6 的参考代码:http://mysql.rjweb.org/doc.php/ipranges

可能是一种罕见的情况,CURSORs 的工作速度比尝试在单个查询中完成的要快。

也就是说,使用上述技术进行 10 次单独的查找会非常快。如果您需要进行 2M 查找,我们需要重新开始。一个想法:对2M和12M进行排序;随时随地匹配。 (类似于传统“排序合并”算法的“合并”部分。)(不,我没有仔细考虑细节。)

【讨论】:

    【解决方案4】:

    很难让索引使用范围。我推荐以下方法:

    • 查找范围在给定 ip 上或之后结束的第一个范围。
    • 重新加入范围表以获取起点
    • 比较!

    作为 SQL:

    select r.*, ir.*
    from (select r.*,
                 (select ir.ip_to
                  from ip_ranges ir
                  where ir.ip_to >= inet_aton(r.ip)
                  order by ir.ip_to
                  limit 1
                 ) as range_to
          from requests r
         ) r join
         ip_ranges ir
         on ir.ip_to = r.range_to
    where r.ip >= ir.ip_to;
    

    这需要ip_ranges(ip_to) 上的索引,用于相关子查询和最终join

    【讨论】:

      猜你喜欢
      • 2018-05-24
      • 2021-12-20
      • 2014-03-01
      • 1970-01-01
      • 2021-02-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多